{"version":3,"file":"index.memory.esm.js","sources":["../src/core/version.ts","../src/util/log.ts","../src/platform/browser/format_json.ts","../src/util/assert.ts","../src/platform/browser/random_bytes.ts","../src/util/misc.ts","../src/core/database_info.ts","../src/util/obj.ts","../src/util/obj_map.ts","../src/util/error.ts","../src/api/timestamp.ts","../src/core/snapshot_version.ts","../src/model/path.ts","../src/model/document_key.ts","../src/util/types.ts","../src/core/target.ts","../src/core/query.ts","../src/util/byte_string.ts","../src/platform/browser/base64.ts","../src/remote/rpc_error.ts","../src/local/target_data.ts","../src/remote/existence_filter.ts","../src/util/sorted_map.ts","../src/util/sorted_set.ts","../src/model/collections.ts","../src/model/document_set.ts","../src/core/view_snapshot.ts","../src/remote/remote_event.ts","../src/remote/watch_change.ts","../src/model/server_timestamps.ts","../src/model/values.ts","../src/remote/serializer.ts","../src/model/transform_operation.ts","../src/model/mutation.ts","../src/model/object_value.ts","../src/model/document.ts","../src/model/mutation_batch.ts","../src/local/persistence_promise.ts","../src/local/local_documents_view.ts","../src/local/local_view_changes.ts","../src/core/listen_sequence.ts","../src/util/promise.ts","../src/remote/backoff.ts","../src/local/memory_index_manager.ts","../src/core/target_id_generator.ts","../src/local/simple_db.ts","../src/platform/browser/dom.ts","../src/util/async_queue.ts","../src/local/lru_garbage_collector.ts","../src/local/local_store.ts","../src/local/persistence.ts","../src/local/reference_set.ts","../src/util/input_validation.ts","../src/api/blob.ts","../src/api/field_path.ts","../src/api/field_value.ts","../src/api/geo_point.ts","../src/platform/browser/serializer.ts","../src/api/user_data_reader.ts","../src/auth/user.ts","../src/api/credentials.ts","../src/remote/persistent_stream.ts","../src/remote/datastore.ts","../src/core/transaction.ts","../src/remote/online_state_tracker.ts","../src/remote/remote_store.ts","../src/local/shared_client_state.ts","../src/core/view.ts","../src/core/transaction_runner.ts","../src/core/sync_engine.ts","../src/core/event_manager.ts","../src/local/index_free_query_engine.ts","../src/local/memory_mutation_queue.ts","../src/local/memory_remote_document_cache.ts","../src/local/remote_document_change_buffer.ts","../src/local/memory_target_cache.ts","../src/local/memory_persistence.ts","../src/remote/stream_bridge.ts","../src/platform/browser/webchannel_connection.ts","../src/platform/browser/connectivity_monitor.ts","../src/remote/connectivity_monitor_noop.ts","../src/core/component_provider.ts","../src/platform/browser/connection.ts","../src/core/firestore_client.ts","../src/util/async_observer.ts","../src/api/observer.ts","../src/api/user_data_writer.ts","../src/api/database.ts","../src/config.ts","../index.memory.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport firebase from '@firebase/app';\n\n/** The semver (www.semver.org) version of the SDK. */\nexport const SDK_VERSION = firebase.SDK_VERSION;\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Logger, LogLevel, LogLevelString } from '@firebase/logger';\nimport { SDK_VERSION } from '../core/version';\nimport { formatJSON } from '../platform/format_json';\n\nexport { LogLevel };\n\nconst logClient = new Logger('@firebase/firestore');\n\n// Helper methods are needed because variables can't be exported as read/write\nexport function getLogLevel(): LogLevel {\n return logClient.logLevel;\n}\n\nexport function setLogLevel(newLevel: LogLevelString | LogLevel): void {\n logClient.setLogLevel(newLevel);\n}\n\nexport function logDebug(msg: string, ...obj: unknown[]): void {\n if (logClient.logLevel <= LogLevel.DEBUG) {\n const args = obj.map(argToString);\n logClient.debug(`Firestore (${SDK_VERSION}): ${msg}`, ...args);\n }\n}\n\nexport function logError(msg: string, ...obj: unknown[]): void {\n if (logClient.logLevel <= LogLevel.ERROR) {\n const args = obj.map(argToString);\n logClient.error(`Firestore (${SDK_VERSION}): ${msg}`, ...args);\n }\n}\n\nexport function logWarn(msg: string, ...obj: unknown[]): void {\n if (logClient.logLevel <= LogLevel.WARN) {\n const args = obj.map(argToString);\n logClient.warn(`Firestore (${SDK_VERSION}): ${msg}`, ...args);\n }\n}\n\n/**\n * Converts an additional log parameter to a string representation.\n */\nfunction argToString(obj: unknown): string | unknown {\n if (typeof obj === 'string') {\n return obj;\n } else {\n try {\n return formatJSON(obj);\n } catch (e) {\n // Converting to JSON failed, just log the object directly\n return obj;\n }\n }\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/** Formats an object as a JSON string, suitable for logging. */\nexport function formatJSON(value: unknown): string {\n return JSON.stringify(value);\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SDK_VERSION } from '../core/version';\nimport { logError } from './log';\n\n/**\n * Unconditionally fails, throwing an Error with the given message.\n * Messages are stripped in production builds.\n *\n * Returns `never` and can be used in expressions:\n * @example\n * let futureVar = fail('not implemented yet');\n */\nexport function fail(failure: string = 'Unexpected state'): never {\n // Log the failure in addition to throw an exception, just in case the\n // exception is swallowed.\n const message =\n `FIRESTORE (${SDK_VERSION}) INTERNAL ASSERTION FAILED: ` + failure;\n logError(message);\n\n // NOTE: We don't use FirestoreError here because these are internal failures\n // that cannot be handled by the user. (Also it would create a circular\n // dependency between the error and assert modules which doesn't work.)\n throw new Error(message);\n}\n\n/**\n * Fails if the given assertion condition is false, throwing an Error with the\n * given message if it did.\n *\n * Messages are stripped in production builds.\n */\nexport function hardAssert(\n assertion: boolean,\n message?: string\n): asserts assertion {\n if (!assertion) {\n fail(message);\n }\n}\n\n/**\n * Fails if the given assertion condition is false, throwing an Error with the\n * given message if it did.\n *\n * The code of callsites invoking this function are stripped out in production\n * builds. Any side-effects of code within the debugAssert() invocation will not\n * happen in this case.\n */\nexport function debugAssert(\n assertion: boolean,\n message: string\n): asserts assertion {\n if (!assertion) {\n fail(message);\n }\n}\n\n/**\n * Casts `obj` to `T`. In non-production builds, verifies that `obj` is an\n * instance of `T` before casting.\n */\nexport function debugCast(\n obj: object,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n constructor: { new (...args: any[]): T }\n): T | never {\n debugAssert(\n obj instanceof constructor,\n `Expected type '${constructor.name}', but was '${obj.constructor.name}'`\n );\n return obj as T;\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { debugAssert } from '../../util/assert';\n\n/**\n * Generates `nBytes` of random bytes.\n *\n * If `nBytes < 0` , an error will be thrown.\n */\nexport function randomBytes(nBytes: number): Uint8Array {\n debugAssert(nBytes >= 0, `Expecting non-negative nBytes, got: ${nBytes}`);\n\n // Polyfills for IE and WebWorker by using `self` and `msCrypto` when `crypto` is not available.\n const crypto =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n typeof self !== 'undefined' && (self.crypto || (self as any)['msCrypto']);\n const bytes = new Uint8Array(nBytes);\n if (crypto) {\n crypto.getRandomValues(bytes);\n } else {\n // Falls back to Math.random\n for (let i = 0; i < nBytes; i++) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n return bytes;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { debugAssert } from './assert';\nimport { randomBytes } from '../platform/random_bytes';\n\nexport type EventHandler = (value: E) => void;\nexport interface Indexable {\n [k: string]: unknown;\n}\n\nexport class AutoId {\n static newId(): string {\n // Alphanumeric characters\n const chars =\n 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n // The largest byte value that is a multiple of `char.length`.\n const maxMultiple = Math.floor(256 / chars.length) * chars.length;\n debugAssert(\n 0 < maxMultiple && maxMultiple < 256,\n `Expect maxMultiple to be (0, 256), but got ${maxMultiple}`\n );\n\n let autoId = '';\n const targetLength = 20;\n while (autoId.length < targetLength) {\n const bytes = randomBytes(40);\n for (let i = 0; i < bytes.length; ++i) {\n // Only accept values that are [0, maxMultiple), this ensures they can\n // be evenly mapped to indices of `chars` via a modulo operation.\n if (autoId.length < targetLength && bytes[i] < maxMultiple) {\n autoId += chars.charAt(bytes[i] % chars.length);\n }\n }\n }\n debugAssert(autoId.length === targetLength, 'Invalid auto ID: ' + autoId);\n\n return autoId;\n }\n}\n\nexport function primitiveComparator(left: T, right: T): number {\n if (left < right) {\n return -1;\n }\n if (left > right) {\n return 1;\n }\n return 0;\n}\n\nexport interface Equatable {\n isEqual(other: T): boolean;\n}\n\n/** Helper to compare arrays using isEqual(). */\nexport function arrayEquals(\n left: T[],\n right: T[],\n comparator: (l: T, r: T) => boolean\n): boolean {\n if (left.length !== right.length) {\n return false;\n }\n return left.every((value, index) => comparator(value, right[index]));\n}\n/**\n * Returns the immediate lexicographically-following string. This is useful to\n * construct an inclusive range for indexeddb iterators.\n */\nexport function immediateSuccessor(s: string): string {\n // Return the input string, with an additional NUL byte appended.\n return s + '\\0';\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { primitiveComparator } from '../util/misc';\n\nexport class DatabaseInfo {\n /**\n * Constructs a DatabaseInfo using the provided host, databaseId and\n * persistenceKey.\n *\n * @param databaseId The database to use.\n * @param persistenceKey A unique identifier for this Firestore's local\n * storage (used in conjunction with the databaseId).\n * @param host The Firestore backend host to connect to.\n * @param ssl Whether to use SSL when connecting.\n * @param forceLongPolling Whether to use the forceLongPolling option\n * when using WebChannel as the network transport.\n */\n constructor(\n readonly databaseId: DatabaseId,\n readonly persistenceKey: string,\n readonly host: string,\n readonly ssl: boolean,\n readonly forceLongPolling: boolean\n ) {}\n}\n\n/** The default database name for a project. */\nconst DEFAULT_DATABASE_NAME = '(default)';\n\n/** Represents the database ID a Firestore client is associated with. */\nexport class DatabaseId {\n readonly database: string;\n constructor(readonly projectId: string, database?: string) {\n this.database = database ? database : DEFAULT_DATABASE_NAME;\n }\n\n get isDefaultDatabase(): boolean {\n return this.database === DEFAULT_DATABASE_NAME;\n }\n\n isEqual(other: {}): boolean {\n return (\n other instanceof DatabaseId &&\n other.projectId === this.projectId &&\n other.database === this.database\n );\n }\n\n compareTo(other: DatabaseId): number {\n return (\n primitiveComparator(this.projectId, other.projectId) ||\n primitiveComparator(this.database, other.database)\n );\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { debugAssert } from './assert';\n\nexport interface Dict {\n [stringKey: string]: V;\n}\n\nexport function objectSize(obj: object): number {\n let count = 0;\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n count++;\n }\n }\n return count;\n}\n\nexport function forEach(\n obj: Dict,\n fn: (key: string, val: V) => void\n): void {\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n fn(key, obj[key]);\n }\n }\n}\n\nexport function isEmpty(obj: Dict): boolean {\n debugAssert(\n obj != null && typeof obj === 'object',\n 'isEmpty() expects object parameter.'\n );\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n return false;\n }\n }\n return true;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { forEach, isEmpty } from './obj';\n\ntype Entry = [K, V];\n\n/**\n * A map implementation that uses objects as keys. Objects must have an\n * associated equals function and must be immutable. Entries in the map are\n * stored together with the key being produced from the mapKeyFn. This map\n * automatically handles collisions of keys.\n */\nexport class ObjectMap {\n /**\n * The inner map for a key -> value pair. Due to the possibility of\n * collisions we keep a list of entries that we do a linear search through\n * to find an actual match. Note that collisions should be rare, so we still\n * expect near constant time lookups in practice.\n */\n private inner: {\n [canonicalId: string]: Array>;\n } = {};\n\n constructor(\n private mapKeyFn: (key: KeyType) => string,\n private equalsFn: (l: KeyType, r: KeyType) => boolean\n ) {}\n\n /** Get a value for this key, or undefined if it does not exist. */\n get(key: KeyType): ValueType | undefined {\n const id = this.mapKeyFn(key);\n const matches = this.inner[id];\n if (matches === undefined) {\n return undefined;\n }\n for (const [otherKey, value] of matches) {\n if (this.equalsFn(otherKey, key)) {\n return value;\n }\n }\n return undefined;\n }\n\n has(key: KeyType): boolean {\n return this.get(key) !== undefined;\n }\n\n /** Put this key and value in the map. */\n set(key: KeyType, value: ValueType): void {\n const id = this.mapKeyFn(key);\n const matches = this.inner[id];\n if (matches === undefined) {\n this.inner[id] = [[key, value]];\n return;\n }\n for (let i = 0; i < matches.length; i++) {\n if (this.equalsFn(matches[i][0], key)) {\n matches[i] = [key, value];\n return;\n }\n }\n matches.push([key, value]);\n }\n\n /**\n * Remove this key from the map. Returns a boolean if anything was deleted.\n */\n delete(key: KeyType): boolean {\n const id = this.mapKeyFn(key);\n const matches = this.inner[id];\n if (matches === undefined) {\n return false;\n }\n for (let i = 0; i < matches.length; i++) {\n if (this.equalsFn(matches[i][0], key)) {\n if (matches.length === 1) {\n delete this.inner[id];\n } else {\n matches.splice(i, 1);\n }\n return true;\n }\n }\n return false;\n }\n\n forEach(fn: (key: KeyType, val: ValueType) => void): void {\n forEach(this.inner, (_, entries) => {\n for (const [k, v] of entries) {\n fn(k, v);\n }\n });\n }\n\n isEmpty(): boolean {\n return isEmpty(this.inner);\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as firestore from '@firebase/firestore-types';\n\n/**\n * Error Codes describing the different ways Firestore can fail. These come\n * directly from GRPC.\n */\nexport type Code = firestore.FirestoreErrorCode;\n\nexport const Code = {\n // Causes are copied from:\n // https://github.com/grpc/grpc/blob/bceec94ea4fc5f0085d81235d8e1c06798dc341a/include/grpc%2B%2B/impl/codegen/status_code_enum.h\n /** Not an error; returned on success. */\n OK: 'ok' as Code,\n\n /** The operation was cancelled (typically by the caller). */\n CANCELLED: 'cancelled' as Code,\n\n /** Unknown error or an error from a different error domain. */\n UNKNOWN: 'unknown' as Code,\n\n /**\n * Client specified an invalid argument. Note that this differs from\n * FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are\n * problematic regardless of the state of the system (e.g., a malformed file\n * name).\n */\n INVALID_ARGUMENT: 'invalid-argument' as Code,\n\n /**\n * Deadline expired before operation could complete. For operations that\n * change the state of the system, this error may be returned even if the\n * operation has completed successfully. For example, a successful response\n * from a server could have been delayed long enough for the deadline to\n * expire.\n */\n DEADLINE_EXCEEDED: 'deadline-exceeded' as Code,\n\n /** Some requested entity (e.g., file or directory) was not found. */\n NOT_FOUND: 'not-found' as Code,\n\n /**\n * Some entity that we attempted to create (e.g., file or directory) already\n * exists.\n */\n ALREADY_EXISTS: 'already-exists' as Code,\n\n /**\n * The caller does not have permission to execute the specified operation.\n * PERMISSION_DENIED must not be used for rejections caused by exhausting\n * some resource (use RESOURCE_EXHAUSTED instead for those errors).\n * PERMISSION_DENIED must not be used if the caller can not be identified\n * (use UNAUTHENTICATED instead for those errors).\n */\n PERMISSION_DENIED: 'permission-denied' as Code,\n\n /**\n * The request does not have valid authentication credentials for the\n * operation.\n */\n UNAUTHENTICATED: 'unauthenticated' as Code,\n\n /**\n * Some resource has been exhausted, perhaps a per-user quota, or perhaps the\n * entire file system is out of space.\n */\n RESOURCE_EXHAUSTED: 'resource-exhausted' as Code,\n\n /**\n * Operation was rejected because the system is not in a state required for\n * the operation's execution. For example, directory to be deleted may be\n * non-empty, an rmdir operation is applied to a non-directory, etc.\n *\n * A litmus test that may help a service implementor in deciding\n * between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:\n * (a) Use UNAVAILABLE if the client can retry just the failing call.\n * (b) Use ABORTED if the client should retry at a higher-level\n * (e.g., restarting a read-modify-write sequence).\n * (c) Use FAILED_PRECONDITION if the client should not retry until\n * the system state has been explicitly fixed. E.g., if an \"rmdir\"\n * fails because the directory is non-empty, FAILED_PRECONDITION\n * should be returned since the client should not retry unless\n * they have first fixed up the directory by deleting files from it.\n * (d) Use FAILED_PRECONDITION if the client performs conditional\n * REST Get/Update/Delete on a resource and the resource on the\n * server does not match the condition. E.g., conflicting\n * read-modify-write on the same resource.\n */\n FAILED_PRECONDITION: 'failed-precondition' as Code,\n\n /**\n * The operation was aborted, typically due to a concurrency issue like\n * sequencer check failures, transaction aborts, etc.\n *\n * See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,\n * and UNAVAILABLE.\n */\n ABORTED: 'aborted' as Code,\n\n /**\n * Operation was attempted past the valid range. E.g., seeking or reading\n * past end of file.\n *\n * Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed\n * if the system state changes. For example, a 32-bit file system will\n * generate INVALID_ARGUMENT if asked to read at an offset that is not in the\n * range [0,2^32-1], but it will generate OUT_OF_RANGE if asked to read from\n * an offset past the current file size.\n *\n * There is a fair bit of overlap between FAILED_PRECONDITION and\n * OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error)\n * when it applies so that callers who are iterating through a space can\n * easily look for an OUT_OF_RANGE error to detect when they are done.\n */\n OUT_OF_RANGE: 'out-of-range' as Code,\n\n /** Operation is not implemented or not supported/enabled in this service. */\n UNIMPLEMENTED: 'unimplemented' as Code,\n\n /**\n * Internal errors. Means some invariants expected by underlying System has\n * been broken. If you see one of these errors, Something is very broken.\n */\n INTERNAL: 'internal' as Code,\n\n /**\n * The service is currently unavailable. This is a most likely a transient\n * condition and may be corrected by retrying with a backoff.\n *\n * See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,\n * and UNAVAILABLE.\n */\n UNAVAILABLE: 'unavailable' as Code,\n\n /** Unrecoverable data loss or corruption. */\n DATA_LOSS: 'data-loss' as Code\n};\n\n/**\n * An error class used for Firestore-generated errors. Ideally we should be\n * using FirebaseError, but integrating with it is overly arduous at the moment,\n * so we define our own compatible error class (with a `name` of 'FirebaseError'\n * and compatible `code` and `message` fields.)\n */\nexport class FirestoreError extends Error implements firestore.FirestoreError {\n name = 'FirebaseError';\n stack?: string;\n\n constructor(readonly code: Code, readonly message: string) {\n super(message);\n\n // HACK: We write a toString property directly because Error is not a real\n // class and so inheritance does not work correctly. We could alternatively\n // do the same \"back-door inheritance\" trick that FirebaseError does.\n this.toString = () => `${this.name}: [code=${this.code}]: ${this.message}`;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Code, FirestoreError } from '../util/error';\nimport { primitiveComparator } from '../util/misc';\n\n// The earlist date supported by Firestore timestamps (0001-01-01T00:00:00Z).\nconst MIN_SECONDS = -62135596800;\n\nexport class Timestamp {\n static now(): Timestamp {\n return Timestamp.fromMillis(Date.now());\n }\n\n static fromDate(date: Date): Timestamp {\n return Timestamp.fromMillis(date.getTime());\n }\n\n static fromMillis(milliseconds: number): Timestamp {\n const seconds = Math.floor(milliseconds / 1000);\n const nanos = (milliseconds - seconds * 1000) * 1e6;\n return new Timestamp(seconds, nanos);\n }\n\n constructor(readonly seconds: number, readonly nanoseconds: number) {\n if (nanoseconds < 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Timestamp nanoseconds out of range: ' + nanoseconds\n );\n }\n if (nanoseconds >= 1e9) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Timestamp nanoseconds out of range: ' + nanoseconds\n );\n }\n if (seconds < MIN_SECONDS) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Timestamp seconds out of range: ' + seconds\n );\n }\n // This will break in the year 10,000.\n if (seconds >= 253402300800) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Timestamp seconds out of range: ' + seconds\n );\n }\n }\n\n toDate(): Date {\n return new Date(this.toMillis());\n }\n\n toMillis(): number {\n return this.seconds * 1000 + this.nanoseconds / 1e6;\n }\n\n _compareTo(other: Timestamp): number {\n if (this.seconds === other.seconds) {\n return primitiveComparator(this.nanoseconds, other.nanoseconds);\n }\n return primitiveComparator(this.seconds, other.seconds);\n }\n\n isEqual(other: Timestamp): boolean {\n return (\n other.seconds === this.seconds && other.nanoseconds === this.nanoseconds\n );\n }\n\n toString(): string {\n return (\n 'Timestamp(seconds=' +\n this.seconds +\n ', nanoseconds=' +\n this.nanoseconds +\n ')'\n );\n }\n\n valueOf(): string {\n // This method returns a string of the form . where is\n // translated to have a non-negative value and both and are left-padded\n // with zeroes to be a consistent length. Strings with this format then have a lexiographical\n // ordering that matches the expected ordering. The translation is done to avoid\n // having a leading negative sign (i.e. a leading '-' character) in its string representation,\n // which would affect its lexiographical ordering.\n const adjustedSeconds = this.seconds - MIN_SECONDS;\n // Note: Up to 12 decimal digits are required to represent all valid 'seconds' values.\n const formattedSeconds = String(adjustedSeconds).padStart(12, '0');\n const formattedNanoseconds = String(this.nanoseconds).padStart(9, '0');\n return formattedSeconds + '.' + formattedNanoseconds;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Timestamp } from '../api/timestamp';\n\n/**\n * A version of a document in Firestore. This corresponds to the version\n * timestamp, such as update_time or read_time.\n */\nexport class SnapshotVersion {\n static fromTimestamp(value: Timestamp): SnapshotVersion {\n return new SnapshotVersion(value);\n }\n\n static min(): SnapshotVersion {\n return new SnapshotVersion(new Timestamp(0, 0));\n }\n\n private constructor(private timestamp: Timestamp) {}\n\n compareTo(other: SnapshotVersion): number {\n return this.timestamp._compareTo(other.timestamp);\n }\n\n isEqual(other: SnapshotVersion): boolean {\n return this.timestamp.isEqual(other.timestamp);\n }\n\n /** Returns a number representation of the version for use in spec tests. */\n toMicroseconds(): number {\n // Convert to microseconds.\n return this.timestamp.seconds * 1e6 + this.timestamp.nanoseconds / 1000;\n }\n\n toString(): string {\n return 'SnapshotVersion(' + this.timestamp.toString() + ')';\n }\n\n toTimestamp(): Timestamp {\n return this.timestamp;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { debugAssert, fail } from '../util/assert';\nimport { Code, FirestoreError } from '../util/error';\n\nexport const DOCUMENT_KEY_NAME = '__name__';\n\n/**\n * Path represents an ordered sequence of string segments.\n */\nabstract class BasePath> {\n private segments: string[];\n private offset: number;\n private len: number;\n\n constructor(segments: string[], offset?: number, length?: number) {\n if (offset === undefined) {\n offset = 0;\n } else if (offset > segments.length) {\n fail('offset ' + offset + ' out of range ' + segments.length);\n }\n\n if (length === undefined) {\n length = segments.length - offset;\n } else if (length > segments.length - offset) {\n fail('length ' + length + ' out of range ' + (segments.length - offset));\n }\n this.segments = segments;\n this.offset = offset;\n this.len = length;\n }\n\n /**\n * Abstract constructor method to construct an instance of B with the given\n * parameters.\n */\n protected abstract construct(\n segments: string[],\n offset?: number,\n length?: number\n ): B;\n\n /**\n * Returns a String representation.\n *\n * Implementing classes are required to provide deterministic implementations as\n * the String representation is used to obtain canonical Query IDs.\n */\n abstract toString(): string;\n\n get length(): number {\n return this.len;\n }\n\n isEqual(other: B): boolean {\n return BasePath.comparator(this, other) === 0;\n }\n\n child(nameOrPath: string | B): B {\n const segments = this.segments.slice(this.offset, this.limit());\n if (nameOrPath instanceof BasePath) {\n nameOrPath.forEach(segment => {\n segments.push(segment);\n });\n } else {\n segments.push(nameOrPath);\n }\n return this.construct(segments);\n }\n\n /** The index of one past the last segment of the path. */\n private limit(): number {\n return this.offset + this.length;\n }\n\n popFirst(size?: number): B {\n size = size === undefined ? 1 : size;\n debugAssert(\n this.length >= size,\n \"Can't call popFirst() with less segments\"\n );\n return this.construct(\n this.segments,\n this.offset + size,\n this.length - size\n );\n }\n\n popLast(): B {\n debugAssert(!this.isEmpty(), \"Can't call popLast() on empty path\");\n return this.construct(this.segments, this.offset, this.length - 1);\n }\n\n firstSegment(): string {\n debugAssert(!this.isEmpty(), \"Can't call firstSegment() on empty path\");\n return this.segments[this.offset];\n }\n\n lastSegment(): string {\n return this.get(this.length - 1);\n }\n\n get(index: number): string {\n debugAssert(index < this.length, 'Index out of range');\n return this.segments[this.offset + index];\n }\n\n isEmpty(): boolean {\n return this.length === 0;\n }\n\n isPrefixOf(other: this): boolean {\n if (other.length < this.length) {\n return false;\n }\n\n for (let i = 0; i < this.length; i++) {\n if (this.get(i) !== other.get(i)) {\n return false;\n }\n }\n\n return true;\n }\n\n isImmediateParentOf(potentialChild: this): boolean {\n if (this.length + 1 !== potentialChild.length) {\n return false;\n }\n\n for (let i = 0; i < this.length; i++) {\n if (this.get(i) !== potentialChild.get(i)) {\n return false;\n }\n }\n\n return true;\n }\n\n forEach(fn: (segment: string) => void): void {\n for (let i = this.offset, end = this.limit(); i < end; i++) {\n fn(this.segments[i]);\n }\n }\n\n toArray(): string[] {\n return this.segments.slice(this.offset, this.limit());\n }\n\n static comparator>(\n p1: BasePath,\n p2: BasePath\n ): number {\n const len = Math.min(p1.length, p2.length);\n for (let i = 0; i < len; i++) {\n const left = p1.get(i);\n const right = p2.get(i);\n if (left < right) {\n return -1;\n }\n if (left > right) {\n return 1;\n }\n }\n if (p1.length < p2.length) {\n return -1;\n }\n if (p1.length > p2.length) {\n return 1;\n }\n return 0;\n }\n}\n\n/**\n * A slash-separated path for navigating resources (documents and collections)\n * within Firestore.\n */\nexport class ResourcePath extends BasePath {\n protected construct(\n segments: string[],\n offset?: number,\n length?: number\n ): ResourcePath {\n return new ResourcePath(segments, offset, length);\n }\n\n canonicalString(): string {\n // NOTE: The client is ignorant of any path segments containing escape\n // sequences (e.g. __id123__) and just passes them through raw (they exist\n // for legacy reasons and should not be used frequently).\n\n return this.toArray().join('/');\n }\n\n toString(): string {\n return this.canonicalString();\n }\n\n /**\n * Creates a resource path from the given slash-delimited string.\n */\n static fromString(path: string): ResourcePath {\n // NOTE: The client is ignorant of any path segments containing escape\n // sequences (e.g. __id123__) and just passes them through raw (they exist\n // for legacy reasons and should not be used frequently).\n\n if (path.indexOf('//') >= 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid path (${path}). Paths must not contain // in them.`\n );\n }\n\n // We may still have an empty segment at the beginning or end if they had a\n // leading or trailing slash (which we allow).\n const segments = path.split('/').filter(segment => segment.length > 0);\n\n return new ResourcePath(segments);\n }\n\n static emptyPath(): ResourcePath {\n return new ResourcePath([]);\n }\n}\n\nconst identifierRegExp = /^[_a-zA-Z][_a-zA-Z0-9]*$/;\n\n/** A dot-separated path for navigating sub-objects within a document. */\nexport class FieldPath extends BasePath {\n protected construct(\n segments: string[],\n offset?: number,\n length?: number\n ): FieldPath {\n return new FieldPath(segments, offset, length);\n }\n\n /**\n * Returns true if the string could be used as a segment in a field path\n * without escaping.\n */\n private static isValidIdentifier(segment: string): boolean {\n return identifierRegExp.test(segment);\n }\n\n canonicalString(): string {\n return this.toArray()\n .map(str => {\n str = str.replace('\\\\', '\\\\\\\\').replace('`', '\\\\`');\n if (!FieldPath.isValidIdentifier(str)) {\n str = '`' + str + '`';\n }\n return str;\n })\n .join('.');\n }\n\n toString(): string {\n return this.canonicalString();\n }\n\n /**\n * Returns true if this field references the key of a document.\n */\n isKeyField(): boolean {\n return this.length === 1 && this.get(0) === DOCUMENT_KEY_NAME;\n }\n\n /**\n * The field designating the key of a document.\n */\n static keyField(): FieldPath {\n return new FieldPath([DOCUMENT_KEY_NAME]);\n }\n\n /**\n * Parses a field string from the given server-formatted string.\n *\n * - Splitting the empty string is not allowed (for now at least).\n * - Empty segments within the string (e.g. if there are two consecutive\n * separators) are not allowed.\n *\n * TODO(b/37244157): we should make this more strict. Right now, it allows\n * non-identifier path components, even if they aren't escaped.\n */\n static fromServerFormat(path: string): FieldPath {\n const segments: string[] = [];\n let current = '';\n let i = 0;\n\n const addCurrentSegment = (): void => {\n if (current.length === 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid field path (${path}). Paths must not be empty, begin ` +\n `with '.', end with '.', or contain '..'`\n );\n }\n segments.push(current);\n current = '';\n };\n\n let inBackticks = false;\n\n while (i < path.length) {\n const c = path[i];\n if (c === '\\\\') {\n if (i + 1 === path.length) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Path has trailing escape character: ' + path\n );\n }\n const next = path[i + 1];\n if (!(next === '\\\\' || next === '.' || next === '`')) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Path has invalid escape sequence: ' + path\n );\n }\n current += next;\n i += 2;\n } else if (c === '`') {\n inBackticks = !inBackticks;\n i++;\n } else if (c === '.' && !inBackticks) {\n addCurrentSegment();\n i++;\n } else {\n current += c;\n i++;\n }\n }\n addCurrentSegment();\n\n if (inBackticks) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Unterminated ` in path: ' + path\n );\n }\n\n return new FieldPath(segments);\n }\n\n static emptyPath(): FieldPath {\n return new FieldPath([]);\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { debugAssert } from '../util/assert';\n\nimport { ResourcePath } from './path';\n\nexport class DocumentKey {\n constructor(readonly path: ResourcePath) {\n debugAssert(\n DocumentKey.isDocumentKey(path),\n 'Invalid DocumentKey with an odd number of segments: ' +\n path.toArray().join('/')\n );\n }\n\n static fromName(name: string): DocumentKey {\n return new DocumentKey(ResourcePath.fromString(name).popFirst(5));\n }\n\n /** Returns true if the document is in the specified collectionId. */\n hasCollectionId(collectionId: string): boolean {\n return (\n this.path.length >= 2 &&\n this.path.get(this.path.length - 2) === collectionId\n );\n }\n\n isEqual(other: DocumentKey | null): boolean {\n return (\n other !== null && ResourcePath.comparator(this.path, other.path) === 0\n );\n }\n\n toString(): string {\n return this.path.toString();\n }\n\n static comparator(k1: DocumentKey, k2: DocumentKey): number {\n return ResourcePath.comparator(k1.path, k2.path);\n }\n\n static isDocumentKey(path: ResourcePath): boolean {\n return path.length % 2 === 0;\n }\n\n /**\n * Creates and returns a new document key with the given segments.\n *\n * @param segments The segments of the path to the document\n * @return A new instance of DocumentKey\n */\n static fromSegments(segments: string[]): DocumentKey {\n return new DocumentKey(new ResourcePath(segments.slice()));\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// An Object whose keys and values are strings.\nexport interface StringMap {\n [key: string]: string;\n}\n\n/**\n * Returns whether a variable is either undefined or null.\n */\nexport function isNullOrUndefined(value: unknown): value is null | undefined {\n return value === null || value === undefined;\n}\n\n/** Returns whether the value represents -0. */\nexport function isNegativeZero(value: number): boolean {\n // Detect if the value is -0.0. Based on polyfill from\n // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is\n return value === -0 && 1 / value === 1 / -0;\n}\n\n/**\n * Returns whether a value is an integer and in the safe integer range\n * @param value The value to test for being an integer and in the safe range\n */\nexport function isSafeInteger(value: unknown): boolean {\n return (\n typeof value === 'number' &&\n Number.isInteger(value) &&\n !isNegativeZero(value) &&\n value <= Number.MAX_SAFE_INTEGER &&\n value >= Number.MIN_SAFE_INTEGER\n );\n}\n\n/** The subset of the browser's Window interface used by the SDK. */\nexport interface WindowLike {\n readonly localStorage: Storage;\n readonly indexedDB: IDBFactory | null;\n addEventListener(type: string, listener: EventListener): void;\n removeEventListener(type: string, listener: EventListener): void;\n}\n\n/** The subset of the browser's Document interface used by the SDK. */\nexport interface DocumentLike {\n readonly visibilityState: VisibilityState;\n addEventListener(type: string, listener: EventListener): void;\n removeEventListener(type: string, listener: EventListener): void;\n}\n","/**\n * @license\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DocumentKey } from '../model/document_key';\nimport { ResourcePath } from '../model/path';\nimport { isNullOrUndefined } from '../util/types';\nimport {\n Bound,\n boundEquals,\n canonifyBound,\n canonifyFilter,\n filterEquals,\n stringifyFilter,\n OrderBy,\n orderByEquals,\n stringifyOrderBy,\n canonifyOrderBy,\n Filter\n} from './query';\nimport { debugCast } from '../util/assert';\n\n/**\n * A Target represents the WatchTarget representation of a Query, which is used\n * by the LocalStore and the RemoteStore to keep track of and to execute\n * backend queries. While a Query can represent multiple Targets, each Targets\n * maps to a single WatchTarget in RemoteStore and a single TargetData entry\n * in persistence.\n */\nexport interface Target {\n readonly path: ResourcePath;\n readonly collectionGroup: string | null;\n readonly orderBy: OrderBy[];\n readonly filters: Filter[];\n readonly limit: number | null;\n readonly startAt: Bound | null;\n readonly endAt: Bound | null;\n}\n\n// Visible for testing\nexport class TargetImpl implements Target {\n memoizedCanonicalId: string | null = null;\n constructor(\n readonly path: ResourcePath,\n readonly collectionGroup: string | null = null,\n readonly orderBy: OrderBy[] = [],\n readonly filters: Filter[] = [],\n readonly limit: number | null = null,\n readonly startAt: Bound | null = null,\n readonly endAt: Bound | null = null\n ) {}\n}\n\n/**\n * Initializes a Target with a path and optional additional query constraints.\n * Path must currently be empty if this is a collection group query.\n *\n * NOTE: you should always construct `Target` from `Query.toTarget` instead of\n * using this factory method, because `Query` provides an implicit `orderBy`\n * property.\n */\nexport function newTarget(\n path: ResourcePath,\n collectionGroup: string | null = null,\n orderBy: OrderBy[] = [],\n filters: Filter[] = [],\n limit: number | null = null,\n startAt: Bound | null = null,\n endAt: Bound | null = null\n): Target {\n return new TargetImpl(\n path,\n collectionGroup,\n orderBy,\n filters,\n limit,\n startAt,\n endAt\n );\n}\n\nexport function canonifyTarget(target: Target): string {\n const targetImpl = debugCast(target, TargetImpl);\n\n if (targetImpl.memoizedCanonicalId === null) {\n let canonicalId = targetImpl.path.canonicalString();\n if (targetImpl.collectionGroup !== null) {\n canonicalId += '|cg:' + targetImpl.collectionGroup;\n }\n canonicalId += '|f:';\n canonicalId += targetImpl.filters.map(f => canonifyFilter(f)).join(',');\n canonicalId += '|ob:';\n canonicalId += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');\n\n if (!isNullOrUndefined(targetImpl.limit)) {\n canonicalId += '|l:';\n canonicalId += targetImpl.limit!;\n }\n if (targetImpl.startAt) {\n canonicalId += '|lb:';\n canonicalId += canonifyBound(targetImpl.startAt);\n }\n if (targetImpl.endAt) {\n canonicalId += '|ub:';\n canonicalId += canonifyBound(targetImpl.endAt);\n }\n targetImpl.memoizedCanonicalId = canonicalId;\n }\n return targetImpl.memoizedCanonicalId;\n}\n\nexport function stringifyTarget(target: Target): string {\n let str = target.path.canonicalString();\n if (target.collectionGroup !== null) {\n str += ' collectionGroup=' + target.collectionGroup;\n }\n if (target.filters.length > 0) {\n str += `, filters: [${target.filters\n .map(f => stringifyFilter(f))\n .join(', ')}]`;\n }\n if (!isNullOrUndefined(target.limit)) {\n str += ', limit: ' + target.limit;\n }\n if (target.orderBy.length > 0) {\n str += `, orderBy: [${target.orderBy\n .map(o => stringifyOrderBy(o))\n .join(', ')}]`;\n }\n if (target.startAt) {\n str += ', startAt: ' + canonifyBound(target.startAt);\n }\n if (target.endAt) {\n str += ', endAt: ' + canonifyBound(target.endAt);\n }\n return `Target(${str})`;\n}\n\nexport function targetEquals(left: Target, right: Target): boolean {\n if (left.limit !== right.limit) {\n return false;\n }\n\n if (left.orderBy.length !== right.orderBy.length) {\n return false;\n }\n\n for (let i = 0; i < left.orderBy.length; i++) {\n if (!orderByEquals(left.orderBy[i], right.orderBy[i])) {\n return false;\n }\n }\n\n if (left.filters.length !== right.filters.length) {\n return false;\n }\n\n for (let i = 0; i < left.filters.length; i++) {\n if (!filterEquals(left.filters[i], right.filters[i])) {\n return false;\n }\n }\n\n if (left.collectionGroup !== right.collectionGroup) {\n return false;\n }\n\n if (!left.path.isEqual(right.path)) {\n return false;\n }\n\n if (!boundEquals(left.startAt, right.startAt)) {\n return false;\n }\n\n return boundEquals(left.endAt, right.endAt);\n}\n\nexport function isDocumentTarget(target: Target): boolean {\n return (\n DocumentKey.isDocumentKey(target.path) &&\n target.collectionGroup === null &&\n target.filters.length === 0\n );\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as api from '../protos/firestore_proto_api';\n\nimport { compareDocumentsByField, Document } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport {\n canonicalId,\n valueCompare,\n arrayValueContains,\n valueEquals,\n isArray,\n isNanValue,\n isNullValue,\n isReferenceValue,\n typeOrder\n} from '../model/values';\nimport { FieldPath, ResourcePath } from '../model/path';\nimport { debugAssert, fail } from '../util/assert';\nimport { Code, FirestoreError } from '../util/error';\nimport { isNullOrUndefined } from '../util/types';\nimport {\n canonifyTarget,\n isDocumentTarget,\n newTarget,\n stringifyTarget,\n Target,\n targetEquals\n} from './target';\n\nexport const enum LimitType {\n First = 'F',\n Last = 'L'\n}\n\n/**\n * Query encapsulates all the query attributes we support in the SDK. It can\n * be run against the LocalStore, as well as be converted to a `Target` to\n * query the RemoteStore results.\n */\nexport class Query {\n static atPath(path: ResourcePath): Query {\n return new Query(path);\n }\n\n private memoizedOrderBy: OrderBy[] | null = null;\n\n // The corresponding `Target` of this `Query` instance.\n private memoizedTarget: Target | null = null;\n\n /**\n * Initializes a Query with a path and optional additional query constraints.\n * Path must currently be empty if this is a collection group query.\n */\n constructor(\n readonly path: ResourcePath,\n readonly collectionGroup: string | null = null,\n readonly explicitOrderBy: OrderBy[] = [],\n readonly filters: Filter[] = [],\n readonly limit: number | null = null,\n readonly limitType: LimitType = LimitType.First,\n readonly startAt: Bound | null = null,\n readonly endAt: Bound | null = null\n ) {\n if (this.startAt) {\n this.assertValidBound(this.startAt);\n }\n if (this.endAt) {\n this.assertValidBound(this.endAt);\n }\n }\n\n get orderBy(): OrderBy[] {\n if (this.memoizedOrderBy === null) {\n this.memoizedOrderBy = [];\n\n const inequalityField = this.getInequalityFilterField();\n const firstOrderByField = this.getFirstOrderByField();\n if (inequalityField !== null && firstOrderByField === null) {\n // In order to implicitly add key ordering, we must also add the\n // inequality filter field for it to be a valid query.\n // Note that the default inequality field and key ordering is ascending.\n if (!inequalityField.isKeyField()) {\n this.memoizedOrderBy.push(new OrderBy(inequalityField));\n }\n this.memoizedOrderBy.push(\n new OrderBy(FieldPath.keyField(), Direction.ASCENDING)\n );\n } else {\n debugAssert(\n inequalityField === null ||\n (firstOrderByField !== null &&\n inequalityField.isEqual(firstOrderByField)),\n 'First orderBy should match inequality field.'\n );\n let foundKeyOrdering = false;\n for (const orderBy of this.explicitOrderBy) {\n this.memoizedOrderBy.push(orderBy);\n if (orderBy.field.isKeyField()) {\n foundKeyOrdering = true;\n }\n }\n if (!foundKeyOrdering) {\n // The order of the implicit key ordering always matches the last\n // explicit order by\n const lastDirection =\n this.explicitOrderBy.length > 0\n ? this.explicitOrderBy[this.explicitOrderBy.length - 1].dir\n : Direction.ASCENDING;\n this.memoizedOrderBy.push(\n new OrderBy(FieldPath.keyField(), lastDirection)\n );\n }\n }\n }\n return this.memoizedOrderBy;\n }\n\n addFilter(filter: Filter): Query {\n debugAssert(\n this.getInequalityFilterField() == null ||\n !(filter instanceof FieldFilter) ||\n !filter.isInequality() ||\n filter.field.isEqual(this.getInequalityFilterField()!),\n 'Query must only have one inequality field.'\n );\n\n debugAssert(\n !this.isDocumentQuery(),\n 'No filtering allowed for document query'\n );\n\n const newFilters = this.filters.concat([filter]);\n return new Query(\n this.path,\n this.collectionGroup,\n this.explicitOrderBy.slice(),\n newFilters,\n this.limit,\n this.limitType,\n this.startAt,\n this.endAt\n );\n }\n\n addOrderBy(orderBy: OrderBy): Query {\n debugAssert(\n !this.startAt && !this.endAt,\n 'Bounds must be set after orderBy'\n );\n // TODO(dimond): validate that orderBy does not list the same key twice.\n const newOrderBy = this.explicitOrderBy.concat([orderBy]);\n return new Query(\n this.path,\n this.collectionGroup,\n newOrderBy,\n this.filters.slice(),\n this.limit,\n this.limitType,\n this.startAt,\n this.endAt\n );\n }\n\n withLimitToFirst(limit: number | null): Query {\n return new Query(\n this.path,\n this.collectionGroup,\n this.explicitOrderBy.slice(),\n this.filters.slice(),\n limit,\n LimitType.First,\n this.startAt,\n this.endAt\n );\n }\n\n withLimitToLast(limit: number | null): Query {\n return new Query(\n this.path,\n this.collectionGroup,\n this.explicitOrderBy.slice(),\n this.filters.slice(),\n limit,\n LimitType.Last,\n this.startAt,\n this.endAt\n );\n }\n\n withStartAt(bound: Bound): Query {\n return new Query(\n this.path,\n this.collectionGroup,\n this.explicitOrderBy.slice(),\n this.filters.slice(),\n this.limit,\n this.limitType,\n bound,\n this.endAt\n );\n }\n\n withEndAt(bound: Bound): Query {\n return new Query(\n this.path,\n this.collectionGroup,\n this.explicitOrderBy.slice(),\n this.filters.slice(),\n this.limit,\n this.limitType,\n this.startAt,\n bound\n );\n }\n\n /**\n * Helper to convert a collection group query into a collection query at a\n * specific path. This is used when executing collection group queries, since\n * we have to split the query into a set of collection queries at multiple\n * paths.\n */\n asCollectionQueryAtPath(path: ResourcePath): Query {\n return new Query(\n path,\n /*collectionGroup=*/ null,\n this.explicitOrderBy.slice(),\n this.filters.slice(),\n this.limit,\n this.limitType,\n this.startAt,\n this.endAt\n );\n }\n\n /**\n * Returns true if this query does not specify any query constraints that\n * could remove results.\n */\n matchesAllDocuments(): boolean {\n return (\n this.filters.length === 0 &&\n this.limit === null &&\n this.startAt == null &&\n this.endAt == null &&\n (this.explicitOrderBy.length === 0 ||\n (this.explicitOrderBy.length === 1 &&\n this.explicitOrderBy[0].field.isKeyField()))\n );\n }\n\n hasLimitToFirst(): boolean {\n return !isNullOrUndefined(this.limit) && this.limitType === LimitType.First;\n }\n\n hasLimitToLast(): boolean {\n return !isNullOrUndefined(this.limit) && this.limitType === LimitType.Last;\n }\n\n getFirstOrderByField(): FieldPath | null {\n return this.explicitOrderBy.length > 0\n ? this.explicitOrderBy[0].field\n : null;\n }\n\n getInequalityFilterField(): FieldPath | null {\n for (const filter of this.filters) {\n if (filter instanceof FieldFilter && filter.isInequality()) {\n return filter.field;\n }\n }\n return null;\n }\n\n // Checks if any of the provided Operators are included in the query and\n // returns the first one that is, or null if none are.\n findFilterOperator(operators: Operator[]): Operator | null {\n for (const filter of this.filters) {\n if (filter instanceof FieldFilter) {\n if (operators.indexOf(filter.op) >= 0) {\n return filter.op;\n }\n }\n }\n return null;\n }\n\n isDocumentQuery(): boolean {\n return isDocumentTarget(this.toTarget());\n }\n\n isCollectionGroupQuery(): boolean {\n return this.collectionGroup !== null;\n }\n\n /**\n * Converts this `Query` instance to it's corresponding `Target`\n * representation.\n */\n toTarget(): Target {\n if (!this.memoizedTarget) {\n if (this.limitType === LimitType.First) {\n this.memoizedTarget = newTarget(\n this.path,\n this.collectionGroup,\n this.orderBy,\n this.filters,\n this.limit,\n this.startAt,\n this.endAt\n );\n } else {\n // Flip the orderBy directions since we want the last results\n const orderBys = [] as OrderBy[];\n for (const orderBy of this.orderBy) {\n const dir =\n orderBy.dir === Direction.DESCENDING\n ? Direction.ASCENDING\n : Direction.DESCENDING;\n orderBys.push(new OrderBy(orderBy.field, dir));\n }\n\n // We need to swap the cursors to match the now-flipped query ordering.\n const startAt = this.endAt\n ? new Bound(this.endAt.position, !this.endAt.before)\n : null;\n const endAt = this.startAt\n ? new Bound(this.startAt.position, !this.startAt.before)\n : null;\n\n // Now return as a LimitType.First query.\n this.memoizedTarget = newTarget(\n this.path,\n this.collectionGroup,\n orderBys,\n this.filters,\n this.limit,\n startAt,\n endAt\n );\n }\n }\n return this.memoizedTarget!;\n }\n\n private assertValidBound(bound: Bound): void {\n debugAssert(\n bound.position.length <= this.orderBy.length,\n 'Bound is longer than orderBy'\n );\n }\n}\n\nexport function queryEquals(left: Query, right: Query): boolean {\n return (\n targetEquals(left.toTarget(), right.toTarget()) &&\n left.limitType === right.limitType\n );\n}\n\n// TODO(b/29183165): This is used to get a unique string from a query to, for\n// example, use as a dictionary key, but the implementation is subject to\n// collisions. Make it collision-free.\nexport function canonifyQuery(query: Query): string {\n return `${canonifyTarget(query.toTarget())}|lt:${query.limitType}`;\n}\n\nexport function stringifyQuery(query: Query): string {\n return `Query(target=${stringifyTarget(query.toTarget())}; limitType=${\n query.limitType\n })`;\n}\n\n/** Returns whether `doc` matches the constraints of `query`. */\nexport function queryMatches(query: Query, doc: Document): boolean {\n return (\n queryMatchesPathAndCollectionGroup(query, doc) &&\n queryMatchesOrderBy(query, doc) &&\n queryMatchesFilters(query, doc) &&\n queryMatchesBounds(query, doc)\n );\n}\n\nfunction queryMatchesPathAndCollectionGroup(\n query: Query,\n doc: Document\n): boolean {\n const docPath = doc.key.path;\n if (query.collectionGroup !== null) {\n // NOTE: this.path is currently always empty since we don't expose Collection\n // Group queries rooted at a document path yet.\n return (\n doc.key.hasCollectionId(query.collectionGroup) &&\n query.path.isPrefixOf(docPath)\n );\n } else if (DocumentKey.isDocumentKey(query.path)) {\n // exact match for document queries\n return query.path.isEqual(docPath);\n } else {\n // shallow ancestor queries by default\n return query.path.isImmediateParentOf(docPath);\n }\n}\n\n/**\n * A document must have a value for every ordering clause in order to show up\n * in the results.\n */\nfunction queryMatchesOrderBy(query: Query, doc: Document): boolean {\n for (const orderBy of query.explicitOrderBy) {\n // order by key always matches\n if (!orderBy.field.isKeyField() && doc.field(orderBy.field) === null) {\n return false;\n }\n }\n return true;\n}\n\nfunction queryMatchesFilters(query: Query, doc: Document): boolean {\n for (const filter of query.filters) {\n if (!filter.matches(doc)) {\n return false;\n }\n }\n return true;\n}\n\n/** Makes sure a document is within the bounds, if provided. */\nfunction queryMatchesBounds(query: Query, doc: Document): boolean {\n if (\n query.startAt &&\n !sortsBeforeDocument(query.startAt, query.orderBy, doc)\n ) {\n return false;\n }\n if (query.endAt && sortsBeforeDocument(query.endAt, query.orderBy, doc)) {\n return false;\n }\n return true;\n}\n\n/**\n * Returns a new comparator function that can be used to compare two documents\n * based on the Query's ordering constraint.\n */\nexport function newQueryComparator(\n query: Query\n): (d1: Document, d2: Document) => number {\n return (d1: Document, d2: Document): number => {\n let comparedOnKeyField = false;\n for (const orderBy of query.orderBy) {\n const comp = compareDocs(orderBy, d1, d2);\n if (comp !== 0) {\n return comp;\n }\n comparedOnKeyField = comparedOnKeyField || orderBy.field.isKeyField();\n }\n // Assert that we actually compared by key\n debugAssert(\n comparedOnKeyField,\n \"orderBy used that doesn't compare on key field\"\n );\n return 0;\n };\n}\n\nexport abstract class Filter {\n abstract matches(doc: Document): boolean;\n}\n\nexport const enum Operator {\n LESS_THAN = '<',\n LESS_THAN_OR_EQUAL = '<=',\n EQUAL = '==',\n GREATER_THAN = '>',\n GREATER_THAN_OR_EQUAL = '>=',\n ARRAY_CONTAINS = 'array-contains',\n IN = 'in',\n ARRAY_CONTAINS_ANY = 'array-contains-any'\n}\n\nexport class FieldFilter extends Filter {\n protected constructor(\n public field: FieldPath,\n public op: Operator,\n public value: api.Value\n ) {\n super();\n }\n\n /**\n * Creates a filter based on the provided arguments.\n */\n static create(field: FieldPath, op: Operator, value: api.Value): FieldFilter {\n if (field.isKeyField()) {\n if (op === Operator.IN) {\n debugAssert(\n isArray(value),\n 'Comparing on key with IN, but filter value not an ArrayValue'\n );\n debugAssert(\n (value.arrayValue.values || []).every(elem => isReferenceValue(elem)),\n 'Comparing on key with IN, but an array value was not a RefValue'\n );\n return new KeyFieldInFilter(field, value);\n } else {\n debugAssert(\n isReferenceValue(value),\n 'Comparing on key, but filter value not a RefValue'\n );\n debugAssert(\n op !== Operator.ARRAY_CONTAINS && op !== Operator.ARRAY_CONTAINS_ANY,\n `'${op.toString()}' queries don't make sense on document keys.`\n );\n return new KeyFieldFilter(field, op, value);\n }\n } else if (isNullValue(value)) {\n if (op !== Operator.EQUAL) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Invalid query. Null supports only equality comparisons.'\n );\n }\n return new FieldFilter(field, op, value);\n } else if (isNanValue(value)) {\n if (op !== Operator.EQUAL) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Invalid query. NaN supports only equality comparisons.'\n );\n }\n return new FieldFilter(field, op, value);\n } else if (op === Operator.ARRAY_CONTAINS) {\n return new ArrayContainsFilter(field, value);\n } else if (op === Operator.IN) {\n debugAssert(\n isArray(value),\n 'IN filter has invalid value: ' + value.toString()\n );\n return new InFilter(field, value);\n } else if (op === Operator.ARRAY_CONTAINS_ANY) {\n debugAssert(\n isArray(value),\n 'ARRAY_CONTAINS_ANY filter has invalid value: ' + value.toString()\n );\n return new ArrayContainsAnyFilter(field, value);\n } else {\n return new FieldFilter(field, op, value);\n }\n }\n\n matches(doc: Document): boolean {\n const other = doc.field(this.field);\n\n // Only compare types with matching backend order (such as double and int).\n return (\n other !== null &&\n typeOrder(this.value) === typeOrder(other) &&\n this.matchesComparison(valueCompare(other, this.value))\n );\n }\n\n protected matchesComparison(comparison: number): boolean {\n switch (this.op) {\n case Operator.LESS_THAN:\n return comparison < 0;\n case Operator.LESS_THAN_OR_EQUAL:\n return comparison <= 0;\n case Operator.EQUAL:\n return comparison === 0;\n case Operator.GREATER_THAN:\n return comparison > 0;\n case Operator.GREATER_THAN_OR_EQUAL:\n return comparison >= 0;\n default:\n return fail('Unknown FieldFilter operator: ' + this.op);\n }\n }\n\n isInequality(): boolean {\n return (\n [\n Operator.LESS_THAN,\n Operator.LESS_THAN_OR_EQUAL,\n Operator.GREATER_THAN,\n Operator.GREATER_THAN_OR_EQUAL\n ].indexOf(this.op) >= 0\n );\n }\n}\n\nexport function canonifyFilter(filter: Filter): string {\n debugAssert(\n filter instanceof FieldFilter,\n 'canonifyFilter() only supports FieldFilters'\n );\n // TODO(b/29183165): Technically, this won't be unique if two values have\n // the same description, such as the int 3 and the string \"3\". So we should\n // add the types in here somehow, too.\n return (\n filter.field.canonicalString() +\n filter.op.toString() +\n canonicalId(filter.value)\n );\n}\n\nexport function filterEquals(f1: Filter, f2: Filter): boolean {\n return (\n f1 instanceof FieldFilter &&\n f2 instanceof FieldFilter &&\n f1.op === f2.op &&\n f1.field.isEqual(f2.field) &&\n valueEquals(f1.value, f2.value)\n );\n}\n\n/** Returns a debug description for `filter`. */\nexport function stringifyFilter(filter: Filter): string {\n debugAssert(\n filter instanceof FieldFilter,\n 'stringifyFilter() only supports FieldFilters'\n );\n return `${filter.field.canonicalString()} ${filter.op} ${canonicalId(\n filter.value\n )}`;\n}\n\n/** Filter that matches on key fields (i.e. '__name__'). */\nexport class KeyFieldFilter extends FieldFilter {\n private readonly key: DocumentKey;\n\n constructor(field: FieldPath, op: Operator, value: api.Value) {\n super(field, op, value);\n debugAssert(\n isReferenceValue(value),\n 'KeyFieldFilter expects a ReferenceValue'\n );\n this.key = DocumentKey.fromName(value.referenceValue);\n }\n\n matches(doc: Document): boolean {\n const comparison = DocumentKey.comparator(doc.key, this.key);\n return this.matchesComparison(comparison);\n }\n}\n\n/** Filter that matches on key fields within an array. */\nexport class KeyFieldInFilter extends FieldFilter {\n private readonly keys: DocumentKey[];\n\n constructor(field: FieldPath, value: api.Value) {\n super(field, Operator.IN, value);\n debugAssert(isArray(value), 'KeyFieldInFilter expects an ArrayValue');\n this.keys = (value.arrayValue.values || []).map(v => {\n debugAssert(\n isReferenceValue(v),\n 'Comparing on key with IN, but an array value was not a ReferenceValue'\n );\n return DocumentKey.fromName(v.referenceValue);\n });\n }\n\n matches(doc: Document): boolean {\n return this.keys.some(key => key.isEqual(doc.key));\n }\n}\n\n/** A Filter that implements the array-contains operator. */\nexport class ArrayContainsFilter extends FieldFilter {\n constructor(field: FieldPath, value: api.Value) {\n super(field, Operator.ARRAY_CONTAINS, value);\n }\n\n matches(doc: Document): boolean {\n const other = doc.field(this.field);\n return isArray(other) && arrayValueContains(other.arrayValue, this.value);\n }\n}\n\n/** A Filter that implements the IN operator. */\nexport class InFilter extends FieldFilter {\n constructor(field: FieldPath, value: api.Value) {\n super(field, Operator.IN, value);\n debugAssert(isArray(value), 'InFilter expects an ArrayValue');\n }\n\n matches(doc: Document): boolean {\n const other = doc.field(this.field);\n return other !== null && arrayValueContains(this.value.arrayValue!, other);\n }\n}\n\n/** A Filter that implements the array-contains-any operator. */\nexport class ArrayContainsAnyFilter extends FieldFilter {\n constructor(field: FieldPath, value: api.Value) {\n super(field, Operator.ARRAY_CONTAINS_ANY, value);\n debugAssert(isArray(value), 'ArrayContainsAnyFilter expects an ArrayValue');\n }\n\n matches(doc: Document): boolean {\n const other = doc.field(this.field);\n if (!isArray(other) || !other.arrayValue.values) {\n return false;\n }\n return other.arrayValue.values.some(val =>\n arrayValueContains(this.value.arrayValue!, val)\n );\n }\n}\n\n/**\n * The direction of sorting in an order by.\n */\nexport const enum Direction {\n ASCENDING = 'asc',\n DESCENDING = 'desc'\n}\n\n/**\n * Represents a bound of a query.\n *\n * The bound is specified with the given components representing a position and\n * whether it's just before or just after the position (relative to whatever the\n * query order is).\n *\n * The position represents a logical index position for a query. It's a prefix\n * of values for the (potentially implicit) order by clauses of a query.\n *\n * Bound provides a function to determine whether a document comes before or\n * after a bound. This is influenced by whether the position is just before or\n * just after the provided values.\n */\nexport class Bound {\n constructor(readonly position: api.Value[], readonly before: boolean) {}\n}\n\nexport function canonifyBound(bound: Bound): string {\n // TODO(b/29183165): Make this collision robust.\n return `${bound.before ? 'b' : 'a'}:${bound.position\n .map(p => canonicalId(p))\n .join(',')}`;\n}\n\n/**\n * Returns true if a document sorts before a bound using the provided sort\n * order.\n */\nexport function sortsBeforeDocument(\n bound: Bound,\n orderBy: OrderBy[],\n doc: Document\n): boolean {\n debugAssert(\n bound.position.length <= orderBy.length,\n \"Bound has more components than query's orderBy\"\n );\n let comparison = 0;\n for (let i = 0; i < bound.position.length; i++) {\n const orderByComponent = orderBy[i];\n const component = bound.position[i];\n if (orderByComponent.field.isKeyField()) {\n debugAssert(\n isReferenceValue(component),\n 'Bound has a non-key value where the key path is being used.'\n );\n comparison = DocumentKey.comparator(\n DocumentKey.fromName(component.referenceValue),\n doc.key\n );\n } else {\n const docValue = doc.field(orderByComponent.field);\n debugAssert(\n docValue !== null,\n 'Field should exist since document matched the orderBy already.'\n );\n comparison = valueCompare(component, docValue);\n }\n if (orderByComponent.dir === Direction.DESCENDING) {\n comparison = comparison * -1;\n }\n if (comparison !== 0) {\n break;\n }\n }\n return bound.before ? comparison <= 0 : comparison < 0;\n}\n\nexport function boundEquals(left: Bound | null, right: Bound | null): boolean {\n if (left === null) {\n return right === null;\n } else if (right === null) {\n return false;\n }\n\n if (\n left.before !== right.before ||\n left.position.length !== right.position.length\n ) {\n return false;\n }\n for (let i = 0; i < left.position.length; i++) {\n const leftPosition = left.position[i];\n const rightPosition = right.position[i];\n if (!valueEquals(leftPosition, rightPosition)) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * An ordering on a field, in some Direction. Direction defaults to ASCENDING.\n */\nexport class OrderBy {\n constructor(\n readonly field: FieldPath,\n readonly dir: Direction = Direction.ASCENDING\n ) {}\n}\n\nexport function compareDocs(\n orderBy: OrderBy,\n d1: Document,\n d2: Document\n): number {\n const comparison = orderBy.field.isKeyField()\n ? DocumentKey.comparator(d1.key, d2.key)\n : compareDocumentsByField(orderBy.field, d1, d2);\n switch (orderBy.dir) {\n case Direction.ASCENDING:\n return comparison;\n case Direction.DESCENDING:\n return -1 * comparison;\n default:\n return fail('Unknown direction: ' + orderBy.dir);\n }\n}\n\nexport function canonifyOrderBy(orderBy: OrderBy): string {\n // TODO(b/29183165): Make this collision robust.\n return orderBy.field.canonicalString() + orderBy.dir;\n}\n\nexport function stringifyOrderBy(orderBy: OrderBy): string {\n return `${orderBy.field.canonicalString()} (${orderBy.dir})`;\n}\n\nexport function orderByEquals(left: OrderBy, right: OrderBy): boolean {\n return left.dir === right.dir && left.field.isEqual(right.field);\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { decodeBase64, encodeBase64 } from '../platform/base64';\nimport { primitiveComparator } from './misc';\n\n/**\n * Immutable class that represents a \"proto\" byte string.\n *\n * Proto byte strings can either be Base64-encoded strings or Uint8Arrays when\n * sent on the wire. This class abstracts away this differentiation by holding\n * the proto byte string in a common class that must be converted into a string\n * before being sent as a proto.\n */\nexport class ByteString {\n static readonly EMPTY_BYTE_STRING = new ByteString('');\n\n private constructor(private readonly binaryString: string) {}\n\n static fromBase64String(base64: string): ByteString {\n const binaryString = decodeBase64(base64);\n return new ByteString(binaryString);\n }\n\n static fromUint8Array(array: Uint8Array): ByteString {\n const binaryString = binaryStringFromUint8Array(array);\n return new ByteString(binaryString);\n }\n\n toBase64(): string {\n return encodeBase64(this.binaryString);\n }\n\n toUint8Array(): Uint8Array {\n return uint8ArrayFromBinaryString(this.binaryString);\n }\n\n approximateByteSize(): number {\n return this.binaryString.length * 2;\n }\n\n compareTo(other: ByteString): number {\n return primitiveComparator(this.binaryString, other.binaryString);\n }\n\n isEqual(other: ByteString): boolean {\n return this.binaryString === other.binaryString;\n }\n}\n\n/**\n * Helper function to convert an Uint8array to a binary string.\n */\nexport function binaryStringFromUint8Array(array: Uint8Array): string {\n let binaryString = '';\n for (let i = 0; i < array.length; ++i) {\n binaryString += String.fromCharCode(array[i]);\n }\n return binaryString;\n}\n\n/**\n * Helper function to convert a binary string to an Uint8Array.\n */\nexport function uint8ArrayFromBinaryString(binaryString: string): Uint8Array {\n const buffer = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n buffer[i] = binaryString.charCodeAt(i);\n }\n return buffer;\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/** Converts a Base64 encoded string to a binary string. */\nexport function decodeBase64(encoded: string): string {\n return atob(encoded);\n}\n\n/** Converts a binary string to a Base64 encoded string. */\nexport function encodeBase64(raw: string): string {\n return btoa(raw);\n}\n\n/** True if and only if the Base64 conversion functions are available. */\nexport function isBase64Available(): boolean {\n return typeof atob !== 'undefined';\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { fail } from '../util/assert';\nimport { Code } from '../util/error';\nimport { logError } from '../util/log';\n\n/**\n * Error Codes describing the different ways GRPC can fail. These are copied\n * directly from GRPC's sources here:\n *\n * https://github.com/grpc/grpc/blob/bceec94ea4fc5f0085d81235d8e1c06798dc341a/include/grpc%2B%2B/impl/codegen/status_code_enum.h\n *\n * Important! The names of these identifiers matter because the string forms\n * are used for reverse lookups from the webchannel stream. Do NOT change the\n * names of these identifiers or change this into a const enum.\n */\nenum RpcCode {\n OK = 0,\n CANCELLED = 1,\n UNKNOWN = 2,\n INVALID_ARGUMENT = 3,\n DEADLINE_EXCEEDED = 4,\n NOT_FOUND = 5,\n ALREADY_EXISTS = 6,\n PERMISSION_DENIED = 7,\n UNAUTHENTICATED = 16,\n RESOURCE_EXHAUSTED = 8,\n FAILED_PRECONDITION = 9,\n ABORTED = 10,\n OUT_OF_RANGE = 11,\n UNIMPLEMENTED = 12,\n INTERNAL = 13,\n UNAVAILABLE = 14,\n DATA_LOSS = 15\n}\n\n/**\n * Determines whether an error code represents a permanent error when received\n * in response to a non-write operation.\n *\n * See isPermanentWriteError for classifying write errors.\n */\nexport function isPermanentError(code: Code): boolean {\n switch (code) {\n case Code.OK:\n return fail('Treated status OK as error');\n case Code.CANCELLED:\n case Code.UNKNOWN:\n case Code.DEADLINE_EXCEEDED:\n case Code.RESOURCE_EXHAUSTED:\n case Code.INTERNAL:\n case Code.UNAVAILABLE:\n // Unauthenticated means something went wrong with our token and we need\n // to retry with new credentials which will happen automatically.\n case Code.UNAUTHENTICATED:\n return false;\n case Code.INVALID_ARGUMENT:\n case Code.NOT_FOUND:\n case Code.ALREADY_EXISTS:\n case Code.PERMISSION_DENIED:\n case Code.FAILED_PRECONDITION:\n // Aborted might be retried in some scenarios, but that is dependant on\n // the context and should handled individually by the calling code.\n // See https://cloud.google.com/apis/design/errors.\n case Code.ABORTED:\n case Code.OUT_OF_RANGE:\n case Code.UNIMPLEMENTED:\n case Code.DATA_LOSS:\n return true;\n default:\n return fail('Unknown status code: ' + code);\n }\n}\n\n/**\n * Determines whether an error code represents a permanent error when received\n * in response to a write operation.\n *\n * Write operations must be handled specially because as of b/119437764, ABORTED\n * errors on the write stream should be retried too (even though ABORTED errors\n * are not generally retryable).\n *\n * Note that during the initial handshake on the write stream an ABORTED error\n * signals that we should discard our stream token (i.e. it is permanent). This\n * means a handshake error should be classified with isPermanentError, above.\n */\nexport function isPermanentWriteError(code: Code): boolean {\n return isPermanentError(code) && code !== Code.ABORTED;\n}\n\n/**\n * Maps an error Code from a GRPC status identifier like 'NOT_FOUND'.\n *\n * @returns The Code equivalent to the given status string or undefined if\n * there is no match.\n */\nexport function mapCodeFromRpcStatus(status: string): Code | undefined {\n // lookup by string\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const code: RpcCode = RpcCode[status as any] as any;\n if (code === undefined) {\n return undefined;\n }\n\n return mapCodeFromRpcCode(code);\n}\n\n/**\n * Maps an error Code from GRPC status code number, like 0, 1, or 14. These\n * are not the same as HTTP status codes.\n *\n * @returns The Code equivalent to the given GRPC status code. Fails if there\n * is no match.\n */\nexport function mapCodeFromRpcCode(code: number | undefined): Code {\n if (code === undefined) {\n // This shouldn't normally happen, but in certain error cases (like trying\n // to send invalid proto messages) we may get an error with no GRPC code.\n logError('GRPC error has no .code');\n return Code.UNKNOWN;\n }\n\n switch (code) {\n case RpcCode.OK:\n return Code.OK;\n case RpcCode.CANCELLED:\n return Code.CANCELLED;\n case RpcCode.UNKNOWN:\n return Code.UNKNOWN;\n case RpcCode.DEADLINE_EXCEEDED:\n return Code.DEADLINE_EXCEEDED;\n case RpcCode.RESOURCE_EXHAUSTED:\n return Code.RESOURCE_EXHAUSTED;\n case RpcCode.INTERNAL:\n return Code.INTERNAL;\n case RpcCode.UNAVAILABLE:\n return Code.UNAVAILABLE;\n case RpcCode.UNAUTHENTICATED:\n return Code.UNAUTHENTICATED;\n case RpcCode.INVALID_ARGUMENT:\n return Code.INVALID_ARGUMENT;\n case RpcCode.NOT_FOUND:\n return Code.NOT_FOUND;\n case RpcCode.ALREADY_EXISTS:\n return Code.ALREADY_EXISTS;\n case RpcCode.PERMISSION_DENIED:\n return Code.PERMISSION_DENIED;\n case RpcCode.FAILED_PRECONDITION:\n return Code.FAILED_PRECONDITION;\n case RpcCode.ABORTED:\n return Code.ABORTED;\n case RpcCode.OUT_OF_RANGE:\n return Code.OUT_OF_RANGE;\n case RpcCode.UNIMPLEMENTED:\n return Code.UNIMPLEMENTED;\n case RpcCode.DATA_LOSS:\n return Code.DATA_LOSS;\n default:\n return fail('Unknown status code: ' + code);\n }\n}\n\n/**\n * Maps an RPC code from a Code. This is the reverse operation from\n * mapCodeFromRpcCode and should really only be used in tests.\n */\nexport function mapRpcCodeFromCode(code: Code | undefined): number {\n if (code === undefined) {\n return RpcCode.OK;\n }\n\n switch (code) {\n case Code.OK:\n return RpcCode.OK;\n case Code.CANCELLED:\n return RpcCode.CANCELLED;\n case Code.UNKNOWN:\n return RpcCode.UNKNOWN;\n case Code.DEADLINE_EXCEEDED:\n return RpcCode.DEADLINE_EXCEEDED;\n case Code.RESOURCE_EXHAUSTED:\n return RpcCode.RESOURCE_EXHAUSTED;\n case Code.INTERNAL:\n return RpcCode.INTERNAL;\n case Code.UNAVAILABLE:\n return RpcCode.UNAVAILABLE;\n case Code.UNAUTHENTICATED:\n return RpcCode.UNAUTHENTICATED;\n case Code.INVALID_ARGUMENT:\n return RpcCode.INVALID_ARGUMENT;\n case Code.NOT_FOUND:\n return RpcCode.NOT_FOUND;\n case Code.ALREADY_EXISTS:\n return RpcCode.ALREADY_EXISTS;\n case Code.PERMISSION_DENIED:\n return RpcCode.PERMISSION_DENIED;\n case Code.FAILED_PRECONDITION:\n return RpcCode.FAILED_PRECONDITION;\n case Code.ABORTED:\n return RpcCode.ABORTED;\n case Code.OUT_OF_RANGE:\n return RpcCode.OUT_OF_RANGE;\n case Code.UNIMPLEMENTED:\n return RpcCode.UNIMPLEMENTED;\n case Code.DATA_LOSS:\n return RpcCode.DATA_LOSS;\n default:\n return fail('Unknown status code: ' + code);\n }\n}\n\n/**\n * Converts an HTTP Status Code to the equivalent error code.\n *\n * @param status An HTTP Status Code, like 200, 404, 503, etc.\n * @returns The equivalent Code. Unknown status codes are mapped to\n * Code.UNKNOWN.\n */\nexport function mapCodeFromHttpStatus(status: number): Code {\n // The canonical error codes for Google APIs [1] specify mapping onto HTTP\n // status codes but the mapping is not bijective. In each case of ambiguity\n // this function chooses a primary error.\n //\n // [1]\n // https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto\n switch (status) {\n case 200: // OK\n return Code.OK;\n\n case 400: // Bad Request\n return Code.INVALID_ARGUMENT;\n // Other possibilities based on the forward mapping\n // return Code.FAILED_PRECONDITION;\n // return Code.OUT_OF_RANGE;\n\n case 401: // Unauthorized\n return Code.UNAUTHENTICATED;\n\n case 403: // Forbidden\n return Code.PERMISSION_DENIED;\n\n case 404: // Not Found\n return Code.NOT_FOUND;\n\n case 409: // Conflict\n return Code.ABORTED;\n // Other possibilities:\n // return Code.ALREADY_EXISTS;\n\n case 416: // Range Not Satisfiable\n return Code.OUT_OF_RANGE;\n\n case 429: // Too Many Requests\n return Code.RESOURCE_EXHAUSTED;\n\n case 499: // Client Closed Request\n return Code.CANCELLED;\n\n case 500: // Internal Server Error\n return Code.UNKNOWN;\n // Other possibilities:\n // return Code.INTERNAL;\n // return Code.DATA_LOSS;\n\n case 501: // Unimplemented\n return Code.UNIMPLEMENTED;\n\n case 503: // Service Unavailable\n return Code.UNAVAILABLE;\n\n case 504: // Gateway Timeout\n return Code.DEADLINE_EXCEEDED;\n\n default:\n if (status >= 200 && status < 300) {\n return Code.OK;\n }\n if (status >= 400 && status < 500) {\n return Code.FAILED_PRECONDITION;\n }\n if (status >= 500 && status < 600) {\n return Code.INTERNAL;\n }\n return Code.UNKNOWN;\n }\n}\n\n/**\n * Converts an HTTP response's error status to the equivalent error code.\n *\n * @param status An HTTP error response status (\"FAILED_PRECONDITION\",\n * \"UNKNOWN\", etc.)\n * @returns The equivalent Code. Non-matching responses are mapped to\n * Code.UNKNOWN.\n */\nexport function mapCodeFromHttpResponseErrorStatus(status: string): Code {\n const serverError = status.toLowerCase().replace('_', '-');\n return Object.values(Code).indexOf(serverError as Code) >= 0\n ? (serverError as Code)\n : Code.UNKNOWN;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { Target } from '../core/target';\nimport { ListenSequenceNumber, TargetId } from '../core/types';\nimport { ByteString } from '../util/byte_string';\n\n/** An enumeration of the different purposes we have for targets. */\nexport const enum TargetPurpose {\n /** A regular, normal query target. */\n Listen,\n\n /**\n * The query target was used to refill a query after an existence filter mismatch.\n */\n ExistenceFilterMismatch,\n\n /** The query target was used to resolve a limbo document. */\n LimboResolution\n}\n\n/**\n * An immutable set of metadata that the local store tracks for each target.\n */\nexport class TargetData {\n constructor(\n /** The target being listened to. */\n readonly target: Target,\n /**\n * The target ID to which the target corresponds; Assigned by the\n * LocalStore for user listens and by the SyncEngine for limbo watches.\n */\n readonly targetId: TargetId,\n /** The purpose of the target. */\n readonly purpose: TargetPurpose,\n /**\n * The sequence number of the last transaction during which this target data\n * was modified.\n */\n readonly sequenceNumber: ListenSequenceNumber,\n /** The latest snapshot version seen for this target. */\n readonly snapshotVersion: SnapshotVersion = SnapshotVersion.min(),\n /**\n * The maximum snapshot version at which the associated view\n * contained no limbo documents.\n */\n readonly lastLimboFreeSnapshotVersion: SnapshotVersion = SnapshotVersion.min(),\n /**\n * An opaque, server-assigned token that allows watching a target to be\n * resumed after disconnecting without retransmitting all the data that\n * matches the target. The resume token essentially identifies a point in\n * time from which the server should resume sending results.\n */\n readonly resumeToken: ByteString = ByteString.EMPTY_BYTE_STRING\n ) {}\n\n /** Creates a new target data instance with an updated sequence number. */\n withSequenceNumber(sequenceNumber: number): TargetData {\n return new TargetData(\n this.target,\n this.targetId,\n this.purpose,\n sequenceNumber,\n this.snapshotVersion,\n this.lastLimboFreeSnapshotVersion,\n this.resumeToken\n );\n }\n\n /**\n * Creates a new target data instance with an updated resume token and\n * snapshot version.\n */\n withResumeToken(\n resumeToken: ByteString,\n snapshotVersion: SnapshotVersion\n ): TargetData {\n return new TargetData(\n this.target,\n this.targetId,\n this.purpose,\n this.sequenceNumber,\n snapshotVersion,\n this.lastLimboFreeSnapshotVersion,\n resumeToken\n );\n }\n\n /**\n * Creates a new target data instance with an updated last limbo free\n * snapshot version number.\n */\n withLastLimboFreeSnapshotVersion(\n lastLimboFreeSnapshotVersion: SnapshotVersion\n ): TargetData {\n return new TargetData(\n this.target,\n this.targetId,\n this.purpose,\n this.sequenceNumber,\n this.snapshotVersion,\n lastLimboFreeSnapshotVersion,\n this.resumeToken\n );\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport class ExistenceFilter {\n // TODO(b/33078163): just use simplest form of existence filter for now\n constructor(public count: number) {}\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { debugAssert, fail } from './assert';\n\n/*\n * Implementation of an immutable SortedMap using a Left-leaning\n * Red-Black Tree, adapted from the implementation in Mugs\n * (http://mads379.github.com/mugs/) by Mads Hartmann Jensen\n * (mads379@gmail.com).\n *\n * Original paper on Left-leaning Red-Black Trees:\n * http://www.cs.princeton.edu/~rs/talks/LLRB/LLRB.pdf\n *\n * Invariant 1: No red node has a red child\n * Invariant 2: Every leaf path has the same number of black nodes\n * Invariant 3: Only the left child can be red (left leaning)\n */\n\nexport type Comparator = (key1: K, key2: K) => number;\n\nexport interface Entry {\n key: K;\n value: V;\n}\n\n// An immutable sorted map implementation, based on a Left-leaning Red-Black\n// tree.\nexport class SortedMap {\n // visible for testing\n root: LLRBNode | LLRBEmptyNode;\n\n constructor(\n public comparator: Comparator,\n root?: LLRBNode | LLRBEmptyNode\n ) {\n this.root = root ? root : LLRBNode.EMPTY;\n }\n\n // Returns a copy of the map, with the specified key/value added or replaced.\n insert(key: K, value: V): SortedMap {\n return new SortedMap(\n this.comparator,\n this.root\n .insert(key, value, this.comparator)\n .copy(null, null, LLRBNode.BLACK, null, null)\n );\n }\n\n // Returns a copy of the map, with the specified key removed.\n remove(key: K): SortedMap {\n return new SortedMap(\n this.comparator,\n this.root\n .remove(key, this.comparator)\n .copy(null, null, LLRBNode.BLACK, null, null)\n );\n }\n\n // Returns the value of the node with the given key, or null.\n get(key: K): V | null {\n let node = this.root;\n while (!node.isEmpty()) {\n const cmp = this.comparator(key, node.key);\n if (cmp === 0) {\n return node.value;\n } else if (cmp < 0) {\n node = node.left;\n } else if (cmp > 0) {\n node = node.right;\n }\n }\n return null;\n }\n\n // Returns the index of the element in this sorted map, or -1 if it doesn't\n // exist.\n indexOf(key: K): number {\n // Number of nodes that were pruned when descending right\n let prunedNodes = 0;\n let node = this.root;\n while (!node.isEmpty()) {\n const cmp = this.comparator(key, node.key);\n if (cmp === 0) {\n return prunedNodes + node.left.size;\n } else if (cmp < 0) {\n node = node.left;\n } else {\n // Count all nodes left of the node plus the node itself\n prunedNodes += node.left.size + 1;\n node = node.right;\n }\n }\n // Node not found\n return -1;\n }\n\n isEmpty(): boolean {\n return this.root.isEmpty();\n }\n\n // Returns the total number of nodes in the map.\n get size(): number {\n return this.root.size;\n }\n\n // Returns the minimum key in the map.\n minKey(): K | null {\n return this.root.minKey();\n }\n\n // Returns the maximum key in the map.\n maxKey(): K | null {\n return this.root.maxKey();\n }\n\n // Traverses the map in key order and calls the specified action function\n // for each key/value pair. If action returns true, traversal is aborted.\n // Returns the first truthy value returned by action, or the last falsey\n // value returned by action.\n inorderTraversal(action: (k: K, v: V) => T): T {\n return (this.root as LLRBNode).inorderTraversal(action);\n }\n\n forEach(fn: (k: K, v: V) => void): void {\n this.inorderTraversal((k, v) => {\n fn(k, v);\n return false;\n });\n }\n\n toString(): string {\n const descriptions: string[] = [];\n this.inorderTraversal((k, v) => {\n descriptions.push(`${k}:${v}`);\n return false;\n });\n return `{${descriptions.join(', ')}}`;\n }\n\n // Traverses the map in reverse key order and calls the specified action\n // function for each key/value pair. If action returns true, traversal is\n // aborted.\n // Returns the first truthy value returned by action, or the last falsey\n // value returned by action.\n reverseTraversal(action: (k: K, v: V) => T): T {\n return (this.root as LLRBNode).reverseTraversal(action);\n }\n\n // Returns an iterator over the SortedMap.\n getIterator(): SortedMapIterator {\n return new SortedMapIterator(this.root, null, this.comparator, false);\n }\n\n getIteratorFrom(key: K): SortedMapIterator {\n return new SortedMapIterator(this.root, key, this.comparator, false);\n }\n\n getReverseIterator(): SortedMapIterator {\n return new SortedMapIterator(this.root, null, this.comparator, true);\n }\n\n getReverseIteratorFrom(key: K): SortedMapIterator {\n return new SortedMapIterator(this.root, key, this.comparator, true);\n }\n} // end SortedMap\n\n// An iterator over an LLRBNode.\nexport class SortedMapIterator {\n private isReverse: boolean;\n private nodeStack: Array | LLRBEmptyNode>;\n\n constructor(\n node: LLRBNode | LLRBEmptyNode,\n startKey: K | null,\n comparator: Comparator,\n isReverse: boolean\n ) {\n this.isReverse = isReverse;\n this.nodeStack = [];\n\n let cmp = 1;\n while (!node.isEmpty()) {\n cmp = startKey ? comparator(node.key, startKey) : 1;\n // flip the comparison if we're going in reverse\n if (isReverse) {\n cmp *= -1;\n }\n\n if (cmp < 0) {\n // This node is less than our start key. ignore it\n if (this.isReverse) {\n node = node.left;\n } else {\n node = node.right;\n }\n } else if (cmp === 0) {\n // This node is exactly equal to our start key. Push it on the stack,\n // but stop iterating;\n this.nodeStack.push(node);\n break;\n } else {\n // This node is greater than our start key, add it to the stack and move\n // to the next one\n this.nodeStack.push(node);\n if (this.isReverse) {\n node = node.right;\n } else {\n node = node.left;\n }\n }\n }\n }\n\n getNext(): Entry {\n debugAssert(\n this.nodeStack.length > 0,\n 'getNext() called on iterator when hasNext() is false.'\n );\n\n let node = this.nodeStack.pop()!;\n const result = { key: node.key, value: node.value };\n\n if (this.isReverse) {\n node = node.left;\n while (!node.isEmpty()) {\n this.nodeStack.push(node);\n node = node.right;\n }\n } else {\n node = node.right;\n while (!node.isEmpty()) {\n this.nodeStack.push(node);\n node = node.left;\n }\n }\n\n return result;\n }\n\n hasNext(): boolean {\n return this.nodeStack.length > 0;\n }\n\n peek(): Entry | null {\n if (this.nodeStack.length === 0) {\n return null;\n }\n\n const node = this.nodeStack[this.nodeStack.length - 1];\n return { key: node.key, value: node.value };\n }\n} // end SortedMapIterator\n\n// Represents a node in a Left-leaning Red-Black tree.\nexport class LLRBNode {\n readonly color: boolean;\n readonly left: LLRBNode | LLRBEmptyNode;\n readonly right: LLRBNode | LLRBEmptyNode;\n readonly size: number;\n\n // Empty node is shared between all LLRB trees.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n static EMPTY: LLRBEmptyNode = null as any;\n\n static RED = true;\n static BLACK = false;\n\n constructor(\n public key: K,\n public value: V,\n color?: boolean,\n left?: LLRBNode | LLRBEmptyNode,\n right?: LLRBNode | LLRBEmptyNode\n ) {\n this.color = color != null ? color : LLRBNode.RED;\n this.left = left != null ? left : LLRBNode.EMPTY;\n this.right = right != null ? right : LLRBNode.EMPTY;\n this.size = this.left.size + 1 + this.right.size;\n }\n\n // Returns a copy of the current node, optionally replacing pieces of it.\n copy(\n key: K | null,\n value: V | null,\n color: boolean | null,\n left: LLRBNode | LLRBEmptyNode | null,\n right: LLRBNode | LLRBEmptyNode | null\n ): LLRBNode {\n return new LLRBNode(\n key != null ? key : this.key,\n value != null ? value : this.value,\n color != null ? color : this.color,\n left != null ? left : this.left,\n right != null ? right : this.right\n );\n }\n\n isEmpty(): boolean {\n return false;\n }\n\n // Traverses the tree in key order and calls the specified action function\n // for each node. If action returns true, traversal is aborted.\n // Returns the first truthy value returned by action, or the last falsey\n // value returned by action.\n inorderTraversal(action: (k: K, v: V) => T): T {\n return (\n (this.left as LLRBNode).inorderTraversal(action) ||\n action(this.key, this.value) ||\n (this.right as LLRBNode).inorderTraversal(action)\n );\n }\n\n // Traverses the tree in reverse key order and calls the specified action\n // function for each node. If action returns true, traversal is aborted.\n // Returns the first truthy value returned by action, or the last falsey\n // value returned by action.\n reverseTraversal(action: (k: K, v: V) => T): T {\n return (\n (this.right as LLRBNode).reverseTraversal(action) ||\n action(this.key, this.value) ||\n (this.left as LLRBNode).reverseTraversal(action)\n );\n }\n\n // Returns the minimum node in the tree.\n private min(): LLRBNode {\n if (this.left.isEmpty()) {\n return this;\n } else {\n return (this.left as LLRBNode).min();\n }\n }\n\n // Returns the maximum key in the tree.\n minKey(): K | null {\n return this.min().key;\n }\n\n // Returns the maximum key in the tree.\n maxKey(): K | null {\n if (this.right.isEmpty()) {\n return this.key;\n } else {\n return this.right.maxKey();\n }\n }\n\n // Returns new tree, with the key/value added.\n insert(key: K, value: V, comparator: Comparator): LLRBNode {\n let n: LLRBNode = this;\n const cmp = comparator(key, n.key);\n if (cmp < 0) {\n n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);\n } else if (cmp === 0) {\n n = n.copy(null, value, null, null, null);\n } else {\n n = n.copy(\n null,\n null,\n null,\n null,\n n.right.insert(key, value, comparator)\n );\n }\n return n.fixUp();\n }\n\n private removeMin(): LLRBNode | LLRBEmptyNode {\n if (this.left.isEmpty()) {\n return LLRBNode.EMPTY;\n }\n let n: LLRBNode = this;\n if (!n.left.isRed() && !n.left.left.isRed()) {\n n = n.moveRedLeft();\n }\n n = n.copy(null, null, null, (n.left as LLRBNode).removeMin(), null);\n return n.fixUp();\n }\n\n // Returns new tree, with the specified item removed.\n remove(\n key: K,\n comparator: Comparator\n ): LLRBNode | LLRBEmptyNode {\n let smallest: LLRBNode;\n let n: LLRBNode = this;\n if (comparator(key, n.key) < 0) {\n if (!n.left.isEmpty() && !n.left.isRed() && !n.left.left.isRed()) {\n n = n.moveRedLeft();\n }\n n = n.copy(null, null, null, n.left.remove(key, comparator), null);\n } else {\n if (n.left.isRed()) {\n n = n.rotateRight();\n }\n if (!n.right.isEmpty() && !n.right.isRed() && !n.right.left.isRed()) {\n n = n.moveRedRight();\n }\n if (comparator(key, n.key) === 0) {\n if (n.right.isEmpty()) {\n return LLRBNode.EMPTY;\n } else {\n smallest = (n.right as LLRBNode).min();\n n = n.copy(\n smallest.key,\n smallest.value,\n null,\n null,\n (n.right as LLRBNode).removeMin()\n );\n }\n }\n n = n.copy(null, null, null, null, n.right.remove(key, comparator));\n }\n return n.fixUp();\n }\n\n isRed(): boolean {\n return this.color;\n }\n\n // Returns new tree after performing any needed rotations.\n private fixUp(): LLRBNode {\n let n: LLRBNode = this;\n if (n.right.isRed() && !n.left.isRed()) {\n n = n.rotateLeft();\n }\n if (n.left.isRed() && n.left.left.isRed()) {\n n = n.rotateRight();\n }\n if (n.left.isRed() && n.right.isRed()) {\n n = n.colorFlip();\n }\n return n;\n }\n\n private moveRedLeft(): LLRBNode {\n let n = this.colorFlip();\n if (n.right.left.isRed()) {\n n = n.copy(\n null,\n null,\n null,\n null,\n (n.right as LLRBNode).rotateRight()\n );\n n = n.rotateLeft();\n n = n.colorFlip();\n }\n return n;\n }\n\n private moveRedRight(): LLRBNode {\n let n = this.colorFlip();\n if (n.left.left.isRed()) {\n n = n.rotateRight();\n n = n.colorFlip();\n }\n return n;\n }\n\n private rotateLeft(): LLRBNode {\n const nl = this.copy(null, null, LLRBNode.RED, null, this.right.left);\n return (this.right as LLRBNode).copy(\n null,\n null,\n this.color,\n nl,\n null\n );\n }\n\n private rotateRight(): LLRBNode {\n const nr = this.copy(null, null, LLRBNode.RED, this.left.right, null);\n return (this.left as LLRBNode).copy(null, null, this.color, null, nr);\n }\n\n private colorFlip(): LLRBNode {\n const left = this.left.copy(null, null, !this.left.color, null, null);\n const right = this.right.copy(null, null, !this.right.color, null, null);\n return this.copy(null, null, !this.color, left, right);\n }\n\n // For testing.\n checkMaxDepth(): boolean {\n const blackDepth = this.check();\n if (Math.pow(2.0, blackDepth) <= this.size + 1) {\n return true;\n } else {\n return false;\n }\n }\n\n // In a balanced RB tree, the black-depth (number of black nodes) from root to\n // leaves is equal on both sides. This function verifies that or asserts.\n protected check(): number {\n if (this.isRed() && this.left.isRed()) {\n throw fail('Red node has red child(' + this.key + ',' + this.value + ')');\n }\n if (this.right.isRed()) {\n throw fail('Right child of (' + this.key + ',' + this.value + ') is red');\n }\n const blackDepth = (this.left as LLRBNode).check();\n if (blackDepth !== (this.right as LLRBNode).check()) {\n throw fail('Black depths differ');\n } else {\n return blackDepth + (this.isRed() ? 0 : 1);\n }\n }\n} // end LLRBNode\n\n// Represents an empty node (a leaf node in the Red-Black Tree).\nexport class LLRBEmptyNode {\n get key(): never {\n throw fail('LLRBEmptyNode has no key.');\n }\n get value(): never {\n throw fail('LLRBEmptyNode has no value.');\n }\n get color(): never {\n throw fail('LLRBEmptyNode has no color.');\n }\n get left(): never {\n throw fail('LLRBEmptyNode has no left child.');\n }\n get right(): never {\n throw fail('LLRBEmptyNode has no right child.');\n }\n size = 0;\n\n // Returns a copy of the current node.\n copy(\n key: K | null,\n value: V | null,\n color: boolean | null,\n left: LLRBNode | LLRBEmptyNode | null,\n right: LLRBNode | LLRBEmptyNode | null\n ): LLRBEmptyNode {\n return this;\n }\n\n // Returns a copy of the tree, with the specified key/value added.\n insert(key: K, value: V, comparator: Comparator): LLRBNode {\n return new LLRBNode(key, value);\n }\n\n // Returns a copy of the tree, with the specified key removed.\n remove(key: K, comparator: Comparator): LLRBEmptyNode {\n return this;\n }\n\n isEmpty(): boolean {\n return true;\n }\n\n inorderTraversal(action: (k: K, v: V) => boolean): boolean {\n return false;\n }\n\n reverseTraversal(action: (k: K, v: V) => boolean): boolean {\n return false;\n }\n\n minKey(): K | null {\n return null;\n }\n\n maxKey(): K | null {\n return null;\n }\n\n isRed(): boolean {\n return false;\n }\n\n // For testing.\n checkMaxDepth(): boolean {\n return true;\n }\n\n protected check(): 0 {\n return 0;\n }\n} // end LLRBEmptyNode\n\nLLRBNode.EMPTY = new LLRBEmptyNode();\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SortedMap, SortedMapIterator } from './sorted_map';\n\n/**\n * SortedSet is an immutable (copy-on-write) collection that holds elements\n * in order specified by the provided comparator.\n *\n * NOTE: if provided comparator returns 0 for two elements, we consider them to\n * be equal!\n */\nexport class SortedSet {\n private data: SortedMap;\n\n constructor(private comparator: (left: T, right: T) => number) {\n this.data = new SortedMap(this.comparator);\n }\n\n has(elem: T): boolean {\n return this.data.get(elem) !== null;\n }\n\n first(): T | null {\n return this.data.minKey();\n }\n\n last(): T | null {\n return this.data.maxKey();\n }\n\n get size(): number {\n return this.data.size;\n }\n\n indexOf(elem: T): number {\n return this.data.indexOf(elem);\n }\n\n /** Iterates elements in order defined by \"comparator\" */\n forEach(cb: (elem: T) => void): void {\n this.data.inorderTraversal((k: T, v: boolean) => {\n cb(k);\n return false;\n });\n }\n\n /** Iterates over `elem`s such that: range[0] <= elem < range[1]. */\n forEachInRange(range: [T, T], cb: (elem: T) => void): void {\n const iter = this.data.getIteratorFrom(range[0]);\n while (iter.hasNext()) {\n const elem = iter.getNext();\n if (this.comparator(elem.key, range[1]) >= 0) {\n return;\n }\n cb(elem.key);\n }\n }\n\n /**\n * Iterates over `elem`s such that: start <= elem until false is returned.\n */\n forEachWhile(cb: (elem: T) => boolean, start?: T): void {\n let iter: SortedMapIterator;\n if (start !== undefined) {\n iter = this.data.getIteratorFrom(start);\n } else {\n iter = this.data.getIterator();\n }\n while (iter.hasNext()) {\n const elem = iter.getNext();\n const result = cb(elem.key);\n if (!result) {\n return;\n }\n }\n }\n\n /** Finds the least element greater than or equal to `elem`. */\n firstAfterOrEqual(elem: T): T | null {\n const iter = this.data.getIteratorFrom(elem);\n return iter.hasNext() ? iter.getNext().key : null;\n }\n\n getIterator(): SortedSetIterator {\n return new SortedSetIterator(this.data.getIterator());\n }\n\n getIteratorFrom(key: T): SortedSetIterator {\n return new SortedSetIterator(this.data.getIteratorFrom(key));\n }\n\n /** Inserts or updates an element */\n add(elem: T): SortedSet {\n return this.copy(this.data.remove(elem).insert(elem, true));\n }\n\n /** Deletes an element */\n delete(elem: T): SortedSet {\n if (!this.has(elem)) {\n return this;\n }\n return this.copy(this.data.remove(elem));\n }\n\n isEmpty(): boolean {\n return this.data.isEmpty();\n }\n\n unionWith(other: SortedSet): SortedSet {\n let result: SortedSet = this;\n\n // Make sure `result` always refers to the larger one of the two sets.\n if (result.size < other.size) {\n result = other;\n other = this;\n }\n\n other.forEach(elem => {\n result = result.add(elem);\n });\n return result;\n }\n\n isEqual(other: SortedSet): boolean {\n if (!(other instanceof SortedSet)) {\n return false;\n }\n if (this.size !== other.size) {\n return false;\n }\n\n const thisIt = this.data.getIterator();\n const otherIt = other.data.getIterator();\n while (thisIt.hasNext()) {\n const thisElem = thisIt.getNext().key;\n const otherElem = otherIt.getNext().key;\n if (this.comparator(thisElem, otherElem) !== 0) {\n return false;\n }\n }\n return true;\n }\n\n toArray(): T[] {\n const res: T[] = [];\n this.forEach(targetId => {\n res.push(targetId);\n });\n return res;\n }\n\n toString(): string {\n const result: T[] = [];\n this.forEach(elem => result.push(elem));\n return 'SortedSet(' + result.toString() + ')';\n }\n\n private copy(data: SortedMap): SortedSet {\n const result = new SortedSet(this.comparator);\n result.data = data;\n return result;\n }\n}\n\nexport class SortedSetIterator {\n constructor(private iter: SortedMapIterator) {}\n\n getNext(): T {\n return this.iter.getNext().key;\n }\n\n hasNext(): boolean {\n return this.iter.hasNext();\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { SortedMap } from '../util/sorted_map';\nimport { SortedSet } from '../util/sorted_set';\n\nimport { TargetId } from '../core/types';\nimport { primitiveComparator } from '../util/misc';\nimport { Document, MaybeDocument } from './document';\nimport { DocumentKey } from './document_key';\n\n/** Miscellaneous collection types / constants. */\nexport interface DocumentSizeEntry {\n maybeDocument: MaybeDocument;\n size: number;\n}\n\nexport type MaybeDocumentMap = SortedMap;\nconst EMPTY_MAYBE_DOCUMENT_MAP = new SortedMap(\n DocumentKey.comparator\n);\nexport function maybeDocumentMap(): MaybeDocumentMap {\n return EMPTY_MAYBE_DOCUMENT_MAP;\n}\n\nexport type NullableMaybeDocumentMap = SortedMap<\n DocumentKey,\n MaybeDocument | null\n>;\n\nexport function nullableMaybeDocumentMap(): NullableMaybeDocumentMap {\n return maybeDocumentMap();\n}\n\nexport interface DocumentSizeEntries {\n maybeDocuments: NullableMaybeDocumentMap;\n sizeMap: SortedMap;\n}\n\nexport type DocumentMap = SortedMap;\nconst EMPTY_DOCUMENT_MAP = new SortedMap(\n DocumentKey.comparator\n);\nexport function documentMap(): DocumentMap {\n return EMPTY_DOCUMENT_MAP;\n}\n\nexport type DocumentVersionMap = SortedMap;\nconst EMPTY_DOCUMENT_VERSION_MAP = new SortedMap(\n DocumentKey.comparator\n);\nexport function documentVersionMap(): DocumentVersionMap {\n return EMPTY_DOCUMENT_VERSION_MAP;\n}\n\nexport type DocumentKeySet = SortedSet;\nconst EMPTY_DOCUMENT_KEY_SET = new SortedSet(DocumentKey.comparator);\nexport function documentKeySet(...keys: DocumentKey[]): DocumentKeySet {\n let set = EMPTY_DOCUMENT_KEY_SET;\n for (const key of keys) {\n set = set.add(key);\n }\n return set;\n}\n\nexport type TargetIdSet = SortedSet;\nconst EMPTY_TARGET_ID_SET = new SortedSet(primitiveComparator);\nexport function targetIdSet(): SortedSet {\n return EMPTY_TARGET_ID_SET;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SortedMap } from '../util/sorted_map';\n\nimport { documentMap } from './collections';\nimport { Document } from './document';\nimport { DocumentComparator } from './document_comparator';\nimport { DocumentKey } from './document_key';\n\n/**\n * DocumentSet is an immutable (copy-on-write) collection that holds documents\n * in order specified by the provided comparator. We always add a document key\n * comparator on top of what is provided to guarantee document equality based on\n * the key.\n */\n\nexport class DocumentSet {\n /**\n * Returns an empty copy of the existing DocumentSet, using the same\n * comparator.\n */\n static emptySet(oldSet: DocumentSet): DocumentSet {\n return new DocumentSet(oldSet.comparator);\n }\n\n private comparator: DocumentComparator;\n private keyedMap: SortedMap;\n private sortedSet: SortedMap;\n\n /** The default ordering is by key if the comparator is omitted */\n constructor(comp?: DocumentComparator) {\n // We are adding document key comparator to the end as it's the only\n // guaranteed unique property of a document.\n if (comp) {\n this.comparator = (d1: Document, d2: Document) =>\n comp(d1, d2) || DocumentKey.comparator(d1.key, d2.key);\n } else {\n this.comparator = (d1: Document, d2: Document) =>\n DocumentKey.comparator(d1.key, d2.key);\n }\n\n this.keyedMap = documentMap();\n this.sortedSet = new SortedMap(this.comparator);\n }\n\n has(key: DocumentKey): boolean {\n return this.keyedMap.get(key) != null;\n }\n\n get(key: DocumentKey): Document | null {\n return this.keyedMap.get(key);\n }\n\n first(): Document | null {\n return this.sortedSet.minKey();\n }\n\n last(): Document | null {\n return this.sortedSet.maxKey();\n }\n\n isEmpty(): boolean {\n return this.sortedSet.isEmpty();\n }\n\n /**\n * Returns the index of the provided key in the document set, or -1 if the\n * document key is not present in the set;\n */\n indexOf(key: DocumentKey): number {\n const doc = this.keyedMap.get(key);\n return doc ? this.sortedSet.indexOf(doc) : -1;\n }\n\n get size(): number {\n return this.sortedSet.size;\n }\n\n /** Iterates documents in order defined by \"comparator\" */\n forEach(cb: (doc: Document) => void): void {\n this.sortedSet.inorderTraversal((k, v) => {\n cb(k);\n return false;\n });\n }\n\n /** Inserts or updates a document with the same key */\n add(doc: Document): DocumentSet {\n // First remove the element if we have it.\n const set = this.delete(doc.key);\n return set.copy(\n set.keyedMap.insert(doc.key, doc),\n set.sortedSet.insert(doc, null)\n );\n }\n\n /** Deletes a document with a given key */\n delete(key: DocumentKey): DocumentSet {\n const doc = this.get(key);\n if (!doc) {\n return this;\n }\n\n return this.copy(this.keyedMap.remove(key), this.sortedSet.remove(doc));\n }\n\n isEqual(other: DocumentSet | null | undefined): boolean {\n if (!(other instanceof DocumentSet)) {\n return false;\n }\n if (this.size !== other.size) {\n return false;\n }\n\n const thisIt = this.sortedSet.getIterator();\n const otherIt = other.sortedSet.getIterator();\n while (thisIt.hasNext()) {\n const thisDoc = thisIt.getNext().key;\n const otherDoc = otherIt.getNext().key;\n if (!thisDoc.isEqual(otherDoc)) {\n return false;\n }\n }\n return true;\n }\n\n toString(): string {\n const docStrings: string[] = [];\n this.forEach(doc => {\n docStrings.push(doc.toString());\n });\n if (docStrings.length === 0) {\n return 'DocumentSet ()';\n } else {\n return 'DocumentSet (\\n ' + docStrings.join(' \\n') + '\\n)';\n }\n }\n\n private copy(\n keyedMap: SortedMap,\n sortedSet: SortedMap\n ): DocumentSet {\n const newSet = new DocumentSet();\n newSet.comparator = this.comparator;\n newSet.keyedMap = keyedMap;\n newSet.sortedSet = sortedSet;\n return newSet;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Document } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport { DocumentSet } from '../model/document_set';\nimport { fail } from '../util/assert';\nimport { SortedMap } from '../util/sorted_map';\n\nimport { DocumentKeySet } from '../model/collections';\nimport { Query, queryEquals } from './query';\n\nexport const enum ChangeType {\n Added,\n Removed,\n Modified,\n Metadata\n}\n\nexport interface DocumentViewChange {\n type: ChangeType;\n doc: Document;\n}\n\nexport const enum SyncState {\n Local,\n Synced\n}\n\n/**\n * DocumentChangeSet keeps track of a set of changes to docs in a query, merging\n * duplicate events for the same doc.\n */\nexport class DocumentChangeSet {\n private changeMap = new SortedMap(\n DocumentKey.comparator\n );\n\n track(change: DocumentViewChange): void {\n const key = change.doc.key;\n const oldChange = this.changeMap.get(key);\n if (!oldChange) {\n this.changeMap = this.changeMap.insert(key, change);\n return;\n }\n\n // Merge the new change with the existing change.\n if (\n change.type !== ChangeType.Added &&\n oldChange.type === ChangeType.Metadata\n ) {\n this.changeMap = this.changeMap.insert(key, change);\n } else if (\n change.type === ChangeType.Metadata &&\n oldChange.type !== ChangeType.Removed\n ) {\n this.changeMap = this.changeMap.insert(key, {\n type: oldChange.type,\n doc: change.doc\n });\n } else if (\n change.type === ChangeType.Modified &&\n oldChange.type === ChangeType.Modified\n ) {\n this.changeMap = this.changeMap.insert(key, {\n type: ChangeType.Modified,\n doc: change.doc\n });\n } else if (\n change.type === ChangeType.Modified &&\n oldChange.type === ChangeType.Added\n ) {\n this.changeMap = this.changeMap.insert(key, {\n type: ChangeType.Added,\n doc: change.doc\n });\n } else if (\n change.type === ChangeType.Removed &&\n oldChange.type === ChangeType.Added\n ) {\n this.changeMap = this.changeMap.remove(key);\n } else if (\n change.type === ChangeType.Removed &&\n oldChange.type === ChangeType.Modified\n ) {\n this.changeMap = this.changeMap.insert(key, {\n type: ChangeType.Removed,\n doc: oldChange.doc\n });\n } else if (\n change.type === ChangeType.Added &&\n oldChange.type === ChangeType.Removed\n ) {\n this.changeMap = this.changeMap.insert(key, {\n type: ChangeType.Modified,\n doc: change.doc\n });\n } else {\n // This includes these cases, which don't make sense:\n // Added->Added\n // Removed->Removed\n // Modified->Added\n // Removed->Modified\n // Metadata->Added\n // Removed->Metadata\n fail(\n 'unsupported combination of changes: ' +\n JSON.stringify(change) +\n ' after ' +\n JSON.stringify(oldChange)\n );\n }\n }\n\n getChanges(): DocumentViewChange[] {\n const changes: DocumentViewChange[] = [];\n this.changeMap.inorderTraversal(\n (key: DocumentKey, change: DocumentViewChange) => {\n changes.push(change);\n }\n );\n return changes;\n }\n}\n\nexport class ViewSnapshot {\n constructor(\n readonly query: Query,\n readonly docs: DocumentSet,\n readonly oldDocs: DocumentSet,\n readonly docChanges: DocumentViewChange[],\n readonly mutatedKeys: DocumentKeySet,\n readonly fromCache: boolean,\n readonly syncStateChanged: boolean,\n readonly excludesMetadataChanges: boolean\n ) {}\n\n /** Returns a view snapshot as if all documents in the snapshot were added. */\n static fromInitialDocuments(\n query: Query,\n documents: DocumentSet,\n mutatedKeys: DocumentKeySet,\n fromCache: boolean\n ): ViewSnapshot {\n const changes: DocumentViewChange[] = [];\n documents.forEach(doc => {\n changes.push({ type: ChangeType.Added, doc });\n });\n\n return new ViewSnapshot(\n query,\n documents,\n DocumentSet.emptySet(documents),\n changes,\n mutatedKeys,\n fromCache,\n /* syncStateChanged= */ true,\n /* excludesMetadataChanges= */ false\n );\n }\n\n get hasPendingWrites(): boolean {\n return !this.mutatedKeys.isEmpty();\n }\n\n isEqual(other: ViewSnapshot): boolean {\n if (\n this.fromCache !== other.fromCache ||\n this.syncStateChanged !== other.syncStateChanged ||\n !this.mutatedKeys.isEqual(other.mutatedKeys) ||\n !queryEquals(this.query, other.query) ||\n !this.docs.isEqual(other.docs) ||\n !this.oldDocs.isEqual(other.oldDocs)\n ) {\n return false;\n }\n const changes: DocumentViewChange[] = this.docChanges;\n const otherChanges: DocumentViewChange[] = other.docChanges;\n if (changes.length !== otherChanges.length) {\n return false;\n }\n for (let i = 0; i < changes.length; i++) {\n if (\n changes[i].type !== otherChanges[i].type ||\n !changes[i].doc.isEqual(otherChanges[i].doc)\n ) {\n return false;\n }\n }\n return true;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { TargetId } from '../core/types';\nimport {\n documentKeySet,\n DocumentKeySet,\n maybeDocumentMap,\n MaybeDocumentMap,\n targetIdSet\n} from '../model/collections';\nimport { SortedSet } from '../util/sorted_set';\nimport { ByteString } from '../util/byte_string';\n\n/**\n * An event from the RemoteStore. It is split into targetChanges (changes to the\n * state or the set of documents in our watched targets) and documentUpdates\n * (changes to the actual documents).\n */\nexport class RemoteEvent {\n constructor(\n /**\n * The snapshot version this event brings us up to, or MIN if not set.\n */\n readonly snapshotVersion: SnapshotVersion,\n /**\n * A map from target to changes to the target. See TargetChange.\n */\n readonly targetChanges: Map,\n /**\n * A set of targets that is known to be inconsistent. Listens for these\n * targets should be re-established without resume tokens.\n */\n readonly targetMismatches: SortedSet,\n /**\n * A set of which documents have changed or been deleted, along with the\n * doc's new values (if not deleted).\n */\n readonly documentUpdates: MaybeDocumentMap,\n /**\n * A set of which document updates are due only to limbo resolution targets.\n */\n readonly resolvedLimboDocuments: DocumentKeySet\n ) {}\n\n /**\n * HACK: Views require RemoteEvents in order to determine whether the view is\n * CURRENT, but secondary tabs don't receive remote events. So this method is\n * used to create a synthesized RemoteEvent that can be used to apply a\n * CURRENT status change to a View, for queries executed in a different tab.\n */\n // PORTING NOTE: Multi-tab only\n static createSynthesizedRemoteEventForCurrentChange(\n targetId: TargetId,\n current: boolean\n ): RemoteEvent {\n const targetChanges = new Map();\n targetChanges.set(\n targetId,\n TargetChange.createSynthesizedTargetChangeForCurrentChange(\n targetId,\n current\n )\n );\n return new RemoteEvent(\n SnapshotVersion.min(),\n targetChanges,\n targetIdSet(),\n maybeDocumentMap(),\n documentKeySet()\n );\n }\n}\n\n/**\n * A TargetChange specifies the set of changes for a specific target as part of\n * a RemoteEvent. These changes track which documents are added, modified or\n * removed, as well as the target's resume token and whether the target is\n * marked CURRENT.\n * The actual changes *to* documents are not part of the TargetChange since\n * documents may be part of multiple targets.\n */\nexport class TargetChange {\n constructor(\n /**\n * An opaque, server-assigned token that allows watching a query to be resumed\n * after disconnecting without retransmitting all the data that matches the\n * query. The resume token essentially identifies a point in time from which\n * the server should resume sending results.\n */\n readonly resumeToken: ByteString,\n /**\n * The \"current\" (synced) status of this target. Note that \"current\"\n * has special meaning in the RPC protocol that implies that a target is\n * both up-to-date and consistent with the rest of the watch stream.\n */\n readonly current: boolean,\n /**\n * The set of documents that were newly assigned to this target as part of\n * this remote event.\n */\n readonly addedDocuments: DocumentKeySet,\n /**\n * The set of documents that were already assigned to this target but received\n * an update during this remote event.\n */\n readonly modifiedDocuments: DocumentKeySet,\n /**\n * The set of documents that were removed from this target as part of this\n * remote event.\n */\n readonly removedDocuments: DocumentKeySet\n ) {}\n\n /**\n * This method is used to create a synthesized TargetChanges that can be used to\n * apply a CURRENT status change to a View (for queries executed in a different\n * tab) or for new queries (to raise snapshots with correct CURRENT status).\n */\n static createSynthesizedTargetChangeForCurrentChange(\n targetId: TargetId,\n current: boolean\n ): TargetChange {\n return new TargetChange(\n ByteString.EMPTY_BYTE_STRING,\n current,\n documentKeySet(),\n documentKeySet(),\n documentKeySet()\n );\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { TargetId } from '../core/types';\nimport { ChangeType } from '../core/view_snapshot';\nimport { TargetData, TargetPurpose } from '../local/target_data';\nimport {\n documentKeySet,\n DocumentKeySet,\n maybeDocumentMap\n} from '../model/collections';\nimport { Document, MaybeDocument, NoDocument } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport { debugAssert, fail, hardAssert } from '../util/assert';\nimport { FirestoreError } from '../util/error';\nimport { logDebug } from '../util/log';\nimport { primitiveComparator } from '../util/misc';\nimport { SortedMap } from '../util/sorted_map';\nimport { SortedSet } from '../util/sorted_set';\nimport { ExistenceFilter } from './existence_filter';\nimport { RemoteEvent, TargetChange } from './remote_event';\nimport { ByteString } from '../util/byte_string';\nimport { isDocumentTarget } from '../core/target';\n\n/**\n * Internal representation of the watcher API protocol buffers.\n */\nexport type WatchChange =\n | DocumentWatchChange\n | WatchTargetChange\n | ExistenceFilterChange;\n\n/**\n * Represents a changed document and a list of target ids to which this change\n * applies.\n *\n * If document has been deleted NoDocument will be provided.\n */\nexport class DocumentWatchChange {\n constructor(\n /** The new document applies to all of these targets. */\n public updatedTargetIds: TargetId[],\n /** The new document is removed from all of these targets. */\n public removedTargetIds: TargetId[],\n /** The key of the document for this change. */\n public key: DocumentKey,\n /**\n * The new document or NoDocument if it was deleted. Is null if the\n * document went out of view without the server sending a new document.\n */\n public newDoc: MaybeDocument | null\n ) {}\n}\n\nexport class ExistenceFilterChange {\n constructor(\n public targetId: TargetId,\n public existenceFilter: ExistenceFilter\n ) {}\n}\n\nexport const enum WatchTargetChangeState {\n NoChange,\n Added,\n Removed,\n Current,\n Reset\n}\n\nexport class WatchTargetChange {\n constructor(\n /** What kind of change occurred to the watch target. */\n public state: WatchTargetChangeState,\n /** The target IDs that were added/removed/set. */\n public targetIds: TargetId[],\n /**\n * An opaque, server-assigned token that allows watching a target to be\n * resumed after disconnecting without retransmitting all the data that\n * matches the target. The resume token essentially identifies a point in\n * time from which the server should resume sending results.\n */\n public resumeToken: ByteString = ByteString.EMPTY_BYTE_STRING,\n /** An RPC error indicating why the watch failed. */\n public cause: FirestoreError | null = null\n ) {}\n}\n\n/** Tracks the internal state of a Watch target. */\nclass TargetState {\n /**\n * The number of pending responses (adds or removes) that we are waiting on.\n * We only consider targets active that have no pending responses.\n */\n private pendingResponses = 0;\n\n /**\n * Keeps track of the document changes since the last raised snapshot.\n *\n * These changes are continuously updated as we receive document updates and\n * always reflect the current set of changes against the last issued snapshot.\n */\n private documentChanges: SortedMap<\n DocumentKey,\n ChangeType\n > = snapshotChangesMap();\n\n /** See public getters for explanations of these fields. */\n private _resumeToken: ByteString = ByteString.EMPTY_BYTE_STRING;\n private _current = false;\n\n /**\n * Whether this target state should be included in the next snapshot. We\n * initialize to true so that newly-added targets are included in the next\n * RemoteEvent.\n */\n private _hasPendingChanges = true;\n\n /**\n * Whether this target has been marked 'current'.\n *\n * 'Current' has special meaning in the RPC protocol: It implies that the\n * Watch backend has sent us all changes up to the point at which the target\n * was added and that the target is consistent with the rest of the watch\n * stream.\n */\n get current(): boolean {\n return this._current;\n }\n\n /** The last resume token sent to us for this target. */\n get resumeToken(): ByteString {\n return this._resumeToken;\n }\n\n /** Whether this target has pending target adds or target removes. */\n get isPending(): boolean {\n return this.pendingResponses !== 0;\n }\n\n /** Whether we have modified any state that should trigger a snapshot. */\n get hasPendingChanges(): boolean {\n return this._hasPendingChanges;\n }\n\n /**\n * Applies the resume token to the TargetChange, but only when it has a new\n * value. Empty resumeTokens are discarded.\n */\n updateResumeToken(resumeToken: ByteString): void {\n if (resumeToken.approximateByteSize() > 0) {\n this._hasPendingChanges = true;\n this._resumeToken = resumeToken;\n }\n }\n\n /**\n * Creates a target change from the current set of changes.\n *\n * To reset the document changes after raising this snapshot, call\n * `clearPendingChanges()`.\n */\n toTargetChange(): TargetChange {\n let addedDocuments = documentKeySet();\n let modifiedDocuments = documentKeySet();\n let removedDocuments = documentKeySet();\n\n this.documentChanges.forEach((key, changeType) => {\n switch (changeType) {\n case ChangeType.Added:\n addedDocuments = addedDocuments.add(key);\n break;\n case ChangeType.Modified:\n modifiedDocuments = modifiedDocuments.add(key);\n break;\n case ChangeType.Removed:\n removedDocuments = removedDocuments.add(key);\n break;\n default:\n fail('Encountered invalid change type: ' + changeType);\n }\n });\n\n return new TargetChange(\n this._resumeToken,\n this._current,\n addedDocuments,\n modifiedDocuments,\n removedDocuments\n );\n }\n\n /**\n * Resets the document changes and sets `hasPendingChanges` to false.\n */\n clearPendingChanges(): void {\n this._hasPendingChanges = false;\n this.documentChanges = snapshotChangesMap();\n }\n\n addDocumentChange(key: DocumentKey, changeType: ChangeType): void {\n this._hasPendingChanges = true;\n this.documentChanges = this.documentChanges.insert(key, changeType);\n }\n\n removeDocumentChange(key: DocumentKey): void {\n this._hasPendingChanges = true;\n this.documentChanges = this.documentChanges.remove(key);\n }\n\n recordPendingTargetRequest(): void {\n this.pendingResponses += 1;\n }\n\n recordTargetResponse(): void {\n this.pendingResponses -= 1;\n }\n\n markCurrent(): void {\n this._hasPendingChanges = true;\n this._current = true;\n }\n}\n\n/**\n * Interface implemented by RemoteStore to expose target metadata to the\n * WatchChangeAggregator.\n */\nexport interface TargetMetadataProvider {\n /**\n * Returns the set of remote document keys for the given target ID as of the\n * last raised snapshot.\n */\n getRemoteKeysForTarget(targetId: TargetId): DocumentKeySet;\n\n /**\n * Returns the TargetData for an active target ID or 'null' if this target\n * has become inactive\n */\n getTargetDataForTarget(targetId: TargetId): TargetData | null;\n}\n\nconst LOG_TAG = 'WatchChangeAggregator';\n\n/**\n * A helper class to accumulate watch changes into a RemoteEvent.\n */\nexport class WatchChangeAggregator {\n constructor(private metadataProvider: TargetMetadataProvider) {}\n\n /** The internal state of all tracked targets. */\n private targetStates = new Map();\n\n /** Keeps track of the documents to update since the last raised snapshot. */\n private pendingDocumentUpdates = maybeDocumentMap();\n\n /** A mapping of document keys to their set of target IDs. */\n private pendingDocumentTargetMapping = documentTargetMap();\n\n /**\n * A list of targets with existence filter mismatches. These targets are\n * known to be inconsistent and their listens needs to be re-established by\n * RemoteStore.\n */\n private pendingTargetResets = new SortedSet(primitiveComparator);\n\n /**\n * Processes and adds the DocumentWatchChange to the current set of changes.\n */\n handleDocumentChange(docChange: DocumentWatchChange): void {\n for (const targetId of docChange.updatedTargetIds) {\n if (docChange.newDoc instanceof Document) {\n this.addDocumentToTarget(targetId, docChange.newDoc);\n } else if (docChange.newDoc instanceof NoDocument) {\n this.removeDocumentFromTarget(\n targetId,\n docChange.key,\n docChange.newDoc\n );\n }\n }\n\n for (const targetId of docChange.removedTargetIds) {\n this.removeDocumentFromTarget(targetId, docChange.key, docChange.newDoc);\n }\n }\n\n /** Processes and adds the WatchTargetChange to the current set of changes. */\n handleTargetChange(targetChange: WatchTargetChange): void {\n this.forEachTarget(targetChange, targetId => {\n const targetState = this.ensureTargetState(targetId);\n switch (targetChange.state) {\n case WatchTargetChangeState.NoChange:\n if (this.isActiveTarget(targetId)) {\n targetState.updateResumeToken(targetChange.resumeToken);\n }\n break;\n case WatchTargetChangeState.Added:\n // We need to decrement the number of pending acks needed from watch\n // for this targetId.\n targetState.recordTargetResponse();\n if (!targetState.isPending) {\n // We have a freshly added target, so we need to reset any state\n // that we had previously. This can happen e.g. when remove and add\n // back a target for existence filter mismatches.\n targetState.clearPendingChanges();\n }\n targetState.updateResumeToken(targetChange.resumeToken);\n break;\n case WatchTargetChangeState.Removed:\n // We need to keep track of removed targets to we can post-filter and\n // remove any target changes.\n // We need to decrement the number of pending acks needed from watch\n // for this targetId.\n targetState.recordTargetResponse();\n if (!targetState.isPending) {\n this.removeTarget(targetId);\n }\n debugAssert(\n !targetChange.cause,\n 'WatchChangeAggregator does not handle errored targets'\n );\n break;\n case WatchTargetChangeState.Current:\n if (this.isActiveTarget(targetId)) {\n targetState.markCurrent();\n targetState.updateResumeToken(targetChange.resumeToken);\n }\n break;\n case WatchTargetChangeState.Reset:\n if (this.isActiveTarget(targetId)) {\n // Reset the target and synthesizes removes for all existing\n // documents. The backend will re-add any documents that still\n // match the target before it sends the next global snapshot.\n this.resetTarget(targetId);\n targetState.updateResumeToken(targetChange.resumeToken);\n }\n break;\n default:\n fail('Unknown target watch change state: ' + targetChange.state);\n }\n });\n }\n\n /**\n * Iterates over all targetIds that the watch change applies to: either the\n * targetIds explicitly listed in the change or the targetIds of all currently\n * active targets.\n */\n forEachTarget(\n targetChange: WatchTargetChange,\n fn: (targetId: TargetId) => void\n ): void {\n if (targetChange.targetIds.length > 0) {\n targetChange.targetIds.forEach(fn);\n } else {\n this.targetStates.forEach((_, targetId) => {\n if (this.isActiveTarget(targetId)) {\n fn(targetId);\n }\n });\n }\n }\n\n /**\n * Handles existence filters and synthesizes deletes for filter mismatches.\n * Targets that are invalidated by filter mismatches are added to\n * `pendingTargetResets`.\n */\n handleExistenceFilter(watchChange: ExistenceFilterChange): void {\n const targetId = watchChange.targetId;\n const expectedCount = watchChange.existenceFilter.count;\n\n const targetData = this.targetDataForActiveTarget(targetId);\n if (targetData) {\n const target = targetData.target;\n if (isDocumentTarget(target)) {\n if (expectedCount === 0) {\n // The existence filter told us the document does not exist. We deduce\n // that this document does not exist and apply a deleted document to\n // our updates. Without applying this deleted document there might be\n // another query that will raise this document as part of a snapshot\n // until it is resolved, essentially exposing inconsistency between\n // queries.\n const key = new DocumentKey(target.path);\n this.removeDocumentFromTarget(\n targetId,\n key,\n new NoDocument(key, SnapshotVersion.min())\n );\n } else {\n hardAssert(\n expectedCount === 1,\n 'Single document existence filter with count: ' + expectedCount\n );\n }\n } else {\n const currentSize = this.getCurrentDocumentCountForTarget(targetId);\n if (currentSize !== expectedCount) {\n // Existence filter mismatch: We reset the mapping and raise a new\n // snapshot with `isFromCache:true`.\n this.resetTarget(targetId);\n this.pendingTargetResets = this.pendingTargetResets.add(targetId);\n }\n }\n }\n }\n\n /**\n * Converts the currently accumulated state into a remote event at the\n * provided snapshot version. Resets the accumulated changes before returning.\n */\n createRemoteEvent(snapshotVersion: SnapshotVersion): RemoteEvent {\n const targetChanges = new Map();\n\n this.targetStates.forEach((targetState, targetId) => {\n const targetData = this.targetDataForActiveTarget(targetId);\n if (targetData) {\n if (targetState.current && isDocumentTarget(targetData.target)) {\n // Document queries for document that don't exist can produce an empty\n // result set. To update our local cache, we synthesize a document\n // delete if we have not previously received the document. This\n // resolves the limbo state of the document, removing it from\n // limboDocumentRefs.\n //\n // TODO(dimond): Ideally we would have an explicit lookup target\n // instead resulting in an explicit delete message and we could\n // remove this special logic.\n const key = new DocumentKey(targetData.target.path);\n if (\n this.pendingDocumentUpdates.get(key) === null &&\n !this.targetContainsDocument(targetId, key)\n ) {\n this.removeDocumentFromTarget(\n targetId,\n key,\n new NoDocument(key, snapshotVersion)\n );\n }\n }\n\n if (targetState.hasPendingChanges) {\n targetChanges.set(targetId, targetState.toTargetChange());\n targetState.clearPendingChanges();\n }\n }\n });\n\n let resolvedLimboDocuments = documentKeySet();\n\n // We extract the set of limbo-only document updates as the GC logic\n // special-cases documents that do not appear in the target cache.\n //\n // TODO(gsoltis): Expand on this comment once GC is available in the JS\n // client.\n this.pendingDocumentTargetMapping.forEach((key, targets) => {\n let isOnlyLimboTarget = true;\n\n targets.forEachWhile(targetId => {\n const targetData = this.targetDataForActiveTarget(targetId);\n if (\n targetData &&\n targetData.purpose !== TargetPurpose.LimboResolution\n ) {\n isOnlyLimboTarget = false;\n return false;\n }\n\n return true;\n });\n\n if (isOnlyLimboTarget) {\n resolvedLimboDocuments = resolvedLimboDocuments.add(key);\n }\n });\n\n const remoteEvent = new RemoteEvent(\n snapshotVersion,\n targetChanges,\n this.pendingTargetResets,\n this.pendingDocumentUpdates,\n resolvedLimboDocuments\n );\n\n this.pendingDocumentUpdates = maybeDocumentMap();\n this.pendingDocumentTargetMapping = documentTargetMap();\n this.pendingTargetResets = new SortedSet(primitiveComparator);\n\n return remoteEvent;\n }\n\n /**\n * Adds the provided document to the internal list of document updates and\n * its document key to the given target's mapping.\n */\n // Visible for testing.\n addDocumentToTarget(targetId: TargetId, document: MaybeDocument): void {\n if (!this.isActiveTarget(targetId)) {\n return;\n }\n\n const changeType = this.targetContainsDocument(targetId, document.key)\n ? ChangeType.Modified\n : ChangeType.Added;\n\n const targetState = this.ensureTargetState(targetId);\n targetState.addDocumentChange(document.key, changeType);\n\n this.pendingDocumentUpdates = this.pendingDocumentUpdates.insert(\n document.key,\n document\n );\n\n this.pendingDocumentTargetMapping = this.pendingDocumentTargetMapping.insert(\n document.key,\n this.ensureDocumentTargetMapping(document.key).add(targetId)\n );\n }\n\n /**\n * Removes the provided document from the target mapping. If the\n * document no longer matches the target, but the document's state is still\n * known (e.g. we know that the document was deleted or we received the change\n * that caused the filter mismatch), the new document can be provided\n * to update the remote document cache.\n */\n // Visible for testing.\n removeDocumentFromTarget(\n targetId: TargetId,\n key: DocumentKey,\n updatedDocument: MaybeDocument | null\n ): void {\n if (!this.isActiveTarget(targetId)) {\n return;\n }\n\n const targetState = this.ensureTargetState(targetId);\n if (this.targetContainsDocument(targetId, key)) {\n targetState.addDocumentChange(key, ChangeType.Removed);\n } else {\n // The document may have entered and left the target before we raised a\n // snapshot, so we can just ignore the change.\n targetState.removeDocumentChange(key);\n }\n\n this.pendingDocumentTargetMapping = this.pendingDocumentTargetMapping.insert(\n key,\n this.ensureDocumentTargetMapping(key).delete(targetId)\n );\n\n if (updatedDocument) {\n this.pendingDocumentUpdates = this.pendingDocumentUpdates.insert(\n key,\n updatedDocument\n );\n }\n }\n\n removeTarget(targetId: TargetId): void {\n this.targetStates.delete(targetId);\n }\n\n /**\n * Returns the current count of documents in the target. This includes both\n * the number of documents that the LocalStore considers to be part of the\n * target as well as any accumulated changes.\n */\n private getCurrentDocumentCountForTarget(targetId: TargetId): number {\n const targetState = this.ensureTargetState(targetId);\n const targetChange = targetState.toTargetChange();\n return (\n this.metadataProvider.getRemoteKeysForTarget(targetId).size +\n targetChange.addedDocuments.size -\n targetChange.removedDocuments.size\n );\n }\n\n /**\n * Increment the number of acks needed from watch before we can consider the\n * server to be 'in-sync' with the client's active targets.\n */\n recordPendingTargetRequest(targetId: TargetId): void {\n // For each request we get we need to record we need a response for it.\n const targetState = this.ensureTargetState(targetId);\n targetState.recordPendingTargetRequest();\n }\n\n private ensureTargetState(targetId: TargetId): TargetState {\n let result = this.targetStates.get(targetId);\n if (!result) {\n result = new TargetState();\n this.targetStates.set(targetId, result);\n }\n return result;\n }\n\n private ensureDocumentTargetMapping(key: DocumentKey): SortedSet {\n let targetMapping = this.pendingDocumentTargetMapping.get(key);\n\n if (!targetMapping) {\n targetMapping = new SortedSet(primitiveComparator);\n this.pendingDocumentTargetMapping = this.pendingDocumentTargetMapping.insert(\n key,\n targetMapping\n );\n }\n\n return targetMapping;\n }\n\n /**\n * Verifies that the user is still interested in this target (by calling\n * `getTargetDataForTarget()`) and that we are not waiting for pending ADDs\n * from watch.\n */\n protected isActiveTarget(targetId: TargetId): boolean {\n const targetActive = this.targetDataForActiveTarget(targetId) !== null;\n if (!targetActive) {\n logDebug(LOG_TAG, 'Detected inactive target', targetId);\n }\n return targetActive;\n }\n\n /**\n * Returns the TargetData for an active target (i.e. a target that the user\n * is still interested in that has no outstanding target change requests).\n */\n protected targetDataForActiveTarget(targetId: TargetId): TargetData | null {\n const targetState = this.targetStates.get(targetId);\n return targetState && targetState.isPending\n ? null\n : this.metadataProvider.getTargetDataForTarget(targetId);\n }\n\n /**\n * Resets the state of a Watch target to its initial state (e.g. sets\n * 'current' to false, clears the resume token and removes its target mapping\n * from all documents).\n */\n private resetTarget(targetId: TargetId): void {\n debugAssert(\n !this.targetStates.get(targetId)!.isPending,\n 'Should only reset active targets'\n );\n this.targetStates.set(targetId, new TargetState());\n\n // Trigger removal for any documents currently mapped to this target.\n // These removals will be part of the initial snapshot if Watch does not\n // resend these documents.\n const existingKeys = this.metadataProvider.getRemoteKeysForTarget(targetId);\n existingKeys.forEach(key => {\n this.removeDocumentFromTarget(targetId, key, /*updatedDocument=*/ null);\n });\n }\n /**\n * Returns whether the LocalStore considers the document to be part of the\n * specified target.\n */\n private targetContainsDocument(\n targetId: TargetId,\n key: DocumentKey\n ): boolean {\n const existingKeys = this.metadataProvider.getRemoteKeysForTarget(targetId);\n return existingKeys.has(key);\n }\n}\n\nfunction documentTargetMap(): SortedMap> {\n return new SortedMap>(\n DocumentKey.comparator\n );\n}\n\nfunction snapshotChangesMap(): SortedMap {\n return new SortedMap(DocumentKey.comparator);\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as api from '../protos/firestore_proto_api';\nimport { Timestamp } from '../api/timestamp';\nimport { normalizeTimestamp } from './values';\n\n/**\n * Represents a locally-applied ServerTimestamp.\n *\n * Server Timestamps are backed by MapValues that contain an internal field\n * `__type__` with a value of `server_timestamp`. The previous value and local\n * write time are stored in its `__previous_value__` and `__local_write_time__`\n * fields respectively.\n *\n * Notes:\n * - ServerTimestampValue instances are created as the result of applying a\n * TransformMutation (see TransformMutation.applyTo()). They can only exist in\n * the local view of a document. Therefore they do not need to be parsed or\n * serialized.\n * - When evaluated locally (e.g. for snapshot.data()), they by default\n * evaluate to `null`. This behavior can be configured by passing custom\n * FieldValueOptions to value().\n * - With respect to other ServerTimestampValues, they sort by their\n * localWriteTime.\n */\n\nconst SERVER_TIMESTAMP_SENTINEL = 'server_timestamp';\nconst TYPE_KEY = '__type__';\nconst PREVIOUS_VALUE_KEY = '__previous_value__';\nconst LOCAL_WRITE_TIME_KEY = '__local_write_time__';\n\nexport function isServerTimestamp(value: api.Value | null): boolean {\n const type = (value?.mapValue?.fields || {})[TYPE_KEY]?.stringValue;\n return type === SERVER_TIMESTAMP_SENTINEL;\n}\n\n/**\n * Creates a new ServerTimestamp proto value (using the internal format).\n */\nexport function serverTimestamp(\n localWriteTime: Timestamp,\n previousValue: api.Value | null\n): api.Value {\n const mapValue: api.MapValue = {\n fields: {\n [TYPE_KEY]: {\n stringValue: SERVER_TIMESTAMP_SENTINEL\n },\n [LOCAL_WRITE_TIME_KEY]: {\n timestampValue: {\n seconds: localWriteTime.seconds,\n nanos: localWriteTime.nanoseconds\n }\n }\n }\n };\n\n if (previousValue) {\n mapValue.fields![PREVIOUS_VALUE_KEY] = previousValue;\n }\n\n return { mapValue };\n}\n\n/**\n * Returns the value of the field before this ServerTimestamp was set.\n *\n * Preserving the previous values allows the user to display the last resoled\n * value until the backend responds with the timestamp.\n */\nexport function getPreviousValue(value: api.Value): api.Value | null {\n const previousValue = value.mapValue!.fields![PREVIOUS_VALUE_KEY];\n\n if (isServerTimestamp(previousValue)) {\n return getPreviousValue(previousValue);\n }\n return previousValue;\n}\n\n/**\n * Returns the local time at which this timestamp was first set.\n */\nexport function getLocalWriteTime(value: api.Value): Timestamp {\n const localWriteTime = normalizeTimestamp(\n value.mapValue!.fields![LOCAL_WRITE_TIME_KEY].timestampValue!\n );\n return new Timestamp(localWriteTime.seconds, localWriteTime.nanos);\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as api from '../protos/firestore_proto_api';\n\nimport { TypeOrder } from './object_value';\nimport { fail, hardAssert } from '../util/assert';\nimport { forEach, objectSize } from '../util/obj';\nimport { ByteString } from '../util/byte_string';\nimport { isNegativeZero } from '../util/types';\nimport { DocumentKey } from './document_key';\nimport { arrayEquals, primitiveComparator } from '../util/misc';\nimport { DatabaseId } from '../core/database_info';\nimport {\n getLocalWriteTime,\n getPreviousValue,\n isServerTimestamp\n} from './server_timestamps';\n\n// A RegExp matching ISO 8601 UTC timestamps with optional fraction.\nconst ISO_TIMESTAMP_REG_EXP = new RegExp(\n /^\\d{4}-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d(?:\\.(\\d+))?Z$/\n);\n\n/** Extracts the backend's type order for the provided value. */\nexport function typeOrder(value: api.Value): TypeOrder {\n if ('nullValue' in value) {\n return TypeOrder.NullValue;\n } else if ('booleanValue' in value) {\n return TypeOrder.BooleanValue;\n } else if ('integerValue' in value || 'doubleValue' in value) {\n return TypeOrder.NumberValue;\n } else if ('timestampValue' in value) {\n return TypeOrder.TimestampValue;\n } else if ('stringValue' in value) {\n return TypeOrder.StringValue;\n } else if ('bytesValue' in value) {\n return TypeOrder.BlobValue;\n } else if ('referenceValue' in value) {\n return TypeOrder.RefValue;\n } else if ('geoPointValue' in value) {\n return TypeOrder.GeoPointValue;\n } else if ('arrayValue' in value) {\n return TypeOrder.ArrayValue;\n } else if ('mapValue' in value) {\n if (isServerTimestamp(value)) {\n return TypeOrder.ServerTimestampValue;\n }\n return TypeOrder.ObjectValue;\n } else {\n return fail('Invalid value type: ' + JSON.stringify(value));\n }\n}\n\n/** Tests `left` and `right` for equality based on the backend semantics. */\nexport function valueEquals(left: api.Value, right: api.Value): boolean {\n const leftType = typeOrder(left);\n const rightType = typeOrder(right);\n if (leftType !== rightType) {\n return false;\n }\n\n switch (leftType) {\n case TypeOrder.NullValue:\n return true;\n case TypeOrder.BooleanValue:\n return left.booleanValue === right.booleanValue;\n case TypeOrder.ServerTimestampValue:\n return getLocalWriteTime(left).isEqual(getLocalWriteTime(right));\n case TypeOrder.TimestampValue:\n return timestampEquals(left, right);\n case TypeOrder.StringValue:\n return left.stringValue === right.stringValue;\n case TypeOrder.BlobValue:\n return blobEquals(left, right);\n case TypeOrder.RefValue:\n return left.referenceValue === right.referenceValue;\n case TypeOrder.GeoPointValue:\n return geoPointEquals(left, right);\n case TypeOrder.NumberValue:\n return numberEquals(left, right);\n case TypeOrder.ArrayValue:\n return arrayEquals(\n left.arrayValue!.values || [],\n right.arrayValue!.values || [],\n valueEquals\n );\n case TypeOrder.ObjectValue:\n return objectEquals(left, right);\n default:\n return fail('Unexpected value type: ' + JSON.stringify(left));\n }\n}\n\nfunction timestampEquals(left: api.Value, right: api.Value): boolean {\n if (\n typeof left.timestampValue === 'string' &&\n typeof right.timestampValue === 'string' &&\n left.timestampValue.length === right.timestampValue.length\n ) {\n // Use string equality for ISO 8601 timestamps\n return left.timestampValue === right.timestampValue;\n }\n\n const leftTimestamp = normalizeTimestamp(left.timestampValue!);\n const rightTimestamp = normalizeTimestamp(right.timestampValue!);\n return (\n leftTimestamp.seconds === rightTimestamp.seconds &&\n leftTimestamp.nanos === rightTimestamp.nanos\n );\n}\n\nfunction geoPointEquals(left: api.Value, right: api.Value): boolean {\n return (\n normalizeNumber(left.geoPointValue!.latitude) ===\n normalizeNumber(right.geoPointValue!.latitude) &&\n normalizeNumber(left.geoPointValue!.longitude) ===\n normalizeNumber(right.geoPointValue!.longitude)\n );\n}\n\nfunction blobEquals(left: api.Value, right: api.Value): boolean {\n return normalizeByteString(left.bytesValue!).isEqual(\n normalizeByteString(right.bytesValue!)\n );\n}\n\nexport function numberEquals(left: api.Value, right: api.Value): boolean {\n if ('integerValue' in left && 'integerValue' in right) {\n return (\n normalizeNumber(left.integerValue) === normalizeNumber(right.integerValue)\n );\n } else if ('doubleValue' in left && 'doubleValue' in right) {\n const n1 = normalizeNumber(left.doubleValue!);\n const n2 = normalizeNumber(right.doubleValue!);\n\n if (n1 === n2) {\n return isNegativeZero(n1) === isNegativeZero(n2);\n } else {\n return isNaN(n1) && isNaN(n2);\n }\n }\n\n return false;\n}\n\nfunction objectEquals(left: api.Value, right: api.Value): boolean {\n const leftMap = left.mapValue!.fields || {};\n const rightMap = right.mapValue!.fields || {};\n\n if (objectSize(leftMap) !== objectSize(rightMap)) {\n return false;\n }\n\n for (const key in leftMap) {\n if (leftMap.hasOwnProperty(key)) {\n if (\n rightMap[key] === undefined ||\n !valueEquals(leftMap[key], rightMap[key])\n ) {\n return false;\n }\n }\n }\n return true;\n}\n\n/** Returns true if the ArrayValue contains the specified element. */\nexport function arrayValueContains(\n haystack: api.ArrayValue,\n needle: api.Value\n): boolean {\n return (\n (haystack.values || []).find(v => valueEquals(v, needle)) !== undefined\n );\n}\n\nexport function valueCompare(left: api.Value, right: api.Value): number {\n const leftType = typeOrder(left);\n const rightType = typeOrder(right);\n\n if (leftType !== rightType) {\n return primitiveComparator(leftType, rightType);\n }\n\n switch (leftType) {\n case TypeOrder.NullValue:\n return 0;\n case TypeOrder.BooleanValue:\n return primitiveComparator(left.booleanValue!, right.booleanValue!);\n case TypeOrder.NumberValue:\n return compareNumbers(left, right);\n case TypeOrder.TimestampValue:\n return compareTimestamps(left.timestampValue!, right.timestampValue!);\n case TypeOrder.ServerTimestampValue:\n return compareTimestamps(\n getLocalWriteTime(left),\n getLocalWriteTime(right)\n );\n case TypeOrder.StringValue:\n return primitiveComparator(left.stringValue!, right.stringValue!);\n case TypeOrder.BlobValue:\n return compareBlobs(left.bytesValue!, right.bytesValue!);\n case TypeOrder.RefValue:\n return compareReferences(left.referenceValue!, right.referenceValue!);\n case TypeOrder.GeoPointValue:\n return compareGeoPoints(left.geoPointValue!, right.geoPointValue!);\n case TypeOrder.ArrayValue:\n return compareArrays(left.arrayValue!, right.arrayValue!);\n case TypeOrder.ObjectValue:\n return compareMaps(left.mapValue!, right.mapValue!);\n default:\n throw fail('Invalid value type: ' + leftType);\n }\n}\n\nfunction compareNumbers(left: api.Value, right: api.Value): number {\n const leftNumber = normalizeNumber(left.integerValue || left.doubleValue);\n const rightNumber = normalizeNumber(right.integerValue || right.doubleValue);\n\n if (leftNumber < rightNumber) {\n return -1;\n } else if (leftNumber > rightNumber) {\n return 1;\n } else if (leftNumber === rightNumber) {\n return 0;\n } else {\n // one or both are NaN.\n if (isNaN(leftNumber)) {\n return isNaN(rightNumber) ? 0 : -1;\n } else {\n return 1;\n }\n }\n}\n\nfunction compareTimestamps(left: api.Timestamp, right: api.Timestamp): number {\n if (\n typeof left === 'string' &&\n typeof right === 'string' &&\n left.length === right.length\n ) {\n return primitiveComparator(left, right);\n }\n\n const leftTimestamp = normalizeTimestamp(left);\n const rightTimestamp = normalizeTimestamp(right);\n\n const comparison = primitiveComparator(\n leftTimestamp.seconds,\n rightTimestamp.seconds\n );\n if (comparison !== 0) {\n return comparison;\n }\n return primitiveComparator(leftTimestamp.nanos, rightTimestamp.nanos);\n}\n\nfunction compareReferences(leftPath: string, rightPath: string): number {\n const leftSegments = leftPath.split('/');\n const rightSegments = rightPath.split('/');\n for (let i = 0; i < leftSegments.length && i < rightSegments.length; i++) {\n const comparison = primitiveComparator(leftSegments[i], rightSegments[i]);\n if (comparison !== 0) {\n return comparison;\n }\n }\n return primitiveComparator(leftSegments.length, rightSegments.length);\n}\n\nfunction compareGeoPoints(left: api.LatLng, right: api.LatLng): number {\n const comparison = primitiveComparator(\n normalizeNumber(left.latitude),\n normalizeNumber(right.latitude)\n );\n if (comparison !== 0) {\n return comparison;\n }\n return primitiveComparator(\n normalizeNumber(left.longitude),\n normalizeNumber(right.longitude)\n );\n}\n\nfunction compareBlobs(\n left: string | Uint8Array,\n right: string | Uint8Array\n): number {\n const leftBytes = normalizeByteString(left);\n const rightBytes = normalizeByteString(right);\n return leftBytes.compareTo(rightBytes);\n}\n\nfunction compareArrays(left: api.ArrayValue, right: api.ArrayValue): number {\n const leftArray = left.values || [];\n const rightArray = right.values || [];\n\n for (let i = 0; i < leftArray.length && i < rightArray.length; ++i) {\n const compare = valueCompare(leftArray[i], rightArray[i]);\n if (compare) {\n return compare;\n }\n }\n return primitiveComparator(leftArray.length, rightArray.length);\n}\n\nfunction compareMaps(left: api.MapValue, right: api.MapValue): number {\n const leftMap = left.fields || {};\n const leftKeys = Object.keys(leftMap);\n const rightMap = right.fields || {};\n const rightKeys = Object.keys(rightMap);\n\n // Even though MapValues are likely sorted correctly based on their insertion\n // order (e.g. when received from the backend), local modifications can bring\n // elements out of order. We need to re-sort the elements to ensure that\n // canonical IDs are independent of insertion order.\n leftKeys.sort();\n rightKeys.sort();\n\n for (let i = 0; i < leftKeys.length && i < rightKeys.length; ++i) {\n const keyCompare = primitiveComparator(leftKeys[i], rightKeys[i]);\n if (keyCompare !== 0) {\n return keyCompare;\n }\n const compare = valueCompare(leftMap[leftKeys[i]], rightMap[rightKeys[i]]);\n if (compare !== 0) {\n return compare;\n }\n }\n\n return primitiveComparator(leftKeys.length, rightKeys.length);\n}\n\n/**\n * Generates the canonical ID for the provided field value (as used in Target\n * serialization).\n */\nexport function canonicalId(value: api.Value): string {\n return canonifyValue(value);\n}\n\nfunction canonifyValue(value: api.Value): string {\n if ('nullValue' in value) {\n return 'null';\n } else if ('booleanValue' in value) {\n return '' + value.booleanValue!;\n } else if ('integerValue' in value) {\n return '' + value.integerValue!;\n } else if ('doubleValue' in value) {\n return '' + value.doubleValue!;\n } else if ('timestampValue' in value) {\n return canonifyTimestamp(value.timestampValue!);\n } else if ('stringValue' in value) {\n return value.stringValue!;\n } else if ('bytesValue' in value) {\n return canonifyByteString(value.bytesValue!);\n } else if ('referenceValue' in value) {\n return canonifyReference(value.referenceValue!);\n } else if ('geoPointValue' in value) {\n return canonifyGeoPoint(value.geoPointValue!);\n } else if ('arrayValue' in value) {\n return canonifyArray(value.arrayValue!);\n } else if ('mapValue' in value) {\n return canonifyMap(value.mapValue!);\n } else {\n return fail('Invalid value type: ' + JSON.stringify(value));\n }\n}\n\nfunction canonifyByteString(byteString: string | Uint8Array): string {\n return normalizeByteString(byteString).toBase64();\n}\n\nfunction canonifyTimestamp(timestamp: api.Timestamp): string {\n const normalizedTimestamp = normalizeTimestamp(timestamp);\n return `time(${normalizedTimestamp.seconds},${normalizedTimestamp.nanos})`;\n}\n\nfunction canonifyGeoPoint(geoPoint: api.LatLng): string {\n return `geo(${geoPoint.latitude},${geoPoint.longitude})`;\n}\n\nfunction canonifyReference(referenceValue: string): string {\n return DocumentKey.fromName(referenceValue).toString();\n}\n\nfunction canonifyMap(mapValue: api.MapValue): string {\n // Iteration order in JavaScript is not guaranteed. To ensure that we generate\n // matching canonical IDs for identical maps, we need to sort the keys.\n const sortedKeys = Object.keys(mapValue.fields || {}).sort();\n\n let result = '{';\n let first = true;\n for (const key of sortedKeys) {\n if (!first) {\n result += ',';\n } else {\n first = false;\n }\n result += `${key}:${canonifyValue(mapValue.fields![key])}`;\n }\n return result + '}';\n}\n\nfunction canonifyArray(arrayValue: api.ArrayValue): string {\n let result = '[';\n let first = true;\n for (const value of arrayValue.values || []) {\n if (!first) {\n result += ',';\n } else {\n first = false;\n }\n result += canonifyValue(value);\n }\n return result + ']';\n}\n\n/**\n * Returns an approximate (and wildly inaccurate) in-memory size for the field\n * value.\n *\n * The memory size takes into account only the actual user data as it resides\n * in memory and ignores object overhead.\n */\nexport function estimateByteSize(value: api.Value): number {\n switch (typeOrder(value)) {\n case TypeOrder.NullValue:\n return 4;\n case TypeOrder.BooleanValue:\n return 4;\n case TypeOrder.NumberValue:\n return 8;\n case TypeOrder.TimestampValue:\n // Timestamps are made up of two distinct numbers (seconds + nanoseconds)\n return 16;\n case TypeOrder.ServerTimestampValue:\n const previousValue = getPreviousValue(value);\n return previousValue ? 16 + estimateByteSize(previousValue) : 16;\n case TypeOrder.StringValue:\n // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures:\n // \"JavaScript's String type is [...] a set of elements of 16-bit unsigned\n // integer values\"\n return value.stringValue!.length * 2;\n case TypeOrder.BlobValue:\n return normalizeByteString(value.bytesValue!).approximateByteSize();\n case TypeOrder.RefValue:\n return value.referenceValue!.length;\n case TypeOrder.GeoPointValue:\n // GeoPoints are made up of two distinct numbers (latitude + longitude)\n return 16;\n case TypeOrder.ArrayValue:\n return estimateArrayByteSize(value.arrayValue!);\n case TypeOrder.ObjectValue:\n return estimateMapByteSize(value.mapValue!);\n default:\n throw fail('Invalid value type: ' + JSON.stringify(value));\n }\n}\n\nfunction estimateMapByteSize(mapValue: api.MapValue): number {\n let size = 0;\n forEach(mapValue.fields || {}, (key, val) => {\n size += key.length + estimateByteSize(val);\n });\n return size;\n}\n\nfunction estimateArrayByteSize(arrayValue: api.ArrayValue): number {\n return (arrayValue.values || []).reduce(\n (previousSize, value) => previousSize + estimateByteSize(value),\n 0\n );\n}\n\n/**\n * Converts the possible Proto values for a timestamp value into a \"seconds and\n * nanos\" representation.\n */\nexport function normalizeTimestamp(\n date: api.Timestamp\n): { seconds: number; nanos: number } {\n hardAssert(!!date, 'Cannot normalize null or undefined timestamp.');\n\n // The json interface (for the browser) will return an iso timestamp string,\n // while the proto js library (for node) will return a\n // google.protobuf.Timestamp instance.\n if (typeof date === 'string') {\n // The date string can have higher precision (nanos) than the Date class\n // (millis), so we do some custom parsing here.\n\n // Parse the nanos right out of the string.\n let nanos = 0;\n const fraction = ISO_TIMESTAMP_REG_EXP.exec(date);\n hardAssert(!!fraction, 'invalid timestamp: ' + date);\n if (fraction[1]) {\n // Pad the fraction out to 9 digits (nanos).\n let nanoStr = fraction[1];\n nanoStr = (nanoStr + '000000000').substr(0, 9);\n nanos = Number(nanoStr);\n }\n\n // Parse the date to get the seconds.\n const parsedDate = new Date(date);\n const seconds = Math.floor(parsedDate.getTime() / 1000);\n\n return { seconds, nanos };\n } else {\n // TODO(b/37282237): Use strings for Proto3 timestamps\n // assert(!this.options.useProto3Json,\n // 'The timestamp instance format requires Proto JS.');\n const seconds = normalizeNumber(date.seconds);\n const nanos = normalizeNumber(date.nanos);\n return { seconds, nanos };\n }\n}\n\n/**\n * Converts the possible Proto types for numbers into a JavaScript number.\n * Returns 0 if the value is not numeric.\n */\nexport function normalizeNumber(value: number | string | undefined): number {\n // TODO(bjornick): Handle int64 greater than 53 bits.\n if (typeof value === 'number') {\n return value;\n } else if (typeof value === 'string') {\n return Number(value);\n } else {\n return 0;\n }\n}\n\n/** Converts the possible Proto types for Blobs into a ByteString. */\nexport function normalizeByteString(blob: string | Uint8Array): ByteString {\n if (typeof blob === 'string') {\n return ByteString.fromBase64String(blob);\n } else {\n return ByteString.fromUint8Array(blob);\n }\n}\n\n/** Returns a reference value for the provided database and key. */\nexport function refValue(databaseId: DatabaseId, key: DocumentKey): api.Value {\n return {\n referenceValue: `projects/${databaseId.projectId}/databases/${\n databaseId.database\n }/documents/${key.path.canonicalString()}`\n };\n}\n\n/** Returns true if `value` is an IntegerValue . */\nexport function isInteger(\n value?: api.Value | null\n): value is { integerValue: string | number } {\n return !!value && 'integerValue' in value;\n}\n\n/** Returns true if `value` is a DoubleValue. */\nexport function isDouble(\n value?: api.Value | null\n): value is { doubleValue: string | number } {\n return !!value && 'doubleValue' in value;\n}\n\n/** Returns true if `value` is either an IntegerValue or a DoubleValue. */\nexport function isNumber(value?: api.Value | null): boolean {\n return isInteger(value) || isDouble(value);\n}\n\n/** Returns true if `value` is an ArrayValue. */\nexport function isArray(\n value?: api.Value | null\n): value is { arrayValue: api.ArrayValue } {\n return !!value && 'arrayValue' in value;\n}\n\n/** Returns true if `value` is a ReferenceValue. */\nexport function isReferenceValue(\n value?: api.Value | null\n): value is { referenceValue: string } {\n return !!value && 'referenceValue' in value;\n}\n\n/** Returns true if `value` is a NullValue. */\nexport function isNullValue(\n value?: api.Value | null\n): value is { nullValue: 'NULL_VALUE' } {\n return !!value && 'nullValue' in value;\n}\n\n/** Returns true if `value` is NaN. */\nexport function isNanValue(\n value?: api.Value | null\n): value is { doubleValue: 'NaN' | number } {\n return !!value && 'doubleValue' in value && isNaN(Number(value.doubleValue));\n}\n\n/** Returns true if `value` is a MapValue. */\nexport function isMapValue(\n value?: api.Value | null\n): value is { mapValue: api.MapValue } {\n return !!value && 'mapValue' in value;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Blob } from '../api/blob';\nimport { Timestamp } from '../api/timestamp';\nimport { DatabaseId } from '../core/database_info';\nimport {\n Bound,\n Direction,\n FieldFilter,\n Filter,\n LimitType,\n Operator,\n OrderBy,\n Query\n} from '../core/query';\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { isDocumentTarget, Target } from '../core/target';\nimport { TargetId } from '../core/types';\nimport { TargetData, TargetPurpose } from '../local/target_data';\nimport { Document, MaybeDocument, NoDocument } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport { ObjectValue } from '../model/object_value';\nimport {\n DeleteMutation,\n FieldMask,\n FieldTransform,\n Mutation,\n MutationResult,\n PatchMutation,\n Precondition,\n SetMutation,\n TransformMutation,\n VerifyMutation\n} from '../model/mutation';\nimport { FieldPath, ResourcePath } from '../model/path';\nimport * as api from '../protos/firestore_proto_api';\nimport { debugAssert, fail, hardAssert } from '../util/assert';\nimport { Code, FirestoreError } from '../util/error';\nimport { ByteString } from '../util/byte_string';\nimport {\n isNegativeZero,\n isNullOrUndefined,\n isSafeInteger\n} from '../util/types';\nimport {\n ArrayRemoveTransformOperation,\n ArrayUnionTransformOperation,\n NumericIncrementTransformOperation,\n ServerTimestampTransform,\n TransformOperation\n} from '../model/transform_operation';\nimport { ExistenceFilter } from './existence_filter';\nimport { mapCodeFromRpcCode } from './rpc_error';\nimport {\n DocumentWatchChange,\n ExistenceFilterChange,\n WatchChange,\n WatchTargetChange,\n WatchTargetChangeState\n} from './watch_change';\nimport { isNanValue, isNullValue, normalizeTimestamp } from '../model/values';\nimport {\n TargetChangeTargetChangeType,\n WriteResult\n} from '../protos/firestore_proto_api';\n\nconst DIRECTIONS = (() => {\n const dirs: { [dir: string]: api.OrderDirection } = {};\n dirs[Direction.ASCENDING] = 'ASCENDING';\n dirs[Direction.DESCENDING] = 'DESCENDING';\n return dirs;\n})();\n\nconst OPERATORS = (() => {\n const ops: { [op: string]: api.FieldFilterOp } = {};\n ops[Operator.LESS_THAN] = 'LESS_THAN';\n ops[Operator.LESS_THAN_OR_EQUAL] = 'LESS_THAN_OR_EQUAL';\n ops[Operator.GREATER_THAN] = 'GREATER_THAN';\n ops[Operator.GREATER_THAN_OR_EQUAL] = 'GREATER_THAN_OR_EQUAL';\n ops[Operator.EQUAL] = 'EQUAL';\n ops[Operator.ARRAY_CONTAINS] = 'ARRAY_CONTAINS';\n ops[Operator.IN] = 'IN';\n ops[Operator.ARRAY_CONTAINS_ANY] = 'ARRAY_CONTAINS_ANY';\n return ops;\n})();\n\nfunction assertPresent(value: unknown, description: string): asserts value {\n debugAssert(!isNullOrUndefined(value), description + ' is missing');\n}\n\n/**\n * This class generates JsonObject values for the Datastore API suitable for\n * sending to either GRPC stub methods or via the JSON/HTTP REST API.\n *\n * The serializer supports both Protobuf.js and Proto3 JSON formats. By\n * setting `useProto3Json` to true, the serializer will use the Proto3 JSON\n * format.\n *\n * For a description of the Proto3 JSON format check\n * https://developers.google.com/protocol-buffers/docs/proto3#json\n *\n * TODO(klimt): We can remove the databaseId argument if we keep the full\n * resource name in documents.\n */\nexport class JsonProtoSerializer {\n constructor(\n readonly databaseId: DatabaseId,\n readonly useProto3Json: boolean\n ) {}\n}\n\nfunction fromRpcStatus(status: api.Status): FirestoreError {\n const code =\n status.code === undefined ? Code.UNKNOWN : mapCodeFromRpcCode(status.code);\n return new FirestoreError(code, status.message || '');\n}\n\n/**\n * Returns a value for a number (or null) that's appropriate to put into\n * a google.protobuf.Int32Value proto.\n * DO NOT USE THIS FOR ANYTHING ELSE.\n * This method cheats. It's typed as returning \"number\" because that's what\n * our generated proto interfaces say Int32Value must be. But GRPC actually\n * expects a { value: } struct.\n */\nfunction toInt32Proto(\n serializer: JsonProtoSerializer,\n val: number | null\n): number | { value: number } | null {\n if (serializer.useProto3Json || isNullOrUndefined(val)) {\n return val;\n } else {\n return { value: val };\n }\n}\n\n/**\n * Returns a number (or null) from a google.protobuf.Int32Value proto.\n */\nfunction fromInt32Proto(\n val: number | { value: number } | undefined\n): number | null {\n let result;\n if (typeof val === 'object') {\n result = val.value;\n } else {\n result = val;\n }\n return isNullOrUndefined(result) ? null : result;\n}\n\n/**\n * Returns an IntegerValue for `value`.\n */\nexport function toInteger(value: number): api.Value {\n return { integerValue: '' + value };\n}\n\n/**\n * Returns an DoubleValue for `value` that is encoded based the serializer's\n * `useProto3Json` setting.\n */\nexport function toDouble(\n serializer: JsonProtoSerializer,\n value: number\n): api.Value {\n if (serializer.useProto3Json) {\n if (isNaN(value)) {\n return { doubleValue: 'NaN' };\n } else if (value === Infinity) {\n return { doubleValue: 'Infinity' };\n } else if (value === -Infinity) {\n return { doubleValue: '-Infinity' };\n }\n }\n return { doubleValue: isNegativeZero(value) ? '-0' : value };\n}\n\n/**\n * Returns a value for a number that's appropriate to put into a proto.\n * The return value is an IntegerValue if it can safely represent the value,\n * otherwise a DoubleValue is returned.\n */\nexport function toNumber(\n serializer: JsonProtoSerializer,\n value: number\n): api.Value {\n return isSafeInteger(value) ? toInteger(value) : toDouble(serializer, value);\n}\n\n/**\n * Returns a value for a Date that's appropriate to put into a proto.\n */\nexport function toTimestamp(\n serializer: JsonProtoSerializer,\n timestamp: Timestamp\n): api.Timestamp {\n if (serializer.useProto3Json) {\n // Serialize to ISO-8601 date format, but with full nano resolution.\n // Since JS Date has only millis, let's only use it for the seconds and\n // then manually add the fractions to the end.\n const jsDateStr = new Date(timestamp.seconds * 1000).toISOString();\n // Remove .xxx frac part and Z in the end.\n const strUntilSeconds = jsDateStr.replace(/\\.\\d*/, '').replace('Z', '');\n // Pad the fraction out to 9 digits (nanos).\n const nanoStr = ('000000000' + timestamp.nanoseconds).slice(-9);\n\n return `${strUntilSeconds}.${nanoStr}Z`;\n } else {\n return {\n seconds: '' + timestamp.seconds,\n nanos: timestamp.nanoseconds\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any;\n }\n}\n\nfunction fromTimestamp(date: api.Timestamp): Timestamp {\n const timestamp = normalizeTimestamp(date);\n return new Timestamp(timestamp.seconds, timestamp.nanos);\n}\n\n/**\n * Returns a value for bytes that's appropriate to put in a proto.\n *\n * Visible for testing.\n */\nexport function toBytes(\n serializer: JsonProtoSerializer,\n bytes: Blob | ByteString\n): string | Uint8Array {\n if (serializer.useProto3Json) {\n return bytes.toBase64();\n } else {\n return bytes.toUint8Array();\n }\n}\n\n/**\n * Returns a ByteString based on the proto string value.\n */\nexport function fromBytes(\n serializer: JsonProtoSerializer,\n value: string | Uint8Array | undefined\n): ByteString {\n if (serializer.useProto3Json) {\n hardAssert(\n value === undefined || typeof value === 'string',\n 'value must be undefined or a string when using proto3 Json'\n );\n return ByteString.fromBase64String(value ? value : '');\n } else {\n hardAssert(\n value === undefined || value instanceof Uint8Array,\n 'value must be undefined or Uint8Array'\n );\n return ByteString.fromUint8Array(value ? value : new Uint8Array());\n }\n}\n\nexport function toVersion(\n serializer: JsonProtoSerializer,\n version: SnapshotVersion\n): api.Timestamp {\n return toTimestamp(serializer, version.toTimestamp());\n}\n\nexport function fromVersion(version: api.Timestamp): SnapshotVersion {\n hardAssert(!!version, \"Trying to deserialize version that isn't set\");\n return SnapshotVersion.fromTimestamp(fromTimestamp(version));\n}\n\nexport function toResourceName(\n databaseId: DatabaseId,\n path: ResourcePath\n): string {\n return fullyQualifiedPrefixPath(databaseId)\n .child('documents')\n .child(path)\n .canonicalString();\n}\n\nfunction fromResourceName(name: string): ResourcePath {\n const resource = ResourcePath.fromString(name);\n hardAssert(\n isValidResourceName(resource),\n 'Tried to deserialize invalid key ' + resource.toString()\n );\n return resource;\n}\n\nexport function toName(\n serializer: JsonProtoSerializer,\n key: DocumentKey\n): string {\n return toResourceName(serializer.databaseId, key.path);\n}\n\nexport function fromName(\n serializer: JsonProtoSerializer,\n name: string\n): DocumentKey {\n const resource = fromResourceName(name);\n hardAssert(\n resource.get(1) === serializer.databaseId.projectId,\n 'Tried to deserialize key from different project: ' +\n resource.get(1) +\n ' vs ' +\n serializer.databaseId.projectId\n );\n hardAssert(\n (!resource.get(3) && !serializer.databaseId.database) ||\n resource.get(3) === serializer.databaseId.database,\n 'Tried to deserialize key from different database: ' +\n resource.get(3) +\n ' vs ' +\n serializer.databaseId.database\n );\n return new DocumentKey(extractLocalPathFromResourceName(resource));\n}\n\nfunction toQueryPath(\n serializer: JsonProtoSerializer,\n path: ResourcePath\n): string {\n return toResourceName(serializer.databaseId, path);\n}\n\nfunction fromQueryPath(name: string): ResourcePath {\n const resourceName = fromResourceName(name);\n // In v1beta1 queries for collections at the root did not have a trailing\n // \"/documents\". In v1 all resource paths contain \"/documents\". Preserve the\n // ability to read the v1beta1 form for compatibility with queries persisted\n // in the local target cache.\n if (resourceName.length === 4) {\n return ResourcePath.emptyPath();\n }\n return extractLocalPathFromResourceName(resourceName);\n}\n\nexport function getEncodedDatabaseId(serializer: JsonProtoSerializer): string {\n const path = new ResourcePath([\n 'projects',\n serializer.databaseId.projectId,\n 'databases',\n serializer.databaseId.database\n ]);\n return path.canonicalString();\n}\n\nfunction fullyQualifiedPrefixPath(databaseId: DatabaseId): ResourcePath {\n return new ResourcePath([\n 'projects',\n databaseId.projectId,\n 'databases',\n databaseId.database\n ]);\n}\n\nfunction extractLocalPathFromResourceName(\n resourceName: ResourcePath\n): ResourcePath {\n hardAssert(\n resourceName.length > 4 && resourceName.get(4) === 'documents',\n 'tried to deserialize invalid key ' + resourceName.toString()\n );\n return resourceName.popFirst(5);\n}\n\n/** Creates an api.Document from key and fields (but no create/update time) */\nexport function toMutationDocument(\n serializer: JsonProtoSerializer,\n key: DocumentKey,\n fields: ObjectValue\n): api.Document {\n return {\n name: toName(serializer, key),\n fields: fields.proto.mapValue.fields\n };\n}\n\nexport function toDocument(\n serializer: JsonProtoSerializer,\n document: Document\n): api.Document {\n debugAssert(\n !document.hasLocalMutations,\n \"Can't serialize documents with mutations.\"\n );\n return {\n name: toName(serializer, document.key),\n fields: document.toProto().mapValue.fields,\n updateTime: toTimestamp(serializer, document.version.toTimestamp())\n };\n}\n\nexport function fromDocument(\n serializer: JsonProtoSerializer,\n document: api.Document,\n hasCommittedMutations?: boolean\n): Document {\n const key = fromName(serializer, document.name!);\n const version = fromVersion(document.updateTime!);\n const data = new ObjectValue({ mapValue: { fields: document.fields } });\n return new Document(key, version, data, {\n hasCommittedMutations: !!hasCommittedMutations\n });\n}\n\nfunction fromFound(\n serializer: JsonProtoSerializer,\n doc: api.BatchGetDocumentsResponse\n): Document {\n hardAssert(\n !!doc.found,\n 'Tried to deserialize a found document from a missing document.'\n );\n assertPresent(doc.found.name, 'doc.found.name');\n assertPresent(doc.found.updateTime, 'doc.found.updateTime');\n const key = fromName(serializer, doc.found.name);\n const version = fromVersion(doc.found.updateTime);\n const data = new ObjectValue({ mapValue: { fields: doc.found.fields } });\n return new Document(key, version, data, {});\n}\n\nfunction fromMissing(\n serializer: JsonProtoSerializer,\n result: api.BatchGetDocumentsResponse\n): NoDocument {\n hardAssert(\n !!result.missing,\n 'Tried to deserialize a missing document from a found document.'\n );\n hardAssert(\n !!result.readTime,\n 'Tried to deserialize a missing document without a read time.'\n );\n const key = fromName(serializer, result.missing);\n const version = fromVersion(result.readTime);\n return new NoDocument(key, version);\n}\n\nexport function fromMaybeDocument(\n serializer: JsonProtoSerializer,\n result: api.BatchGetDocumentsResponse\n): MaybeDocument {\n if ('found' in result) {\n return fromFound(serializer, result);\n } else if ('missing' in result) {\n return fromMissing(serializer, result);\n }\n return fail('invalid batch get response: ' + JSON.stringify(result));\n}\n\nexport function fromWatchChange(\n serializer: JsonProtoSerializer,\n change: api.ListenResponse\n): WatchChange {\n let watchChange: WatchChange;\n if ('targetChange' in change) {\n assertPresent(change.targetChange, 'targetChange');\n // proto3 default value is unset in JSON (undefined), so use 'NO_CHANGE'\n // if unset\n const state = fromWatchTargetChangeState(\n change.targetChange.targetChangeType || 'NO_CHANGE'\n );\n const targetIds: TargetId[] = change.targetChange.targetIds || [];\n\n const resumeToken = fromBytes(serializer, change.targetChange.resumeToken);\n const causeProto = change.targetChange!.cause;\n const cause = causeProto && fromRpcStatus(causeProto);\n watchChange = new WatchTargetChange(\n state,\n targetIds,\n resumeToken,\n cause || null\n );\n } else if ('documentChange' in change) {\n assertPresent(change.documentChange, 'documentChange');\n const entityChange = change.documentChange;\n assertPresent(entityChange.document, 'documentChange.name');\n assertPresent(entityChange.document.name, 'documentChange.document.name');\n assertPresent(\n entityChange.document.updateTime,\n 'documentChange.document.updateTime'\n );\n const key = fromName(serializer, entityChange.document.name);\n const version = fromVersion(entityChange.document.updateTime);\n const data = new ObjectValue({\n mapValue: { fields: entityChange.document.fields }\n });\n const doc = new Document(key, version, data, {});\n const updatedTargetIds = entityChange.targetIds || [];\n const removedTargetIds = entityChange.removedTargetIds || [];\n watchChange = new DocumentWatchChange(\n updatedTargetIds,\n removedTargetIds,\n doc.key,\n doc\n );\n } else if ('documentDelete' in change) {\n assertPresent(change.documentDelete, 'documentDelete');\n const docDelete = change.documentDelete;\n assertPresent(docDelete.document, 'documentDelete.document');\n const key = fromName(serializer, docDelete.document);\n const version = docDelete.readTime\n ? fromVersion(docDelete.readTime)\n : SnapshotVersion.min();\n const doc = new NoDocument(key, version);\n const removedTargetIds = docDelete.removedTargetIds || [];\n watchChange = new DocumentWatchChange([], removedTargetIds, doc.key, doc);\n } else if ('documentRemove' in change) {\n assertPresent(change.documentRemove, 'documentRemove');\n const docRemove = change.documentRemove;\n assertPresent(docRemove.document, 'documentRemove');\n const key = fromName(serializer, docRemove.document);\n const removedTargetIds = docRemove.removedTargetIds || [];\n watchChange = new DocumentWatchChange([], removedTargetIds, key, null);\n } else if ('filter' in change) {\n // TODO(dimond): implement existence filter parsing with strategy.\n assertPresent(change.filter, 'filter');\n const filter = change.filter;\n assertPresent(filter.targetId, 'filter.targetId');\n const count = filter.count || 0;\n const existenceFilter = new ExistenceFilter(count);\n const targetId = filter.targetId;\n watchChange = new ExistenceFilterChange(targetId, existenceFilter);\n } else {\n return fail('Unknown change type ' + JSON.stringify(change));\n }\n return watchChange;\n}\n\nfunction fromWatchTargetChangeState(\n state: TargetChangeTargetChangeType\n): WatchTargetChangeState {\n if (state === 'NO_CHANGE') {\n return WatchTargetChangeState.NoChange;\n } else if (state === 'ADD') {\n return WatchTargetChangeState.Added;\n } else if (state === 'REMOVE') {\n return WatchTargetChangeState.Removed;\n } else if (state === 'CURRENT') {\n return WatchTargetChangeState.Current;\n } else if (state === 'RESET') {\n return WatchTargetChangeState.Reset;\n } else {\n return fail('Got unexpected TargetChange.state: ' + state);\n }\n}\n\nexport function versionFromListenResponse(\n change: api.ListenResponse\n): SnapshotVersion {\n // We have only reached a consistent snapshot for the entire stream if there\n // is a read_time set and it applies to all targets (i.e. the list of\n // targets is empty). The backend is guaranteed to send such responses.\n if (!('targetChange' in change)) {\n return SnapshotVersion.min();\n }\n const targetChange = change.targetChange!;\n if (targetChange.targetIds && targetChange.targetIds.length) {\n return SnapshotVersion.min();\n }\n if (!targetChange.readTime) {\n return SnapshotVersion.min();\n }\n return fromVersion(targetChange.readTime);\n}\n\nexport function toMutation(\n serializer: JsonProtoSerializer,\n mutation: Mutation\n): api.Write {\n let result: api.Write;\n if (mutation instanceof SetMutation) {\n result = {\n update: toMutationDocument(serializer, mutation.key, mutation.value)\n };\n } else if (mutation instanceof DeleteMutation) {\n result = { delete: toName(serializer, mutation.key) };\n } else if (mutation instanceof PatchMutation) {\n result = {\n update: toMutationDocument(serializer, mutation.key, mutation.data),\n updateMask: toDocumentMask(mutation.fieldMask)\n };\n } else if (mutation instanceof TransformMutation) {\n result = {\n transform: {\n document: toName(serializer, mutation.key),\n fieldTransforms: mutation.fieldTransforms.map(transform =>\n toFieldTransform(serializer, transform)\n )\n }\n };\n } else if (mutation instanceof VerifyMutation) {\n result = {\n verify: toName(serializer, mutation.key)\n };\n } else {\n return fail('Unknown mutation type ' + mutation.type);\n }\n\n if (!mutation.precondition.isNone) {\n result.currentDocument = toPrecondition(serializer, mutation.precondition);\n }\n\n return result;\n}\n\nexport function fromMutation(\n serializer: JsonProtoSerializer,\n proto: api.Write\n): Mutation {\n const precondition = proto.currentDocument\n ? fromPrecondition(proto.currentDocument)\n : Precondition.none();\n\n if (proto.update) {\n assertPresent(proto.update.name, 'name');\n const key = fromName(serializer, proto.update.name);\n const value = new ObjectValue({\n mapValue: { fields: proto.update.fields }\n });\n if (proto.updateMask) {\n const fieldMask = fromDocumentMask(proto.updateMask);\n return new PatchMutation(key, value, fieldMask, precondition);\n } else {\n return new SetMutation(key, value, precondition);\n }\n } else if (proto.delete) {\n const key = fromName(serializer, proto.delete);\n return new DeleteMutation(key, precondition);\n } else if (proto.transform) {\n const key = fromName(serializer, proto.transform.document!);\n const fieldTransforms = proto.transform.fieldTransforms!.map(transform =>\n fromFieldTransform(serializer, transform)\n );\n hardAssert(\n precondition.exists === true,\n 'Transforms only support precondition \"exists == true\"'\n );\n return new TransformMutation(key, fieldTransforms);\n } else if (proto.verify) {\n const key = fromName(serializer, proto.verify);\n return new VerifyMutation(key, precondition);\n } else {\n return fail('unknown mutation proto: ' + JSON.stringify(proto));\n }\n}\n\nfunction toPrecondition(\n serializer: JsonProtoSerializer,\n precondition: Precondition\n): api.Precondition {\n debugAssert(!precondition.isNone, \"Can't serialize an empty precondition\");\n if (precondition.updateTime !== undefined) {\n return {\n updateTime: toVersion(serializer, precondition.updateTime)\n };\n } else if (precondition.exists !== undefined) {\n return { exists: precondition.exists };\n } else {\n return fail('Unknown precondition');\n }\n}\n\nfunction fromPrecondition(precondition: api.Precondition): Precondition {\n if (precondition.updateTime !== undefined) {\n return Precondition.updateTime(fromVersion(precondition.updateTime));\n } else if (precondition.exists !== undefined) {\n return Precondition.exists(precondition.exists);\n } else {\n return Precondition.none();\n }\n}\n\nfunction fromWriteResult(\n proto: WriteResult,\n commitTime: api.Timestamp\n): MutationResult {\n // NOTE: Deletes don't have an updateTime.\n let version = proto.updateTime\n ? fromVersion(proto.updateTime)\n : fromVersion(commitTime);\n\n if (version.isEqual(SnapshotVersion.min())) {\n // The Firestore Emulator currently returns an update time of 0 for\n // deletes of non-existing documents (rather than null). This breaks the\n // test \"get deleted doc while offline with source=cache\" as NoDocuments\n // with version 0 are filtered by IndexedDb's RemoteDocumentCache.\n // TODO(#2149): Remove this when Emulator is fixed\n version = fromVersion(commitTime);\n }\n\n let transformResults: api.Value[] | null = null;\n if (proto.transformResults && proto.transformResults.length > 0) {\n transformResults = proto.transformResults;\n }\n return new MutationResult(version, transformResults);\n}\n\nexport function fromWriteResults(\n protos: WriteResult[] | undefined,\n commitTime?: api.Timestamp\n): MutationResult[] {\n if (protos && protos.length > 0) {\n hardAssert(\n commitTime !== undefined,\n 'Received a write result without a commit time'\n );\n return protos.map(proto => fromWriteResult(proto, commitTime));\n } else {\n return [];\n }\n}\n\nfunction toFieldTransform(\n serializer: JsonProtoSerializer,\n fieldTransform: FieldTransform\n): api.FieldTransform {\n const transform = fieldTransform.transform;\n if (transform instanceof ServerTimestampTransform) {\n return {\n fieldPath: fieldTransform.field.canonicalString(),\n setToServerValue: 'REQUEST_TIME'\n };\n } else if (transform instanceof ArrayUnionTransformOperation) {\n return {\n fieldPath: fieldTransform.field.canonicalString(),\n appendMissingElements: {\n values: transform.elements\n }\n };\n } else if (transform instanceof ArrayRemoveTransformOperation) {\n return {\n fieldPath: fieldTransform.field.canonicalString(),\n removeAllFromArray: {\n values: transform.elements\n }\n };\n } else if (transform instanceof NumericIncrementTransformOperation) {\n return {\n fieldPath: fieldTransform.field.canonicalString(),\n increment: transform.operand\n };\n } else {\n throw fail('Unknown transform: ' + fieldTransform.transform);\n }\n}\n\nfunction fromFieldTransform(\n serializer: JsonProtoSerializer,\n proto: api.FieldTransform\n): FieldTransform {\n let transform: TransformOperation | null = null;\n if ('setToServerValue' in proto) {\n hardAssert(\n proto.setToServerValue === 'REQUEST_TIME',\n 'Unknown server value transform proto: ' + JSON.stringify(proto)\n );\n transform = new ServerTimestampTransform();\n } else if ('appendMissingElements' in proto) {\n const values = proto.appendMissingElements!.values || [];\n transform = new ArrayUnionTransformOperation(values);\n } else if ('removeAllFromArray' in proto) {\n const values = proto.removeAllFromArray!.values || [];\n transform = new ArrayRemoveTransformOperation(values);\n } else if ('increment' in proto) {\n transform = new NumericIncrementTransformOperation(\n serializer,\n proto.increment!\n );\n } else {\n fail('Unknown transform proto: ' + JSON.stringify(proto));\n }\n const fieldPath = FieldPath.fromServerFormat(proto.fieldPath!);\n return new FieldTransform(fieldPath, transform!);\n}\n\nexport function toDocumentsTarget(\n serializer: JsonProtoSerializer,\n target: Target\n): api.DocumentsTarget {\n return { documents: [toQueryPath(serializer, target.path)] };\n}\n\nexport function fromDocumentsTarget(\n documentsTarget: api.DocumentsTarget\n): Target {\n const count = documentsTarget.documents!.length;\n hardAssert(\n count === 1,\n 'DocumentsTarget contained other than 1 document: ' + count\n );\n const name = documentsTarget.documents![0];\n return Query.atPath(fromQueryPath(name)).toTarget();\n}\n\nexport function toQueryTarget(\n serializer: JsonProtoSerializer,\n target: Target\n): api.QueryTarget {\n // Dissect the path into parent, collectionId, and optional key filter.\n const result: api.QueryTarget = { structuredQuery: {} };\n const path = target.path;\n if (target.collectionGroup !== null) {\n debugAssert(\n path.length % 2 === 0,\n 'Collection Group queries should be within a document path or root.'\n );\n result.parent = toQueryPath(serializer, path);\n result.structuredQuery!.from = [\n {\n collectionId: target.collectionGroup,\n allDescendants: true\n }\n ];\n } else {\n debugAssert(\n path.length % 2 !== 0,\n 'Document queries with filters are not supported.'\n );\n result.parent = toQueryPath(serializer, path.popLast());\n result.structuredQuery!.from = [{ collectionId: path.lastSegment() }];\n }\n\n const where = toFilter(target.filters);\n if (where) {\n result.structuredQuery!.where = where;\n }\n\n const orderBy = toOrder(target.orderBy);\n if (orderBy) {\n result.structuredQuery!.orderBy = orderBy;\n }\n\n const limit = toInt32Proto(serializer, target.limit);\n if (limit !== null) {\n result.structuredQuery!.limit = limit;\n }\n\n if (target.startAt) {\n result.structuredQuery!.startAt = toCursor(target.startAt);\n }\n if (target.endAt) {\n result.structuredQuery!.endAt = toCursor(target.endAt);\n }\n\n return result;\n}\n\nexport function fromQueryTarget(target: api.QueryTarget): Target {\n let path = fromQueryPath(target.parent!);\n\n const query = target.structuredQuery!;\n const fromCount = query.from ? query.from.length : 0;\n let collectionGroup: string | null = null;\n if (fromCount > 0) {\n hardAssert(\n fromCount === 1,\n 'StructuredQuery.from with more than one collection is not supported.'\n );\n const from = query.from![0];\n if (from.allDescendants) {\n collectionGroup = from.collectionId!;\n } else {\n path = path.child(from.collectionId!);\n }\n }\n\n let filterBy: Filter[] = [];\n if (query.where) {\n filterBy = fromFilter(query.where);\n }\n\n let orderBy: OrderBy[] = [];\n if (query.orderBy) {\n orderBy = fromOrder(query.orderBy);\n }\n\n let limit: number | null = null;\n if (query.limit) {\n limit = fromInt32Proto(query.limit);\n }\n\n let startAt: Bound | null = null;\n if (query.startAt) {\n startAt = fromCursor(query.startAt);\n }\n\n let endAt: Bound | null = null;\n if (query.endAt) {\n endAt = fromCursor(query.endAt);\n }\n\n return new Query(\n path,\n collectionGroup,\n orderBy,\n filterBy,\n limit,\n LimitType.First,\n startAt,\n endAt\n ).toTarget();\n}\n\nexport function toListenRequestLabels(\n serializer: JsonProtoSerializer,\n targetData: TargetData\n): api.ApiClientObjectMap | null {\n const value = toLabel(serializer, targetData.purpose);\n if (value == null) {\n return null;\n } else {\n return {\n 'goog-listen-tags': value\n };\n }\n}\n\nfunction toLabel(\n serializer: JsonProtoSerializer,\n purpose: TargetPurpose\n): string | null {\n switch (purpose) {\n case TargetPurpose.Listen:\n return null;\n case TargetPurpose.ExistenceFilterMismatch:\n return 'existence-filter-mismatch';\n case TargetPurpose.LimboResolution:\n return 'limbo-document';\n default:\n return fail('Unrecognized query purpose: ' + purpose);\n }\n}\n\nexport function toTarget(\n serializer: JsonProtoSerializer,\n targetData: TargetData\n): api.Target {\n let result: api.Target;\n const target = targetData.target;\n\n if (isDocumentTarget(target)) {\n result = { documents: toDocumentsTarget(serializer, target) };\n } else {\n result = { query: toQueryTarget(serializer, target) };\n }\n\n result.targetId = targetData.targetId;\n\n if (targetData.resumeToken.approximateByteSize() > 0) {\n result.resumeToken = toBytes(serializer, targetData.resumeToken);\n }\n\n return result;\n}\n\nfunction toFilter(filters: Filter[]): api.Filter | undefined {\n if (filters.length === 0) {\n return;\n }\n const protos = filters.map(filter => {\n if (filter instanceof FieldFilter) {\n return toUnaryOrFieldFilter(filter);\n } else {\n return fail('Unrecognized filter: ' + JSON.stringify(filter));\n }\n });\n if (protos.length === 1) {\n return protos[0];\n }\n return { compositeFilter: { op: 'AND', filters: protos } };\n}\n\nfunction fromFilter(filter: api.Filter | undefined): Filter[] {\n if (!filter) {\n return [];\n } else if (filter.unaryFilter !== undefined) {\n return [fromUnaryFilter(filter)];\n } else if (filter.fieldFilter !== undefined) {\n return [fromFieldFilter(filter)];\n } else if (filter.compositeFilter !== undefined) {\n return filter.compositeFilter\n .filters!.map(f => fromFilter(f))\n .reduce((accum, current) => accum.concat(current));\n } else {\n return fail('Unknown filter: ' + JSON.stringify(filter));\n }\n}\n\nfunction toOrder(orderBys: OrderBy[]): api.Order[] | undefined {\n if (orderBys.length === 0) {\n return;\n }\n return orderBys.map(order => toPropertyOrder(order));\n}\n\nfunction fromOrder(orderBys: api.Order[]): OrderBy[] {\n return orderBys.map(order => fromPropertyOrder(order));\n}\n\nfunction toCursor(cursor: Bound): api.Cursor {\n return {\n before: cursor.before,\n values: cursor.position\n };\n}\n\nfunction fromCursor(cursor: api.Cursor): Bound {\n const before = !!cursor.before;\n const position = cursor.values || [];\n return new Bound(position, before);\n}\n\n// visible for testing\nexport function toDirection(dir: Direction): api.OrderDirection {\n return DIRECTIONS[dir];\n}\n\n// visible for testing\nexport function fromDirection(\n dir: api.OrderDirection | undefined\n): Direction | undefined {\n switch (dir) {\n case 'ASCENDING':\n return Direction.ASCENDING;\n case 'DESCENDING':\n return Direction.DESCENDING;\n default:\n return undefined;\n }\n}\n\n// visible for testing\nexport function toOperatorName(op: Operator): api.FieldFilterOp {\n return OPERATORS[op];\n}\n\nexport function fromOperatorName(op: api.FieldFilterOp): Operator {\n switch (op) {\n case 'EQUAL':\n return Operator.EQUAL;\n case 'GREATER_THAN':\n return Operator.GREATER_THAN;\n case 'GREATER_THAN_OR_EQUAL':\n return Operator.GREATER_THAN_OR_EQUAL;\n case 'LESS_THAN':\n return Operator.LESS_THAN;\n case 'LESS_THAN_OR_EQUAL':\n return Operator.LESS_THAN_OR_EQUAL;\n case 'ARRAY_CONTAINS':\n return Operator.ARRAY_CONTAINS;\n case 'IN':\n return Operator.IN;\n case 'ARRAY_CONTAINS_ANY':\n return Operator.ARRAY_CONTAINS_ANY;\n case 'OPERATOR_UNSPECIFIED':\n return fail('Unspecified operator');\n default:\n return fail('Unknown operator');\n }\n}\n\nexport function toFieldPathReference(path: FieldPath): api.FieldReference {\n return { fieldPath: path.canonicalString() };\n}\n\nexport function fromFieldPathReference(\n fieldReference: api.FieldReference\n): FieldPath {\n return FieldPath.fromServerFormat(fieldReference.fieldPath!);\n}\n\n// visible for testing\nexport function toPropertyOrder(orderBy: OrderBy): api.Order {\n return {\n field: toFieldPathReference(orderBy.field),\n direction: toDirection(orderBy.dir)\n };\n}\n\nexport function fromPropertyOrder(orderBy: api.Order): OrderBy {\n return new OrderBy(\n fromFieldPathReference(orderBy.field!),\n fromDirection(orderBy.direction)\n );\n}\n\nexport function fromFieldFilter(filter: api.Filter): Filter {\n return FieldFilter.create(\n fromFieldPathReference(filter.fieldFilter!.field!),\n fromOperatorName(filter.fieldFilter!.op!),\n filter.fieldFilter!.value!\n );\n}\n\n// visible for testing\nexport function toUnaryOrFieldFilter(filter: FieldFilter): api.Filter {\n if (filter.op === Operator.EQUAL) {\n if (isNanValue(filter.value)) {\n return {\n unaryFilter: {\n field: toFieldPathReference(filter.field),\n op: 'IS_NAN'\n }\n };\n } else if (isNullValue(filter.value)) {\n return {\n unaryFilter: {\n field: toFieldPathReference(filter.field),\n op: 'IS_NULL'\n }\n };\n }\n }\n return {\n fieldFilter: {\n field: toFieldPathReference(filter.field),\n op: toOperatorName(filter.op),\n value: filter.value\n }\n };\n}\n\nexport function fromUnaryFilter(filter: api.Filter): Filter {\n switch (filter.unaryFilter!.op!) {\n case 'IS_NAN':\n const nanField = fromFieldPathReference(filter.unaryFilter!.field!);\n return FieldFilter.create(nanField, Operator.EQUAL, {\n doubleValue: NaN\n });\n case 'IS_NULL':\n const nullField = fromFieldPathReference(filter.unaryFilter!.field!);\n return FieldFilter.create(nullField, Operator.EQUAL, {\n nullValue: 'NULL_VALUE'\n });\n case 'OPERATOR_UNSPECIFIED':\n return fail('Unspecified filter');\n default:\n return fail('Unknown filter');\n }\n}\n\nexport function toDocumentMask(fieldMask: FieldMask): api.DocumentMask {\n const canonicalFields: string[] = [];\n fieldMask.fields.forEach(field =>\n canonicalFields.push(field.canonicalString())\n );\n return {\n fieldPaths: canonicalFields\n };\n}\n\nexport function fromDocumentMask(proto: api.DocumentMask): FieldMask {\n const paths = proto.fieldPaths || [];\n return new FieldMask(paths.map(path => FieldPath.fromServerFormat(path)));\n}\n\nexport function isValidResourceName(path: ResourcePath): boolean {\n // Resource names have at least 4 components (project ID, database ID)\n return (\n path.length >= 4 &&\n path.get(0) === 'projects' &&\n path.get(2) === 'databases'\n );\n}\n","/**\n * @license\n * Copyright 2018 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as api from '../protos/firestore_proto_api';\n\nimport { Timestamp } from '../api/timestamp';\nimport { debugAssert } from '../util/assert';\nimport { JsonProtoSerializer, toDouble, toInteger } from '../remote/serializer';\nimport {\n isArray,\n isInteger,\n isNumber,\n normalizeNumber,\n valueEquals\n} from './values';\nimport { serverTimestamp } from './server_timestamps';\nimport { arrayEquals } from '../util/misc';\n\n/** Represents a transform within a TransformMutation. */\nexport class TransformOperation {\n // Make sure that the structural type of `TransformOperation` is unique.\n // See https://github.com/microsoft/TypeScript/issues/5451\n private _ = undefined;\n}\n\n/**\n * Computes the local transform result against the provided `previousValue`,\n * optionally using the provided localWriteTime.\n */\nexport function applyTransformOperationToLocalView(\n transform: TransformOperation,\n previousValue: api.Value | null,\n localWriteTime: Timestamp\n): api.Value {\n if (transform instanceof ServerTimestampTransform) {\n return serverTimestamp(localWriteTime, previousValue);\n } else if (transform instanceof ArrayUnionTransformOperation) {\n return applyArrayUnionTransformOperation(transform, previousValue);\n } else if (transform instanceof ArrayRemoveTransformOperation) {\n return applyArrayRemoveTransformOperation(transform, previousValue);\n } else {\n debugAssert(\n transform instanceof NumericIncrementTransformOperation,\n 'Expected NumericIncrementTransformOperation but was: ' + transform\n );\n return applyNumericIncrementTransformOperationToLocalView(\n transform,\n previousValue\n );\n }\n}\n\n/**\n * Computes a final transform result after the transform has been acknowledged\n * by the server, potentially using the server-provided transformResult.\n */\nexport function applyTransformOperationToRemoteDocument(\n transform: TransformOperation,\n previousValue: api.Value | null,\n transformResult: api.Value | null\n): api.Value {\n // The server just sends null as the transform result for array operations,\n // so we have to calculate a result the same as we do for local\n // applications.\n if (transform instanceof ArrayUnionTransformOperation) {\n return applyArrayUnionTransformOperation(transform, previousValue);\n } else if (transform instanceof ArrayRemoveTransformOperation) {\n return applyArrayRemoveTransformOperation(transform, previousValue);\n }\n\n debugAssert(\n transformResult !== null,\n \"Didn't receive transformResult for non-array transform\"\n );\n return transformResult;\n}\n\n/**\n * If this transform operation is not idempotent, returns the base value to\n * persist for this transform. If a base value is returned, the transform\n * operation is always applied to this base value, even if document has\n * already been updated.\n *\n * Base values provide consistent behavior for non-idempotent transforms and\n * allow us to return the same latency-compensated value even if the backend\n * has already applied the transform operation. The base value is null for\n * idempotent transforms, as they can be re-played even if the backend has\n * already applied them.\n *\n * @return a base value to store along with the mutation, or null for\n * idempotent transforms.\n */\nexport function computeTransformOperationBaseValue(\n transform: TransformOperation,\n previousValue: api.Value | null\n): api.Value | null {\n if (transform instanceof NumericIncrementTransformOperation) {\n return isNumber(previousValue) ? previousValue! : { integerValue: 0 };\n }\n return null;\n}\n\nexport function transformOperationEquals(\n left: TransformOperation,\n right: TransformOperation\n): boolean {\n if (\n left instanceof ArrayUnionTransformOperation &&\n right instanceof ArrayUnionTransformOperation\n ) {\n return arrayEquals(left.elements, right.elements, valueEquals);\n } else if (\n left instanceof ArrayRemoveTransformOperation &&\n right instanceof ArrayRemoveTransformOperation\n ) {\n return arrayEquals(left.elements, right.elements, valueEquals);\n } else if (\n left instanceof NumericIncrementTransformOperation &&\n right instanceof NumericIncrementTransformOperation\n ) {\n return valueEquals(left.operand, right.operand);\n }\n\n return (\n left instanceof ServerTimestampTransform &&\n right instanceof ServerTimestampTransform\n );\n}\n\n/** Transforms a value into a server-generated timestamp. */\nexport class ServerTimestampTransform extends TransformOperation {}\n\n/** Transforms an array value via a union operation. */\nexport class ArrayUnionTransformOperation extends TransformOperation {\n constructor(readonly elements: api.Value[]) {\n super();\n }\n}\n\nfunction applyArrayUnionTransformOperation(\n transform: ArrayUnionTransformOperation,\n previousValue: api.Value | null\n): api.Value {\n const values = coercedFieldValuesArray(previousValue);\n for (const toUnion of transform.elements) {\n if (!values.some(element => valueEquals(element, toUnion))) {\n values.push(toUnion);\n }\n }\n return { arrayValue: { values } };\n}\n\n/** Transforms an array value via a remove operation. */\nexport class ArrayRemoveTransformOperation extends TransformOperation {\n constructor(readonly elements: api.Value[]) {\n super();\n }\n}\n\nfunction applyArrayRemoveTransformOperation(\n transform: ArrayRemoveTransformOperation,\n previousValue: api.Value | null\n): api.Value {\n let values = coercedFieldValuesArray(previousValue);\n for (const toRemove of transform.elements) {\n values = values.filter(element => !valueEquals(element, toRemove));\n }\n return { arrayValue: { values } };\n}\n\n/**\n * Implements the backend semantics for locally computed NUMERIC_ADD (increment)\n * transforms. Converts all field values to integers or doubles, but unlike the\n * backend does not cap integer values at 2^63. Instead, JavaScript number\n * arithmetic is used and precision loss can occur for values greater than 2^53.\n */\nexport class NumericIncrementTransformOperation extends TransformOperation {\n constructor(\n readonly serializer: JsonProtoSerializer,\n readonly operand: api.Value\n ) {\n super();\n debugAssert(\n isNumber(operand),\n 'NumericIncrementTransform transform requires a NumberValue'\n );\n }\n}\n\nexport function applyNumericIncrementTransformOperationToLocalView(\n transform: NumericIncrementTransformOperation,\n previousValue: api.Value | null\n): api.Value {\n // PORTING NOTE: Since JavaScript's integer arithmetic is limited to 53 bit\n // precision and resolves overflows by reducing precision, we do not\n // manually cap overflows at 2^63.\n const baseValue = computeTransformOperationBaseValue(\n transform,\n previousValue\n )!;\n const sum = asNumber(baseValue) + asNumber(transform.operand);\n if (isInteger(baseValue) && isInteger(transform.operand)) {\n return toInteger(sum);\n } else {\n return toDouble(transform.serializer, sum);\n }\n}\n\nfunction asNumber(value: api.Value): number {\n return normalizeNumber(value.integerValue || value.doubleValue);\n}\n\nfunction coercedFieldValuesArray(value: api.Value | null): api.Value[] {\n return isArray(value) && value.arrayValue.values\n ? value.arrayValue.values.slice()\n : [];\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as api from '../protos/firestore_proto_api';\n\nimport { Timestamp } from '../api/timestamp';\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { debugAssert, hardAssert } from '../util/assert';\n\nimport {\n Document,\n MaybeDocument,\n NoDocument,\n UnknownDocument\n} from './document';\nimport { DocumentKey } from './document_key';\nimport { ObjectValue, ObjectValueBuilder } from './object_value';\nimport { FieldPath } from './path';\nimport {\n applyTransformOperationToLocalView,\n applyTransformOperationToRemoteDocument,\n computeTransformOperationBaseValue,\n TransformOperation,\n transformOperationEquals\n} from './transform_operation';\nimport { arrayEquals } from '../util/misc';\n\n/**\n * Provides a set of fields that can be used to partially patch a document.\n * FieldMask is used in conjunction with ObjectValue.\n * Examples:\n * foo - Overwrites foo entirely with the provided value. If foo is not\n * present in the companion ObjectValue, the field is deleted.\n * foo.bar - Overwrites only the field bar of the object foo.\n * If foo is not an object, foo is replaced with an object\n * containing foo\n */\nexport class FieldMask {\n constructor(readonly fields: FieldPath[]) {\n // TODO(dimond): validation of FieldMask\n // Sort the field mask to support `FieldMask.isEqual()` and assert below.\n fields.sort(FieldPath.comparator);\n debugAssert(\n !fields.some((v, i) => i !== 0 && v.isEqual(fields[i - 1])),\n 'FieldMask contains field that is not unique: ' +\n fields.find((v, i) => i !== 0 && v.isEqual(fields[i - 1]))!\n );\n }\n\n /**\n * Verifies that `fieldPath` is included by at least one field in this field\n * mask.\n *\n * This is an O(n) operation, where `n` is the size of the field mask.\n */\n covers(fieldPath: FieldPath): boolean {\n for (const fieldMaskPath of this.fields) {\n if (fieldMaskPath.isPrefixOf(fieldPath)) {\n return true;\n }\n }\n return false;\n }\n\n isEqual(other: FieldMask): boolean {\n return arrayEquals(this.fields, other.fields, (l, r) => l.isEqual(r));\n }\n}\n\n/** A field path and the TransformOperation to perform upon it. */\nexport class FieldTransform {\n constructor(\n readonly field: FieldPath,\n readonly transform: TransformOperation\n ) {}\n}\n\nexport function fieldTransformEquals(\n left: FieldTransform,\n right: FieldTransform\n): boolean {\n return (\n left.field.isEqual(right.field) &&\n transformOperationEquals(left.transform, right.transform)\n );\n}\n\n/** The result of successfully applying a mutation to the backend. */\nexport class MutationResult {\n constructor(\n /**\n * The version at which the mutation was committed:\n *\n * - For most operations, this is the updateTime in the WriteResult.\n * - For deletes, the commitTime of the WriteResponse (because deletes are\n * not stored and have no updateTime).\n *\n * Note that these versions can be different: No-op writes will not change\n * the updateTime even though the commitTime advances.\n */\n readonly version: SnapshotVersion,\n /**\n * The resulting fields returned from the backend after a\n * TransformMutation has been committed. Contains one FieldValue for each\n * FieldTransform that was in the mutation.\n *\n * Will be null if the mutation was not a TransformMutation.\n */\n readonly transformResults: Array | null\n ) {}\n}\n\nexport const enum MutationType {\n Set,\n Patch,\n Transform,\n Delete,\n Verify\n}\n\n/**\n * Encodes a precondition for a mutation. This follows the model that the\n * backend accepts with the special case of an explicit \"empty\" precondition\n * (meaning no precondition).\n */\nexport class Precondition {\n private constructor(\n readonly updateTime?: SnapshotVersion,\n readonly exists?: boolean\n ) {\n debugAssert(\n updateTime === undefined || exists === undefined,\n 'Precondition can specify \"exists\" or \"updateTime\" but not both'\n );\n }\n\n /** Creates a new empty Precondition. */\n static none(): Precondition {\n return new Precondition();\n }\n\n /** Creates a new Precondition with an exists flag. */\n static exists(exists: boolean): Precondition {\n return new Precondition(undefined, exists);\n }\n\n /** Creates a new Precondition based on a version a document exists at. */\n static updateTime(version: SnapshotVersion): Precondition {\n return new Precondition(version);\n }\n\n /** Returns whether this Precondition is empty. */\n get isNone(): boolean {\n return this.updateTime === undefined && this.exists === undefined;\n }\n\n isEqual(other: Precondition): boolean {\n return (\n this.exists === other.exists &&\n (this.updateTime\n ? !!other.updateTime && this.updateTime.isEqual(other.updateTime)\n : !other.updateTime)\n );\n }\n}\n\n/**\n * Returns true if the preconditions is valid for the given document\n * (or null if no document is available).\n */\nexport function preconditionIsValidForDocument(\n precondition: Precondition,\n maybeDoc: MaybeDocument | null\n): boolean {\n if (precondition.updateTime !== undefined) {\n return (\n maybeDoc instanceof Document &&\n maybeDoc.version.isEqual(precondition.updateTime)\n );\n } else if (precondition.exists !== undefined) {\n return precondition.exists === maybeDoc instanceof Document;\n } else {\n debugAssert(precondition.isNone, 'Precondition should be empty');\n return true;\n }\n}\n\n/**\n * A mutation describes a self-contained change to a document. Mutations can\n * create, replace, delete, and update subsets of documents.\n *\n * Mutations not only act on the value of the document but also its version.\n *\n * For local mutations (mutations that haven't been committed yet), we preserve\n * the existing version for Set, Patch, and Transform mutations. For Delete\n * mutations, we reset the version to 0.\n *\n * Here's the expected transition table.\n *\n * MUTATION APPLIED TO RESULTS IN\n *\n * SetMutation Document(v3) Document(v3)\n * SetMutation NoDocument(v3) Document(v0)\n * SetMutation null Document(v0)\n * PatchMutation Document(v3) Document(v3)\n * PatchMutation NoDocument(v3) NoDocument(v3)\n * PatchMutation null null\n * TransformMutation Document(v3) Document(v3)\n * TransformMutation NoDocument(v3) NoDocument(v3)\n * TransformMutation null null\n * DeleteMutation Document(v3) NoDocument(v0)\n * DeleteMutation NoDocument(v3) NoDocument(v0)\n * DeleteMutation null NoDocument(v0)\n *\n * For acknowledged mutations, we use the updateTime of the WriteResponse as\n * the resulting version for Set, Patch, and Transform mutations. As deletes\n * have no explicit update time, we use the commitTime of the WriteResponse for\n * Delete mutations.\n *\n * If a mutation is acknowledged by the backend but fails the precondition check\n * locally, we return an `UnknownDocument` and rely on Watch to send us the\n * updated version.\n *\n * Note that TransformMutations don't create Documents (in the case of being\n * applied to a NoDocument), even though they would on the backend. This is\n * because the client always combines the TransformMutation with a SetMutation\n * or PatchMutation and we only want to apply the transform if the prior\n * mutation resulted in a Document (always true for a SetMutation, but not\n * necessarily for a PatchMutation).\n *\n * ## Subclassing Notes\n *\n * Subclasses of Mutation need to implement applyToRemoteDocument() and\n * applyToLocalView() to implement the actual behavior of applying the mutation\n * to some source document.\n */\nexport abstract class Mutation {\n abstract readonly type: MutationType;\n abstract readonly key: DocumentKey;\n abstract readonly precondition: Precondition;\n}\n\n/**\n * Applies this mutation to the given MaybeDocument or null for the purposes\n * of computing a new remote document. If the input document doesn't match the\n * expected state (e.g. it is null or outdated), an `UnknownDocument` can be\n * returned.\n *\n * @param mutation The mutation to apply.\n * @param maybeDoc The document to mutate. The input document can be null if\n * the client has no knowledge of the pre-mutation state of the document.\n * @param mutationResult The result of applying the mutation from the backend.\n * @return The mutated document. The returned document may be an\n * UnknownDocument if the mutation could not be applied to the locally\n * cached base document.\n */\nexport function applyMutationToRemoteDocument(\n mutation: Mutation,\n maybeDoc: MaybeDocument | null,\n mutationResult: MutationResult\n): MaybeDocument {\n verifyMutationKeyMatches(mutation, maybeDoc);\n if (mutation instanceof SetMutation) {\n return applySetMutationToRemoteDocument(mutation, maybeDoc, mutationResult);\n } else if (mutation instanceof PatchMutation) {\n return applyPatchMutationToRemoteDocument(\n mutation,\n maybeDoc,\n mutationResult\n );\n } else if (mutation instanceof TransformMutation) {\n return applyTransformMutationToRemoteDocument(\n mutation,\n maybeDoc,\n mutationResult\n );\n } else {\n debugAssert(\n mutation instanceof DeleteMutation,\n 'Unexpected mutation type: ' + mutation\n );\n return applyDeleteMutationToRemoteDocument(\n mutation,\n maybeDoc,\n mutationResult\n );\n }\n}\n\n/**\n * Applies this mutation to the given MaybeDocument or null for the purposes\n * of computing the new local view of a document. Both the input and returned\n * documents can be null.\n *\n * @param mutation The mutation to apply.\n * @param maybeDoc The document to mutate. The input document can be null if\n * the client has no knowledge of the pre-mutation state of the document.\n * @param baseDoc The state of the document prior to this mutation batch. The\n * input document can be null if the client has no knowledge of the\n * pre-mutation state of the document.\n * @param localWriteTime A timestamp indicating the local write time of the\n * batch this mutation is a part of.\n * @return The mutated document. The returned document may be null, but only\n * if maybeDoc was null and the mutation would not create a new document.\n */\nexport function applyMutationToLocalView(\n mutation: Mutation,\n maybeDoc: MaybeDocument | null,\n baseDoc: MaybeDocument | null,\n localWriteTime: Timestamp\n): MaybeDocument | null {\n verifyMutationKeyMatches(mutation, maybeDoc);\n\n if (mutation instanceof SetMutation) {\n return applySetMutationToLocalView(mutation, maybeDoc);\n } else if (mutation instanceof PatchMutation) {\n return applyPatchMutationToLocalView(mutation, maybeDoc);\n } else if (mutation instanceof TransformMutation) {\n return applyTransformMutationToLocalView(\n mutation,\n maybeDoc,\n localWriteTime,\n baseDoc\n );\n } else {\n debugAssert(\n mutation instanceof DeleteMutation,\n 'Unexpected mutation type: ' + mutation\n );\n return applyDeleteMutationToLocalView(mutation, maybeDoc);\n }\n}\n\n/**\n * If this mutation is not idempotent, returns the base value to persist with\n * this mutation. If a base value is returned, the mutation is always applied\n * to this base value, even if document has already been updated.\n *\n * The base value is a sparse object that consists of only the document\n * fields for which this mutation contains a non-idempotent transformation\n * (e.g. a numeric increment). The provided value guarantees consistent\n * behavior for non-idempotent transforms and allow us to return the same\n * latency-compensated value even if the backend has already applied the\n * mutation. The base value is null for idempotent mutations, as they can be\n * re-played even if the backend has already applied them.\n *\n * @return a base value to store along with the mutation, or null for\n * idempotent mutations.\n */\nexport function extractMutationBaseValue(\n mutation: Mutation,\n maybeDoc: MaybeDocument | null\n): ObjectValue | null {\n if (mutation instanceof TransformMutation) {\n return extractTransformMutationBaseValue(mutation, maybeDoc);\n }\n return null;\n}\n\nexport function mutationEquals(left: Mutation, right: Mutation): boolean {\n if (left.type !== right.type) {\n return false;\n }\n\n if (!left.key.isEqual(right.key)) {\n return false;\n }\n\n if (!left.precondition.isEqual(right.precondition)) {\n return false;\n }\n\n if (left.type === MutationType.Set) {\n return (left as SetMutation).value.isEqual((right as SetMutation).value);\n }\n\n if (left.type === MutationType.Patch) {\n return (\n (left as PatchMutation).data.isEqual((right as PatchMutation).data) &&\n (left as PatchMutation).fieldMask.isEqual(\n (right as PatchMutation).fieldMask\n )\n );\n }\n\n if (left.type === MutationType.Transform) {\n return arrayEquals(\n (left as TransformMutation).fieldTransforms,\n (left as TransformMutation).fieldTransforms,\n (l, r) => fieldTransformEquals(l, r)\n );\n }\n\n return true;\n}\n\nfunction verifyMutationKeyMatches(\n mutation: Mutation,\n maybeDoc: MaybeDocument | null\n): void {\n if (maybeDoc != null) {\n debugAssert(\n maybeDoc.key.isEqual(mutation.key),\n 'Can only apply a mutation to a document with the same key'\n );\n }\n}\n\n/**\n * Returns the version from the given document for use as the result of a\n * mutation. Mutations are defined to return the version of the base document\n * only if it is an existing document. Deleted and unknown documents have a\n * post-mutation version of SnapshotVersion.min().\n */\nfunction getPostMutationVersion(\n maybeDoc: MaybeDocument | null\n): SnapshotVersion {\n if (maybeDoc instanceof Document) {\n return maybeDoc.version;\n } else {\n return SnapshotVersion.min();\n }\n}\n\n/**\n * A mutation that creates or replaces the document at the given key with the\n * object value contents.\n */\nexport class SetMutation extends Mutation {\n constructor(\n readonly key: DocumentKey,\n readonly value: ObjectValue,\n readonly precondition: Precondition\n ) {\n super();\n }\n\n readonly type: MutationType = MutationType.Set;\n}\n\nfunction applySetMutationToRemoteDocument(\n mutation: SetMutation,\n maybeDoc: MaybeDocument | null,\n mutationResult: MutationResult\n): Document {\n debugAssert(\n mutationResult.transformResults == null,\n 'Transform results received by SetMutation.'\n );\n\n // Unlike applySetMutationToLocalView, if we're applying a mutation to a\n // remote document the server has accepted the mutation so the precondition\n // must have held.\n return new Document(mutation.key, mutationResult.version, mutation.value, {\n hasCommittedMutations: true\n });\n}\n\nfunction applySetMutationToLocalView(\n mutation: SetMutation,\n maybeDoc: MaybeDocument | null\n): MaybeDocument | null {\n if (!preconditionIsValidForDocument(mutation.precondition, maybeDoc)) {\n return maybeDoc;\n }\n\n const version = getPostMutationVersion(maybeDoc);\n return new Document(mutation.key, version, mutation.value, {\n hasLocalMutations: true\n });\n}\n\n/**\n * A mutation that modifies fields of the document at the given key with the\n * given values. The values are applied through a field mask:\n *\n * * When a field is in both the mask and the values, the corresponding field\n * is updated.\n * * When a field is in neither the mask nor the values, the corresponding\n * field is unmodified.\n * * When a field is in the mask but not in the values, the corresponding field\n * is deleted.\n * * When a field is not in the mask but is in the values, the values map is\n * ignored.\n */\nexport class PatchMutation extends Mutation {\n constructor(\n readonly key: DocumentKey,\n readonly data: ObjectValue,\n readonly fieldMask: FieldMask,\n readonly precondition: Precondition\n ) {\n super();\n }\n\n readonly type: MutationType = MutationType.Patch;\n}\n\nfunction applyPatchMutationToRemoteDocument(\n mutation: PatchMutation,\n maybeDoc: MaybeDocument | null,\n mutationResult: MutationResult\n): MaybeDocument {\n debugAssert(\n mutationResult.transformResults == null,\n 'Transform results received by PatchMutation.'\n );\n\n if (!preconditionIsValidForDocument(mutation.precondition, maybeDoc)) {\n // Since the mutation was not rejected, we know that the precondition\n // matched on the backend. We therefore must not have the expected version\n // of the document in our cache and return an UnknownDocument with the\n // known updateTime.\n return new UnknownDocument(mutation.key, mutationResult.version);\n }\n\n const newData = patchDocument(mutation, maybeDoc);\n return new Document(mutation.key, mutationResult.version, newData, {\n hasCommittedMutations: true\n });\n}\n\nfunction applyPatchMutationToLocalView(\n mutation: PatchMutation,\n maybeDoc: MaybeDocument | null\n): MaybeDocument | null {\n if (!preconditionIsValidForDocument(mutation.precondition, maybeDoc)) {\n return maybeDoc;\n }\n\n const version = getPostMutationVersion(maybeDoc);\n const newData = patchDocument(mutation, maybeDoc);\n return new Document(mutation.key, version, newData, {\n hasLocalMutations: true\n });\n}\n\n/**\n * Patches the data of document if available or creates a new document. Note\n * that this does not check whether or not the precondition of this patch\n * holds.\n */\nfunction patchDocument(\n mutation: PatchMutation,\n maybeDoc: MaybeDocument | null\n): ObjectValue {\n let data: ObjectValue;\n if (maybeDoc instanceof Document) {\n data = maybeDoc.data();\n } else {\n data = ObjectValue.empty();\n }\n return patchObject(mutation, data);\n}\n\nfunction patchObject(mutation: PatchMutation, data: ObjectValue): ObjectValue {\n const builder = new ObjectValueBuilder(data);\n mutation.fieldMask.fields.forEach(fieldPath => {\n if (!fieldPath.isEmpty()) {\n const newValue = mutation.data.field(fieldPath);\n if (newValue !== null) {\n builder.set(fieldPath, newValue);\n } else {\n builder.delete(fieldPath);\n }\n }\n });\n return builder.build();\n}\n\n/**\n * A mutation that modifies specific fields of the document with transform\n * operations. Currently the only supported transform is a server timestamp, but\n * IP Address, increment(n), etc. could be supported in the future.\n *\n * It is somewhat similar to a PatchMutation in that it patches specific fields\n * and has no effect when applied to a null or NoDocument (see comment on\n * Mutation for rationale).\n */\nexport class TransformMutation extends Mutation {\n readonly type: MutationType = MutationType.Transform;\n\n // NOTE: We set a precondition of exists: true as a safety-check, since we\n // always combine TransformMutations with a SetMutation or PatchMutation which\n // (if successful) should end up with an existing document.\n readonly precondition = Precondition.exists(true);\n\n constructor(\n readonly key: DocumentKey,\n readonly fieldTransforms: FieldTransform[]\n ) {\n super();\n }\n}\n\nfunction applyTransformMutationToRemoteDocument(\n mutation: TransformMutation,\n maybeDoc: MaybeDocument | null,\n mutationResult: MutationResult\n): Document | UnknownDocument {\n hardAssert(\n mutationResult.transformResults != null,\n 'Transform results missing for TransformMutation.'\n );\n\n if (!preconditionIsValidForDocument(mutation.precondition, maybeDoc)) {\n // Since the mutation was not rejected, we know that the precondition\n // matched on the backend. We therefore must not have the expected version\n // of the document in our cache and return an UnknownDocument with the\n // known updateTime.\n return new UnknownDocument(mutation.key, mutationResult.version);\n }\n\n const doc = requireDocument(mutation, maybeDoc);\n const transformResults = serverTransformResults(\n mutation.fieldTransforms,\n maybeDoc,\n mutationResult.transformResults!\n );\n\n const version = mutationResult.version;\n const newData = transformObject(mutation, doc.data(), transformResults);\n return new Document(mutation.key, version, newData, {\n hasCommittedMutations: true\n });\n}\n\nfunction applyTransformMutationToLocalView(\n mutation: TransformMutation,\n maybeDoc: MaybeDocument | null,\n localWriteTime: Timestamp,\n baseDoc: MaybeDocument | null\n): MaybeDocument | null {\n if (!preconditionIsValidForDocument(mutation.precondition, maybeDoc)) {\n return maybeDoc;\n }\n\n const doc = requireDocument(mutation, maybeDoc);\n const transformResults = localTransformResults(\n mutation.fieldTransforms,\n localWriteTime,\n maybeDoc,\n baseDoc\n );\n const newData = transformObject(mutation, doc.data(), transformResults);\n return new Document(mutation.key, doc.version, newData, {\n hasLocalMutations: true\n });\n}\n\nfunction extractTransformMutationBaseValue(\n mutation: TransformMutation,\n maybeDoc: MaybeDocument | null | Document\n): ObjectValue | null {\n let baseObject: ObjectValueBuilder | null = null;\n for (const fieldTransform of mutation.fieldTransforms) {\n const existingValue =\n maybeDoc instanceof Document\n ? maybeDoc.field(fieldTransform.field)\n : undefined;\n const coercedValue = computeTransformOperationBaseValue(\n fieldTransform.transform,\n existingValue || null\n );\n\n if (coercedValue != null) {\n if (baseObject == null) {\n baseObject = new ObjectValueBuilder().set(\n fieldTransform.field,\n coercedValue\n );\n } else {\n baseObject = baseObject.set(fieldTransform.field, coercedValue);\n }\n }\n }\n return baseObject ? baseObject.build() : null;\n}\n\n/**\n * Asserts that the given MaybeDocument is actually a Document and verifies\n * that it matches the key for this mutation. Since we only support\n * transformations with precondition exists this method is guaranteed to be\n * safe.\n */\nfunction requireDocument(\n mutation: Mutation,\n maybeDoc: MaybeDocument | null\n): Document {\n debugAssert(\n maybeDoc instanceof Document,\n 'Unknown MaybeDocument type ' + maybeDoc\n );\n debugAssert(\n maybeDoc.key.isEqual(mutation.key),\n 'Can only transform a document with the same key'\n );\n return maybeDoc;\n}\n\n/**\n * Creates a list of \"transform results\" (a transform result is a field value\n * representing the result of applying a transform) for use after a\n * TransformMutation has been acknowledged by the server.\n *\n * @param fieldTransforms The field transforms to apply the result to.\n * @param baseDoc The document prior to applying this mutation batch.\n * @param serverTransformResults The transform results received by the server.\n * @return The transform results list.\n */\nfunction serverTransformResults(\n fieldTransforms: FieldTransform[],\n baseDoc: MaybeDocument | null,\n serverTransformResults: Array\n): api.Value[] {\n const transformResults: api.Value[] = [];\n hardAssert(\n fieldTransforms.length === serverTransformResults.length,\n `server transform result count (${serverTransformResults.length}) ` +\n `should match field transform count (${fieldTransforms.length})`\n );\n\n for (let i = 0; i < serverTransformResults.length; i++) {\n const fieldTransform = fieldTransforms[i];\n const transform = fieldTransform.transform;\n let previousValue: api.Value | null = null;\n if (baseDoc instanceof Document) {\n previousValue = baseDoc.field(fieldTransform.field);\n }\n transformResults.push(\n applyTransformOperationToRemoteDocument(\n transform,\n previousValue,\n serverTransformResults[i]\n )\n );\n }\n return transformResults;\n}\n\n/**\n * Creates a list of \"transform results\" (a transform result is a field value\n * representing the result of applying a transform) for use when applying a\n * TransformMutation locally.\n *\n * @param fieldTransforms The field transforms to apply the result to.\n * @param localWriteTime The local time of the transform mutation (used to\n * generate ServerTimestampValues).\n * @param maybeDoc The current state of the document after applying all\n * previous mutations.\n * @param baseDoc The document prior to applying this mutation batch.\n * @return The transform results list.\n */\nfunction localTransformResults(\n fieldTransforms: FieldTransform[],\n localWriteTime: Timestamp,\n maybeDoc: MaybeDocument | null,\n baseDoc: MaybeDocument | null\n): api.Value[] {\n const transformResults: api.Value[] = [];\n for (const fieldTransform of fieldTransforms) {\n const transform = fieldTransform.transform;\n\n let previousValue: api.Value | null = null;\n if (maybeDoc instanceof Document) {\n previousValue = maybeDoc.field(fieldTransform.field);\n }\n\n if (previousValue === null && baseDoc instanceof Document) {\n // If the current document does not contain a value for the mutated\n // field, use the value that existed before applying this mutation\n // batch. This solves an edge case where a PatchMutation clears the\n // values in a nested map before the TransformMutation is applied.\n previousValue = baseDoc.field(fieldTransform.field);\n }\n\n transformResults.push(\n applyTransformOperationToLocalView(\n transform,\n previousValue,\n localWriteTime\n )\n );\n }\n return transformResults;\n}\n\nfunction transformObject(\n mutation: TransformMutation,\n data: ObjectValue,\n transformResults: api.Value[]\n): ObjectValue {\n debugAssert(\n transformResults.length === mutation.fieldTransforms.length,\n 'TransformResults length mismatch.'\n );\n\n const builder = new ObjectValueBuilder(data);\n for (let i = 0; i < mutation.fieldTransforms.length; i++) {\n const fieldTransform = mutation.fieldTransforms[i];\n builder.set(fieldTransform.field, transformResults[i]);\n }\n return builder.build();\n}\n\n/** A mutation that deletes the document at the given key. */\nexport class DeleteMutation extends Mutation {\n constructor(readonly key: DocumentKey, readonly precondition: Precondition) {\n super();\n }\n\n readonly type: MutationType = MutationType.Delete;\n}\n\nfunction applyDeleteMutationToRemoteDocument(\n mutation: DeleteMutation,\n maybeDoc: MaybeDocument | null,\n mutationResult: MutationResult\n): NoDocument {\n debugAssert(\n mutationResult.transformResults == null,\n 'Transform results received by DeleteMutation.'\n );\n\n // Unlike applyToLocalView, if we're applying a mutation to a remote\n // document the server has accepted the mutation so the precondition must\n // have held.\n\n return new NoDocument(mutation.key, mutationResult.version, {\n hasCommittedMutations: true\n });\n}\n\nfunction applyDeleteMutationToLocalView(\n mutation: DeleteMutation,\n maybeDoc: MaybeDocument | null\n): MaybeDocument | null {\n if (!preconditionIsValidForDocument(mutation.precondition, maybeDoc)) {\n return maybeDoc;\n }\n\n if (maybeDoc) {\n debugAssert(\n maybeDoc.key.isEqual(mutation.key),\n 'Can only apply mutation to document with same key'\n );\n }\n return new NoDocument(mutation.key, SnapshotVersion.min());\n}\n\n/**\n * A mutation that verifies the existence of the document at the given key with\n * the provided precondition.\n *\n * The `verify` operation is only used in Transactions, and this class serves\n * primarily to facilitate serialization into protos.\n */\nexport class VerifyMutation extends Mutation {\n constructor(readonly key: DocumentKey, readonly precondition: Precondition) {\n super();\n }\n\n readonly type: MutationType = MutationType.Verify;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as api from '../protos/firestore_proto_api';\n\nimport { debugAssert } from '../util/assert';\nimport { FieldMask } from './mutation';\nimport { FieldPath } from './path';\nimport { isServerTimestamp } from './server_timestamps';\nimport { valueEquals, isMapValue, typeOrder } from './values';\nimport { forEach } from '../util/obj';\n\nexport interface JsonObject {\n [name: string]: T;\n}\n\nexport const enum TypeOrder {\n // This order is based on the backend's ordering, but modified to support\n // server timestamps.\n NullValue = 0,\n BooleanValue = 1,\n NumberValue = 2,\n TimestampValue = 3,\n ServerTimestampValue = 4,\n StringValue = 5,\n BlobValue = 6,\n RefValue = 7,\n GeoPointValue = 8,\n ArrayValue = 9,\n ObjectValue = 10\n}\n\n/**\n * An ObjectValue represents a MapValue in the Firestore Proto and offers the\n * ability to add and remove fields (via the ObjectValueBuilder).\n */\nexport class ObjectValue {\n constructor(readonly proto: { mapValue: api.MapValue }) {\n debugAssert(\n !isServerTimestamp(proto),\n 'ServerTimestamps should be converted to ServerTimestampValue'\n );\n }\n\n static empty(): ObjectValue {\n return new ObjectValue({ mapValue: {} });\n }\n\n /**\n * Returns the value at the given path or null.\n *\n * @param path the path to search\n * @return The value at the path or if there it doesn't exist.\n */\n field(path: FieldPath): api.Value | null {\n if (path.isEmpty()) {\n return this.proto;\n } else {\n let value: api.Value = this.proto;\n for (let i = 0; i < path.length - 1; ++i) {\n if (!value.mapValue!.fields) {\n return null;\n }\n value = value.mapValue!.fields[path.get(i)];\n if (!isMapValue(value)) {\n return null;\n }\n }\n\n value = (value.mapValue!.fields || {})[path.lastSegment()];\n return value || null;\n }\n }\n\n isEqual(other: ObjectValue): boolean {\n return valueEquals(this.proto, other.proto);\n }\n}\n\n/**\n * An Overlay, which contains an update to apply. Can either be Value proto, a\n * map of Overlay values (to represent additional nesting at the given key) or\n * `null` (to represent field deletes).\n */\ntype Overlay = Map | api.Value | null;\n\n/**\n * An ObjectValueBuilder provides APIs to set and delete fields from an\n * ObjectValue.\n */\nexport class ObjectValueBuilder {\n /** A map that contains the accumulated changes in this builder. */\n private overlayMap = new Map();\n\n /**\n * @param baseObject The object to mutate.\n */\n constructor(private readonly baseObject: ObjectValue = ObjectValue.empty()) {}\n\n /**\n * Sets the field to the provided value.\n *\n * @param path The field path to set.\n * @param value The value to set.\n * @return The current Builder instance.\n */\n set(path: FieldPath, value: api.Value): ObjectValueBuilder {\n debugAssert(\n !path.isEmpty(),\n 'Cannot set field for empty path on ObjectValue'\n );\n this.setOverlay(path, value);\n return this;\n }\n\n /**\n * Removes the field at the specified path. If there is no field at the\n * specified path, nothing is changed.\n *\n * @param path The field path to remove.\n * @return The current Builder instance.\n */\n delete(path: FieldPath): ObjectValueBuilder {\n debugAssert(\n !path.isEmpty(),\n 'Cannot delete field for empty path on ObjectValue'\n );\n this.setOverlay(path, null);\n return this;\n }\n\n /**\n * Adds `value` to the overlay map at `path`. Creates nested map entries if\n * needed.\n */\n private setOverlay(path: FieldPath, value: api.Value | null): void {\n let currentLevel = this.overlayMap;\n\n for (let i = 0; i < path.length - 1; ++i) {\n const currentSegment = path.get(i);\n let currentValue = currentLevel.get(currentSegment);\n\n if (currentValue instanceof Map) {\n // Re-use a previously created map\n currentLevel = currentValue;\n } else if (\n currentValue &&\n typeOrder(currentValue) === TypeOrder.ObjectValue\n ) {\n // Convert the existing Protobuf MapValue into a map\n currentValue = new Map(\n Object.entries(currentValue.mapValue!.fields || {})\n );\n currentLevel.set(currentSegment, currentValue);\n currentLevel = currentValue;\n } else {\n // Create an empty map to represent the current nesting level\n currentValue = new Map();\n currentLevel.set(currentSegment, currentValue);\n currentLevel = currentValue;\n }\n }\n\n currentLevel.set(path.lastSegment(), value);\n }\n\n /** Returns an ObjectValue with all mutations applied. */\n build(): ObjectValue {\n const mergedResult = this.applyOverlay(\n FieldPath.emptyPath(),\n this.overlayMap\n );\n if (mergedResult != null) {\n return new ObjectValue(mergedResult);\n } else {\n return this.baseObject;\n }\n }\n\n /**\n * Applies any overlays from `currentOverlays` that exist at `currentPath`\n * and returns the merged data at `currentPath` (or null if there were no\n * changes).\n *\n * @param currentPath The path at the current nesting level. Can be set to\n * FieldValue.emptyPath() to represent the root.\n * @param currentOverlays The overlays at the current nesting level in the\n * same format as `overlayMap`.\n * @return The merged data at `currentPath` or null if no modifications\n * were applied.\n */\n private applyOverlay(\n currentPath: FieldPath,\n currentOverlays: Map\n ): { mapValue: api.MapValue } | null {\n let modified = false;\n\n const existingValue = this.baseObject.field(currentPath);\n const resultAtPath = isMapValue(existingValue)\n ? // If there is already data at the current path, base our\n // modifications on top of the existing data.\n { ...existingValue.mapValue.fields }\n : {};\n\n currentOverlays.forEach((value, pathSegment) => {\n if (value instanceof Map) {\n const nested = this.applyOverlay(currentPath.child(pathSegment), value);\n if (nested != null) {\n resultAtPath[pathSegment] = nested;\n modified = true;\n }\n } else if (value !== null) {\n resultAtPath[pathSegment] = value;\n modified = true;\n } else if (resultAtPath.hasOwnProperty(pathSegment)) {\n delete resultAtPath[pathSegment];\n modified = true;\n }\n });\n\n return modified ? { mapValue: { fields: resultAtPath } } : null;\n }\n}\n\n/**\n * Returns a FieldMask built from all fields in a MapValue.\n */\nexport function extractFieldMask(value: api.MapValue): FieldMask {\n const fields: FieldPath[] = [];\n forEach(value!.fields || {}, (key, value) => {\n const currentPath = new FieldPath([key]);\n if (isMapValue(value)) {\n const nestedMask = extractFieldMask(value.mapValue!);\n const nestedFields = nestedMask.fields;\n if (nestedFields.length === 0) {\n // Preserve the empty map by adding it to the FieldMask.\n fields.push(currentPath);\n } else {\n // For nested and non-empty ObjectValues, add the FieldPath of the\n // leaf nodes.\n for (const nestedPath of nestedFields) {\n fields.push(currentPath.child(nestedPath));\n }\n }\n } else {\n // For nested and non-empty ObjectValues, add the FieldPath of the leaf\n // nodes.\n fields.push(currentPath);\n }\n });\n return new FieldMask(fields);\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as api from '../protos/firestore_proto_api';\n\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { fail } from '../util/assert';\n\nimport { DocumentKey } from './document_key';\nimport { ObjectValue } from './object_value';\nimport { FieldPath } from './path';\nimport { valueCompare } from './values';\n\nexport interface DocumentOptions {\n hasLocalMutations?: boolean;\n hasCommittedMutations?: boolean;\n}\n\n/**\n * The result of a lookup for a given path may be an existing document or a\n * marker that this document does not exist at a given version.\n */\nexport abstract class MaybeDocument {\n constructor(readonly key: DocumentKey, readonly version: SnapshotVersion) {}\n\n /**\n * Whether this document had a local mutation applied that has not yet been\n * acknowledged by Watch.\n */\n abstract get hasPendingWrites(): boolean;\n\n abstract isEqual(other: MaybeDocument | null | undefined): boolean;\n\n abstract toString(): string;\n}\n\n/**\n * Represents a document in Firestore with a key, version, data and whether the\n * data has local mutations applied to it.\n */\nexport class Document extends MaybeDocument {\n readonly hasLocalMutations: boolean;\n readonly hasCommittedMutations: boolean;\n\n constructor(\n key: DocumentKey,\n version: SnapshotVersion,\n private readonly objectValue: ObjectValue,\n options: DocumentOptions\n ) {\n super(key, version);\n this.hasLocalMutations = !!options.hasLocalMutations;\n this.hasCommittedMutations = !!options.hasCommittedMutations;\n }\n\n field(path: FieldPath): api.Value | null {\n return this.objectValue.field(path);\n }\n\n data(): ObjectValue {\n return this.objectValue;\n }\n\n toProto(): { mapValue: api.MapValue } {\n return this.objectValue.proto;\n }\n\n isEqual(other: MaybeDocument | null | undefined): boolean {\n return (\n other instanceof Document &&\n this.key.isEqual(other.key) &&\n this.version.isEqual(other.version) &&\n this.hasLocalMutations === other.hasLocalMutations &&\n this.hasCommittedMutations === other.hasCommittedMutations &&\n this.objectValue.isEqual(other.objectValue)\n );\n }\n\n toString(): string {\n return (\n `Document(${this.key}, ${\n this.version\n }, ${this.objectValue.toString()}, ` +\n `{hasLocalMutations: ${this.hasLocalMutations}}), ` +\n `{hasCommittedMutations: ${this.hasCommittedMutations}})`\n );\n }\n\n get hasPendingWrites(): boolean {\n return this.hasLocalMutations || this.hasCommittedMutations;\n }\n}\n\n/**\n * Compares the value for field `field` in the provided documents. Throws if\n * the field does not exist in both documents.\n */\nexport function compareDocumentsByField(\n field: FieldPath,\n d1: Document,\n d2: Document\n): number {\n const v1 = d1.field(field);\n const v2 = d2.field(field);\n if (v1 !== null && v2 !== null) {\n return valueCompare(v1, v2);\n } else {\n return fail(\"Trying to compare documents on fields that don't exist\");\n }\n}\n\n/**\n * A class representing a deleted document.\n * Version is set to 0 if we don't point to any specific time, otherwise it\n * denotes time we know it didn't exist at.\n */\nexport class NoDocument extends MaybeDocument {\n readonly hasCommittedMutations: boolean;\n\n constructor(\n key: DocumentKey,\n version: SnapshotVersion,\n options?: DocumentOptions\n ) {\n super(key, version);\n this.hasCommittedMutations = !!(options && options.hasCommittedMutations);\n }\n\n toString(): string {\n return `NoDocument(${this.key}, ${this.version})`;\n }\n\n get hasPendingWrites(): boolean {\n return this.hasCommittedMutations;\n }\n\n isEqual(other: MaybeDocument | null | undefined): boolean {\n return (\n other instanceof NoDocument &&\n other.hasCommittedMutations === this.hasCommittedMutations &&\n other.version.isEqual(this.version) &&\n other.key.isEqual(this.key)\n );\n }\n}\n\n/**\n * A class representing an existing document whose data is unknown (e.g. a\n * document that was updated without a known base document).\n */\nexport class UnknownDocument extends MaybeDocument {\n toString(): string {\n return `UnknownDocument(${this.key}, ${this.version})`;\n }\n\n get hasPendingWrites(): boolean {\n return true;\n }\n\n isEqual(other: MaybeDocument | null | undefined): boolean {\n return (\n other instanceof UnknownDocument &&\n other.version.isEqual(this.version) &&\n other.key.isEqual(this.key)\n );\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Timestamp } from '../api/timestamp';\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { BatchId } from '../core/types';\nimport { debugAssert, hardAssert } from '../util/assert';\nimport { arrayEquals } from '../util/misc';\nimport {\n documentKeySet,\n DocumentKeySet,\n DocumentVersionMap,\n documentVersionMap,\n MaybeDocumentMap\n} from './collections';\nimport { MaybeDocument } from './document';\nimport { DocumentKey } from './document_key';\nimport {\n applyMutationToLocalView,\n applyMutationToRemoteDocument,\n Mutation,\n mutationEquals,\n MutationResult\n} from './mutation';\n\nexport const BATCHID_UNKNOWN = -1;\n\n/**\n * A batch of mutations that will be sent as one unit to the backend.\n */\nexport class MutationBatch {\n /**\n * @param batchId The unique ID of this mutation batch.\n * @param localWriteTime The original write time of this mutation.\n * @param baseMutations Mutations that are used to populate the base\n * values when this mutation is applied locally. This can be used to locally\n * overwrite values that are persisted in the remote document cache. Base\n * mutations are never sent to the backend.\n * @param mutations The user-provided mutations in this mutation batch.\n * User-provided mutations are applied both locally and remotely on the\n * backend.\n */\n constructor(\n public batchId: BatchId,\n public localWriteTime: Timestamp,\n public baseMutations: Mutation[],\n public mutations: Mutation[]\n ) {\n debugAssert(mutations.length > 0, 'Cannot create an empty mutation batch');\n }\n\n /**\n * Applies all the mutations in this MutationBatch to the specified document\n * to create a new remote document\n *\n * @param docKey The key of the document to apply mutations to.\n * @param maybeDoc The document to apply mutations to.\n * @param batchResult The result of applying the MutationBatch to the\n * backend.\n */\n applyToRemoteDocument(\n docKey: DocumentKey,\n maybeDoc: MaybeDocument | null,\n batchResult: MutationBatchResult\n ): MaybeDocument | null {\n if (maybeDoc) {\n debugAssert(\n maybeDoc.key.isEqual(docKey),\n `applyToRemoteDocument: key ${docKey} should match maybeDoc key\n ${maybeDoc.key}`\n );\n }\n\n const mutationResults = batchResult.mutationResults;\n debugAssert(\n mutationResults.length === this.mutations.length,\n `Mismatch between mutations length\n (${this.mutations.length}) and mutation results length\n (${mutationResults.length}).`\n );\n\n for (let i = 0; i < this.mutations.length; i++) {\n const mutation = this.mutations[i];\n if (mutation.key.isEqual(docKey)) {\n const mutationResult = mutationResults[i];\n maybeDoc = applyMutationToRemoteDocument(\n mutation,\n maybeDoc,\n mutationResult\n );\n }\n }\n return maybeDoc;\n }\n\n /**\n * Computes the local view of a document given all the mutations in this\n * batch.\n *\n * @param docKey The key of the document to apply mutations to.\n * @param maybeDoc The document to apply mutations to.\n */\n applyToLocalView(\n docKey: DocumentKey,\n maybeDoc: MaybeDocument | null\n ): MaybeDocument | null {\n if (maybeDoc) {\n debugAssert(\n maybeDoc.key.isEqual(docKey),\n `applyToLocalDocument: key ${docKey} should match maybeDoc key\n ${maybeDoc.key}`\n );\n }\n\n // First, apply the base state. This allows us to apply non-idempotent\n // transform against a consistent set of values.\n for (const mutation of this.baseMutations) {\n if (mutation.key.isEqual(docKey)) {\n maybeDoc = applyMutationToLocalView(\n mutation,\n maybeDoc,\n maybeDoc,\n this.localWriteTime\n );\n }\n }\n\n const baseDoc = maybeDoc;\n\n // Second, apply all user-provided mutations.\n for (const mutation of this.mutations) {\n if (mutation.key.isEqual(docKey)) {\n maybeDoc = applyMutationToLocalView(\n mutation,\n maybeDoc,\n baseDoc,\n this.localWriteTime\n );\n }\n }\n return maybeDoc;\n }\n\n /**\n * Computes the local view for all provided documents given the mutations in\n * this batch.\n */\n applyToLocalDocumentSet(maybeDocs: MaybeDocumentMap): MaybeDocumentMap {\n // TODO(mrschmidt): This implementation is O(n^2). If we apply the mutations\n // directly (as done in `applyToLocalView()`), we can reduce the complexity\n // to O(n).\n let mutatedDocuments = maybeDocs;\n this.mutations.forEach(m => {\n const mutatedDocument = this.applyToLocalView(\n m.key,\n maybeDocs.get(m.key)\n );\n if (mutatedDocument) {\n mutatedDocuments = mutatedDocuments.insert(m.key, mutatedDocument);\n }\n });\n return mutatedDocuments;\n }\n\n keys(): DocumentKeySet {\n return this.mutations.reduce(\n (keys, m) => keys.add(m.key),\n documentKeySet()\n );\n }\n\n isEqual(other: MutationBatch): boolean {\n return (\n this.batchId === other.batchId &&\n arrayEquals(this.mutations, other.mutations, (l, r) =>\n mutationEquals(l, r)\n ) &&\n arrayEquals(this.baseMutations, other.baseMutations, (l, r) =>\n mutationEquals(l, r)\n )\n );\n }\n}\n\n/** The result of applying a mutation batch to the backend. */\nexport class MutationBatchResult {\n private constructor(\n readonly batch: MutationBatch,\n readonly commitVersion: SnapshotVersion,\n readonly mutationResults: MutationResult[],\n /**\n * A pre-computed mapping from each mutated document to the resulting\n * version.\n */\n readonly docVersions: DocumentVersionMap\n ) {}\n\n /**\n * Creates a new MutationBatchResult for the given batch and results. There\n * must be one result for each mutation in the batch. This static factory\n * caches a document=>version mapping (docVersions).\n */\n static from(\n batch: MutationBatch,\n commitVersion: SnapshotVersion,\n results: MutationResult[]\n ): MutationBatchResult {\n hardAssert(\n batch.mutations.length === results.length,\n 'Mutations sent ' +\n batch.mutations.length +\n ' must equal results received ' +\n results.length\n );\n\n let versionMap = documentVersionMap();\n const mutations = batch.mutations;\n for (let i = 0; i < mutations.length; i++) {\n versionMap = versionMap.insert(mutations[i].key, results[i].version);\n }\n\n return new MutationBatchResult(batch, commitVersion, results, versionMap);\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { fail } from '../util/assert';\n\nexport type FulfilledHandler =\n | ((result: T) => R | PersistencePromise)\n | null;\nexport type RejectedHandler =\n | ((reason: Error) => R | PersistencePromise)\n | null;\nexport type Resolver = (value?: T) => void;\nexport type Rejector = (error: Error) => void;\n\n/**\n * PersistencePromise<> is essentially a re-implementation of Promise<> except\n * it has a .next() method instead of .then() and .next() and .catch() callbacks\n * are executed synchronously when a PersistencePromise resolves rather than\n * asynchronously (Promise<> implementations use setImmediate() or similar).\n *\n * This is necessary to interoperate with IndexedDB which will automatically\n * commit transactions if control is returned to the event loop without\n * synchronously initiating another operation on the transaction.\n *\n * NOTE: .then() and .catch() only allow a single consumer, unlike normal\n * Promises.\n */\nexport class PersistencePromise {\n // NOTE: next/catchCallback will always point to our own wrapper functions,\n // not the user's raw next() or catch() callbacks.\n private nextCallback: FulfilledHandler = null;\n private catchCallback: RejectedHandler = null;\n\n // When the operation resolves, we'll set result or error and mark isDone.\n private result: T | undefined = undefined;\n private error: Error | undefined = undefined;\n private isDone = false;\n\n // Set to true when .then() or .catch() are called and prevents additional\n // chaining.\n private callbackAttached = false;\n\n constructor(callback: (resolve: Resolver, reject: Rejector) => void) {\n callback(\n value => {\n this.isDone = true;\n this.result = value;\n if (this.nextCallback) {\n // value should be defined unless T is Void, but we can't express\n // that in the type system.\n this.nextCallback(value!);\n }\n },\n error => {\n this.isDone = true;\n this.error = error;\n if (this.catchCallback) {\n this.catchCallback(error);\n }\n }\n );\n }\n\n catch(\n fn: (error: Error) => R | PersistencePromise\n ): PersistencePromise {\n return this.next(undefined, fn);\n }\n\n next(\n nextFn?: FulfilledHandler,\n catchFn?: RejectedHandler\n ): PersistencePromise {\n if (this.callbackAttached) {\n fail('Called next() or catch() twice for PersistencePromise');\n }\n this.callbackAttached = true;\n if (this.isDone) {\n if (!this.error) {\n return this.wrapSuccess(nextFn, this.result!);\n } else {\n return this.wrapFailure(catchFn, this.error);\n }\n } else {\n return new PersistencePromise((resolve, reject) => {\n this.nextCallback = (value: T) => {\n this.wrapSuccess(nextFn, value).next(resolve, reject);\n };\n this.catchCallback = (error: Error) => {\n this.wrapFailure(catchFn, error).next(resolve, reject);\n };\n });\n }\n }\n\n toPromise(): Promise {\n return new Promise((resolve, reject) => {\n this.next(resolve, reject);\n });\n }\n\n private wrapUserFunction(\n fn: () => R | PersistencePromise\n ): PersistencePromise {\n try {\n const result = fn();\n if (result instanceof PersistencePromise) {\n return result;\n } else {\n return PersistencePromise.resolve(result);\n }\n } catch (e) {\n return PersistencePromise.reject(e);\n }\n }\n\n private wrapSuccess(\n nextFn: FulfilledHandler | undefined,\n value: T\n ): PersistencePromise {\n if (nextFn) {\n return this.wrapUserFunction(() => nextFn(value));\n } else {\n // If there's no nextFn, then R must be the same as T\n return PersistencePromise.resolve((value as unknown) as R);\n }\n }\n\n private wrapFailure(\n catchFn: RejectedHandler | undefined,\n error: Error\n ): PersistencePromise {\n if (catchFn) {\n return this.wrapUserFunction(() => catchFn(error));\n } else {\n return PersistencePromise.reject(error);\n }\n }\n\n static resolve(): PersistencePromise;\n static resolve(result: R): PersistencePromise;\n static resolve(result?: R): PersistencePromise {\n return new PersistencePromise((resolve, reject) => {\n resolve(result);\n });\n }\n\n static reject(error: Error): PersistencePromise {\n return new PersistencePromise((resolve, reject) => {\n reject(error);\n });\n }\n\n static waitFor(\n // Accept all Promise types in waitFor().\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n all: { forEach: (cb: (el: PersistencePromise) => void) => void }\n ): PersistencePromise {\n return new PersistencePromise((resolve, reject) => {\n let expectedCount = 0;\n let resolvedCount = 0;\n let done = false;\n\n all.forEach(element => {\n ++expectedCount;\n element.next(\n () => {\n ++resolvedCount;\n if (done && resolvedCount === expectedCount) {\n resolve();\n }\n },\n err => reject(err)\n );\n });\n\n done = true;\n if (resolvedCount === expectedCount) {\n resolve();\n }\n });\n }\n\n /**\n * Given an array of predicate functions that asynchronously evaluate to a\n * boolean, implements a short-circuiting `or` between the results. Predicates\n * will be evaluated until one of them returns `true`, then stop. The final\n * result will be whether any of them returned `true`.\n */\n static or(\n predicates: Array<() => PersistencePromise>\n ): PersistencePromise {\n let p: PersistencePromise = PersistencePromise.resolve(\n false\n );\n for (const predicate of predicates) {\n p = p.next(isTrue => {\n if (isTrue) {\n return PersistencePromise.resolve(isTrue);\n } else {\n return predicate();\n }\n });\n }\n return p;\n }\n\n /**\n * Given an iterable, call the given function on each element in the\n * collection and wait for all of the resulting concurrent PersistencePromises\n * to resolve.\n */\n static forEach(\n collection: { forEach: (cb: (r: R, s: S) => void) => void },\n f:\n | ((r: R, s: S) => PersistencePromise)\n | ((r: R) => PersistencePromise)\n ): PersistencePromise;\n static forEach(\n collection: { forEach: (cb: (r: R) => void) => void },\n f: (r: R) => PersistencePromise\n ): PersistencePromise;\n static forEach(\n collection: { forEach: (cb: (r: R, s?: S) => void) => void },\n f: (r: R, s?: S) => PersistencePromise\n ): PersistencePromise {\n const promises: Array> = [];\n collection.forEach((r, s) => {\n promises.push(f.call(this, r, s));\n });\n return this.waitFor(promises);\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Query, queryMatches } from '../core/query';\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport {\n DocumentKeySet,\n documentKeySet,\n DocumentMap,\n documentMap,\n MaybeDocumentMap,\n maybeDocumentMap,\n NullableMaybeDocumentMap,\n nullableMaybeDocumentMap\n} from '../model/collections';\nimport { Document, MaybeDocument, NoDocument } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport { MutationBatch } from '../model/mutation_batch';\nimport { ResourcePath } from '../model/path';\n\nimport { debugAssert } from '../util/assert';\nimport { IndexManager } from './index_manager';\nimport { MutationQueue } from './mutation_queue';\nimport { applyMutationToLocalView, PatchMutation } from '../model/mutation';\nimport { PersistenceTransaction } from './persistence';\nimport { PersistencePromise } from './persistence_promise';\nimport { RemoteDocumentCache } from './remote_document_cache';\n\n/**\n * A readonly view of the local state of all documents we're tracking (i.e. we\n * have a cached version in remoteDocumentCache or local mutations for the\n * document). The view is computed by applying the mutations in the\n * MutationQueue to the RemoteDocumentCache.\n */\nexport class LocalDocumentsView {\n constructor(\n readonly remoteDocumentCache: RemoteDocumentCache,\n readonly mutationQueue: MutationQueue,\n readonly indexManager: IndexManager\n ) {}\n\n /**\n * Get the local view of the document identified by `key`.\n *\n * @return Local view of the document or null if we don't have any cached\n * state for it.\n */\n getDocument(\n transaction: PersistenceTransaction,\n key: DocumentKey\n ): PersistencePromise {\n return this.mutationQueue\n .getAllMutationBatchesAffectingDocumentKey(transaction, key)\n .next(batches => this.getDocumentInternal(transaction, key, batches));\n }\n\n /** Internal version of `getDocument` that allows reusing batches. */\n private getDocumentInternal(\n transaction: PersistenceTransaction,\n key: DocumentKey,\n inBatches: MutationBatch[]\n ): PersistencePromise {\n return this.remoteDocumentCache.getEntry(transaction, key).next(doc => {\n for (const batch of inBatches) {\n doc = batch.applyToLocalView(key, doc);\n }\n return doc;\n });\n }\n\n // Returns the view of the given `docs` as they would appear after applying\n // all mutations in the given `batches`.\n private applyLocalMutationsToDocuments(\n transaction: PersistenceTransaction,\n docs: NullableMaybeDocumentMap,\n batches: MutationBatch[]\n ): NullableMaybeDocumentMap {\n let results = nullableMaybeDocumentMap();\n docs.forEach((key, localView) => {\n for (const batch of batches) {\n localView = batch.applyToLocalView(key, localView);\n }\n results = results.insert(key, localView);\n });\n return results;\n }\n\n /**\n * Gets the local view of the documents identified by `keys`.\n *\n * If we don't have cached state for a document in `keys`, a NoDocument will\n * be stored for that key in the resulting set.\n */\n getDocuments(\n transaction: PersistenceTransaction,\n keys: DocumentKeySet\n ): PersistencePromise {\n return this.remoteDocumentCache\n .getEntries(transaction, keys)\n .next(docs => this.getLocalViewOfDocuments(transaction, docs));\n }\n\n /**\n * Similar to `getDocuments`, but creates the local view from the given\n * `baseDocs` without retrieving documents from the local store.\n */\n getLocalViewOfDocuments(\n transaction: PersistenceTransaction,\n baseDocs: NullableMaybeDocumentMap\n ): PersistencePromise {\n return this.mutationQueue\n .getAllMutationBatchesAffectingDocumentKeys(transaction, baseDocs)\n .next(batches => {\n const docs = this.applyLocalMutationsToDocuments(\n transaction,\n baseDocs,\n batches\n );\n let results = maybeDocumentMap();\n docs.forEach((key, maybeDoc) => {\n // TODO(http://b/32275378): Don't conflate missing / deleted.\n if (!maybeDoc) {\n maybeDoc = new NoDocument(key, SnapshotVersion.min());\n }\n results = results.insert(key, maybeDoc);\n });\n\n return results;\n });\n }\n\n /**\n * Performs a query against the local view of all documents.\n *\n * @param transaction The persistence transaction.\n * @param query The query to match documents against.\n * @param sinceReadTime If not set to SnapshotVersion.min(), return only\n * documents that have been read since this snapshot version (exclusive).\n */\n getDocumentsMatchingQuery(\n transaction: PersistenceTransaction,\n query: Query,\n sinceReadTime: SnapshotVersion\n ): PersistencePromise {\n if (query.isDocumentQuery()) {\n return this.getDocumentsMatchingDocumentQuery(transaction, query.path);\n } else if (query.isCollectionGroupQuery()) {\n return this.getDocumentsMatchingCollectionGroupQuery(\n transaction,\n query,\n sinceReadTime\n );\n } else {\n return this.getDocumentsMatchingCollectionQuery(\n transaction,\n query,\n sinceReadTime\n );\n }\n }\n\n private getDocumentsMatchingDocumentQuery(\n transaction: PersistenceTransaction,\n docPath: ResourcePath\n ): PersistencePromise {\n // Just do a simple document lookup.\n return this.getDocument(transaction, new DocumentKey(docPath)).next(\n maybeDoc => {\n let result = documentMap();\n if (maybeDoc instanceof Document) {\n result = result.insert(maybeDoc.key, maybeDoc);\n }\n return result;\n }\n );\n }\n\n private getDocumentsMatchingCollectionGroupQuery(\n transaction: PersistenceTransaction,\n query: Query,\n sinceReadTime: SnapshotVersion\n ): PersistencePromise {\n debugAssert(\n query.path.isEmpty(),\n 'Currently we only support collection group queries at the root.'\n );\n const collectionId = query.collectionGroup!;\n let results = documentMap();\n return this.indexManager\n .getCollectionParents(transaction, collectionId)\n .next(parents => {\n // Perform a collection query against each parent that contains the\n // collectionId and aggregate the results.\n return PersistencePromise.forEach(parents, (parent: ResourcePath) => {\n const collectionQuery = query.asCollectionQueryAtPath(\n parent.child(collectionId)\n );\n return this.getDocumentsMatchingCollectionQuery(\n transaction,\n collectionQuery,\n sinceReadTime\n ).next(r => {\n r.forEach((key, doc) => {\n results = results.insert(key, doc);\n });\n });\n }).next(() => results);\n });\n }\n\n private getDocumentsMatchingCollectionQuery(\n transaction: PersistenceTransaction,\n query: Query,\n sinceReadTime: SnapshotVersion\n ): PersistencePromise {\n // Query the remote documents and overlay mutations.\n let results: DocumentMap;\n let mutationBatches: MutationBatch[];\n return this.remoteDocumentCache\n .getDocumentsMatchingQuery(transaction, query, sinceReadTime)\n .next(queryResults => {\n results = queryResults;\n return this.mutationQueue.getAllMutationBatchesAffectingQuery(\n transaction,\n query\n );\n })\n .next(matchingMutationBatches => {\n mutationBatches = matchingMutationBatches;\n // It is possible that a PatchMutation can make a document match a query, even if\n // the version in the RemoteDocumentCache is not a match yet (waiting for server\n // to ack). To handle this, we find all document keys affected by the PatchMutations\n // that are not in `result` yet, and back fill them via `remoteDocumentCache.getEntries`,\n // otherwise those `PatchMutations` will be ignored because no base document can be found,\n // and lead to missing result for the query.\n return this.addMissingBaseDocuments(\n transaction,\n mutationBatches,\n results\n ).next(mergedDocuments => {\n results = mergedDocuments;\n\n for (const batch of mutationBatches) {\n for (const mutation of batch.mutations) {\n const key = mutation.key;\n const baseDoc = results.get(key);\n const mutatedDoc = applyMutationToLocalView(\n mutation,\n baseDoc,\n baseDoc,\n batch.localWriteTime\n );\n if (mutatedDoc instanceof Document) {\n results = results.insert(key, mutatedDoc);\n } else {\n results = results.remove(key);\n }\n }\n }\n });\n })\n .next(() => {\n // Finally, filter out any documents that don't actually match\n // the query.\n results.forEach((key, doc) => {\n if (!queryMatches(query, doc)) {\n results = results.remove(key);\n }\n });\n\n return results;\n });\n }\n\n private addMissingBaseDocuments(\n transaction: PersistenceTransaction,\n matchingMutationBatches: MutationBatch[],\n existingDocuments: DocumentMap\n ): PersistencePromise {\n let missingBaseDocEntriesForPatching = documentKeySet();\n for (const batch of matchingMutationBatches) {\n for (const mutation of batch.mutations) {\n if (\n mutation instanceof PatchMutation &&\n existingDocuments.get(mutation.key) === null\n ) {\n missingBaseDocEntriesForPatching = missingBaseDocEntriesForPatching.add(\n mutation.key\n );\n }\n }\n }\n\n let mergedDocuments = existingDocuments;\n return this.remoteDocumentCache\n .getEntries(transaction, missingBaseDocEntriesForPatching)\n .next(missingBaseDocs => {\n missingBaseDocs.forEach((key, doc) => {\n if (doc !== null && doc instanceof Document) {\n mergedDocuments = mergedDocuments.insert(key, doc);\n }\n });\n return mergedDocuments;\n });\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TargetId } from '../core/types';\nimport { ChangeType, ViewSnapshot } from '../core/view_snapshot';\nimport { documentKeySet, DocumentKeySet } from '../model/collections';\n\n/**\n * A set of changes to what documents are currently in view and out of view for\n * a given query. These changes are sent to the LocalStore by the View (via\n * the SyncEngine) and are used to pin / unpin documents as appropriate.\n */\nexport class LocalViewChanges {\n constructor(\n readonly targetId: TargetId,\n readonly fromCache: boolean,\n readonly addedKeys: DocumentKeySet,\n readonly removedKeys: DocumentKeySet\n ) {}\n\n static fromSnapshot(\n targetId: TargetId,\n viewSnapshot: ViewSnapshot\n ): LocalViewChanges {\n let addedKeys = documentKeySet();\n let removedKeys = documentKeySet();\n\n for (const docChange of viewSnapshot.docChanges) {\n switch (docChange.type) {\n case ChangeType.Added:\n addedKeys = addedKeys.add(docChange.doc.key);\n break;\n case ChangeType.Removed:\n removedKeys = removedKeys.add(docChange.doc.key);\n break;\n default:\n // do nothing\n }\n }\n\n return new LocalViewChanges(\n targetId,\n viewSnapshot.fromCache,\n addedKeys,\n removedKeys\n );\n }\n}\n","/**\n * @license\n * Copyright 2018 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ListenSequenceNumber } from './types';\n\n/**\n * `SequenceNumberSyncer` defines the methods required to keep multiple instances of a\n * `ListenSequence` in sync.\n */\nexport interface SequenceNumberSyncer {\n // Notify the syncer that a new sequence number has been used.\n writeSequenceNumber(sequenceNumber: ListenSequenceNumber): void;\n // Setting this property allows the syncer to notify when a sequence number has been used, and\n // and lets the ListenSequence adjust its internal previous value accordingly.\n sequenceNumberHandler:\n | ((sequenceNumber: ListenSequenceNumber) => void)\n | null;\n}\n\n/**\n * `ListenSequence` is a monotonic sequence. It is initialized with a minimum value to\n * exceed. All subsequent calls to next will return increasing values. If provided with a\n * `SequenceNumberSyncer`, it will additionally bump its next value when told of a new value, as\n * well as write out sequence numbers that it produces via `next()`.\n */\nexport class ListenSequence {\n static readonly INVALID: ListenSequenceNumber = -1;\n\n private writeNewSequenceNumber?: (\n newSequenceNumber: ListenSequenceNumber\n ) => void;\n\n constructor(\n private previousValue: ListenSequenceNumber,\n sequenceNumberSyncer?: SequenceNumberSyncer\n ) {\n if (sequenceNumberSyncer) {\n sequenceNumberSyncer.sequenceNumberHandler = sequenceNumber =>\n this.setPreviousValue(sequenceNumber);\n this.writeNewSequenceNumber = sequenceNumber =>\n sequenceNumberSyncer.writeSequenceNumber(sequenceNumber);\n }\n }\n\n private setPreviousValue(\n externalPreviousValue: ListenSequenceNumber\n ): ListenSequenceNumber {\n this.previousValue = Math.max(externalPreviousValue, this.previousValue);\n return this.previousValue;\n }\n\n next(): ListenSequenceNumber {\n const nextValue = ++this.previousValue;\n if (this.writeNewSequenceNumber) {\n this.writeNewSequenceNumber(nextValue);\n }\n return nextValue;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport interface Resolver {\n (value?: R | Promise): void;\n}\n\nexport interface Rejecter {\n (reason?: Error): void;\n}\n\nexport class Deferred {\n promise: Promise;\n // Assigned synchronously in constructor by Promise constructor callback.\n resolve!: Resolver;\n reject!: Rejecter;\n\n constructor() {\n this.promise = new Promise((resolve: Resolver, reject: Rejecter) => {\n this.resolve = resolve;\n this.reject = reject;\n });\n }\n}\n\n/**\n * Takes an array of values and a function from a value to a Promise. The function is run on each\n * value sequentially, waiting for the previous promise to resolve before starting the next one.\n * The returned promise resolves once the function has been run on all values.\n */\nexport function sequence(\n values: T[],\n fn: (value: T) => Promise\n): Promise {\n let p = Promise.resolve();\n for (const value of values) {\n p = p.then(() => fn(value));\n }\n return p;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AsyncQueue, DelayedOperation, TimerId } from '../util/async_queue';\nimport { logDebug } from '../util/log';\n\nconst LOG_TAG = 'ExponentialBackoff';\n\n/**\n * Initial backoff time in milliseconds after an error.\n * Set to 1s according to https://cloud.google.com/apis/design/errors.\n */\nconst DEFAULT_BACKOFF_INITIAL_DELAY_MS = 1000;\n\nconst DEFAULT_BACKOFF_FACTOR = 1.5;\n\n/** Maximum backoff time in milliseconds */\nconst DEFAULT_BACKOFF_MAX_DELAY_MS = 60 * 1000;\n\n/**\n * A helper for running delayed tasks following an exponential backoff curve\n * between attempts.\n *\n * Each delay is made up of a \"base\" delay which follows the exponential\n * backoff curve, and a +/- 50% \"jitter\" that is calculated and added to the\n * base delay. This prevents clients from accidentally synchronizing their\n * delays causing spikes of load to the backend.\n */\nexport class ExponentialBackoff {\n private currentBaseMs: number = 0;\n private timerPromise: DelayedOperation | null = null;\n /** The last backoff attempt, as epoch milliseconds. */\n private lastAttemptTime = Date.now();\n\n constructor(\n /**\n * The AsyncQueue to run backoff operations on.\n */\n private readonly queue: AsyncQueue,\n /**\n * The ID to use when scheduling backoff operations on the AsyncQueue.\n */\n private readonly timerId: TimerId,\n /**\n * The initial delay (used as the base delay on the first retry attempt).\n * Note that jitter will still be applied, so the actual delay could be as\n * little as 0.5*initialDelayMs.\n */\n private readonly initialDelayMs: number = DEFAULT_BACKOFF_INITIAL_DELAY_MS,\n /**\n * The multiplier to use to determine the extended base delay after each\n * attempt.\n */\n private readonly backoffFactor: number = DEFAULT_BACKOFF_FACTOR,\n /**\n * The maximum base delay after which no further backoff is performed.\n * Note that jitter will still be applied, so the actual delay could be as\n * much as 1.5*maxDelayMs.\n */\n private readonly maxDelayMs: number = DEFAULT_BACKOFF_MAX_DELAY_MS\n ) {\n this.reset();\n }\n\n /**\n * Resets the backoff delay.\n *\n * The very next backoffAndWait() will have no delay. If it is called again\n * (i.e. due to an error), initialDelayMs (plus jitter) will be used, and\n * subsequent ones will increase according to the backoffFactor.\n */\n reset(): void {\n this.currentBaseMs = 0;\n }\n\n /**\n * Resets the backoff delay to the maximum delay (e.g. for use after a\n * RESOURCE_EXHAUSTED error).\n */\n resetToMax(): void {\n this.currentBaseMs = this.maxDelayMs;\n }\n\n /**\n * Returns a promise that resolves after currentDelayMs, and increases the\n * delay for any subsequent attempts. If there was a pending backoff operation\n * already, it will be canceled.\n */\n backoffAndRun(op: () => Promise): void {\n // Cancel any pending backoff operation.\n this.cancel();\n\n // First schedule using the current base (which may be 0 and should be\n // honored as such).\n const desiredDelayWithJitterMs = Math.floor(\n this.currentBaseMs + this.jitterDelayMs()\n );\n\n // Guard against lastAttemptTime being in the future due to a clock change.\n const delaySoFarMs = Math.max(0, Date.now() - this.lastAttemptTime);\n\n // Guard against the backoff delay already being past.\n const remainingDelayMs = Math.max(\n 0,\n desiredDelayWithJitterMs - delaySoFarMs\n );\n\n if (remainingDelayMs > 0) {\n logDebug(\n LOG_TAG,\n `Backing off for ${remainingDelayMs} ms ` +\n `(base delay: ${this.currentBaseMs} ms, ` +\n `delay with jitter: ${desiredDelayWithJitterMs} ms, ` +\n `last attempt: ${delaySoFarMs} ms ago)`\n );\n }\n\n this.timerPromise = this.queue.enqueueAfterDelay(\n this.timerId,\n remainingDelayMs,\n () => {\n this.lastAttemptTime = Date.now();\n return op();\n }\n );\n\n // Apply backoff factor to determine next delay and ensure it is within\n // bounds.\n this.currentBaseMs *= this.backoffFactor;\n if (this.currentBaseMs < this.initialDelayMs) {\n this.currentBaseMs = this.initialDelayMs;\n }\n if (this.currentBaseMs > this.maxDelayMs) {\n this.currentBaseMs = this.maxDelayMs;\n }\n }\n\n skipBackoff(): void {\n if (this.timerPromise !== null) {\n this.timerPromise.skipDelay();\n this.timerPromise = null;\n }\n }\n\n cancel(): void {\n if (this.timerPromise !== null) {\n this.timerPromise.cancel();\n this.timerPromise = null;\n }\n }\n\n /** Returns a random value in the range [-currentBaseMs/2, currentBaseMs/2] */\n private jitterDelayMs(): number {\n return (Math.random() - 0.5) * this.currentBaseMs;\n }\n}\n","/**\n * @license\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ResourcePath } from '../model/path';\nimport { debugAssert } from '../util/assert';\nimport { SortedSet } from '../util/sorted_set';\nimport { IndexManager } from './index_manager';\nimport { PersistenceTransaction } from './persistence';\nimport { PersistencePromise } from './persistence_promise';\n\n/**\n * An in-memory implementation of IndexManager.\n */\nexport class MemoryIndexManager implements IndexManager {\n private collectionParentIndex = new MemoryCollectionParentIndex();\n\n addToCollectionParentIndex(\n transaction: PersistenceTransaction,\n collectionPath: ResourcePath\n ): PersistencePromise {\n this.collectionParentIndex.add(collectionPath);\n return PersistencePromise.resolve();\n }\n\n getCollectionParents(\n transaction: PersistenceTransaction,\n collectionId: string\n ): PersistencePromise {\n return PersistencePromise.resolve(\n this.collectionParentIndex.getEntries(collectionId)\n );\n }\n}\n\n/**\n * Internal implementation of the collection-parent index exposed by MemoryIndexManager.\n * Also used for in-memory caching by IndexedDbIndexManager and initial index population\n * in indexeddb_schema.ts\n */\nexport class MemoryCollectionParentIndex {\n private index = {} as {\n [collectionId: string]: SortedSet;\n };\n\n // Returns false if the entry already existed.\n add(collectionPath: ResourcePath): boolean {\n debugAssert(collectionPath.length % 2 === 1, 'Expected a collection path.');\n const collectionId = collectionPath.lastSegment();\n const parentPath = collectionPath.popLast();\n const existingParents =\n this.index[collectionId] ||\n new SortedSet(ResourcePath.comparator);\n const added = !existingParents.has(parentPath);\n this.index[collectionId] = existingParents.add(parentPath);\n return added;\n }\n\n has(collectionPath: ResourcePath): boolean {\n const collectionId = collectionPath.lastSegment();\n const parentPath = collectionPath.popLast();\n const existingParents = this.index[collectionId];\n return existingParents && existingParents.has(parentPath);\n }\n\n getEntries(collectionId: string): ResourcePath[] {\n const parentPaths =\n this.index[collectionId] ||\n new SortedSet(ResourcePath.comparator);\n return parentPaths.toArray();\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TargetId } from './types';\n\n/** Offset to ensure non-overlapping target ids. */\nconst OFFSET = 2;\n\n/**\n * Generates monotonically increasing target IDs for sending targets to the\n * watch stream.\n *\n * The client constructs two generators, one for the target cache, and one for\n * for the sync engine (to generate limbo documents targets). These\n * generators produce non-overlapping IDs (by using even and odd IDs\n * respectively).\n *\n * By separating the target ID space, the query cache can generate target IDs\n * that persist across client restarts, while sync engine can independently\n * generate in-memory target IDs that are transient and can be reused after a\n * restart.\n */\nexport class TargetIdGenerator {\n constructor(private lastId: number) {}\n\n next(): TargetId {\n this.lastId += OFFSET;\n return this.lastId;\n }\n\n static forTargetCache(): TargetIdGenerator {\n // The target cache generator must return '2' in its first call to `next()`\n // as there is no differentiation in the protocol layer between an unset\n // number and the number '0'. If we were to sent a target with target ID\n // '0', the backend would consider it unset and replace it with its own ID.\n return new TargetIdGenerator(2 - OFFSET);\n }\n\n static forSyncEngine(): TargetIdGenerator {\n // Sync engine assigns target IDs for limbo document detection.\n return new TargetIdGenerator(1 - OFFSET);\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { getUA } from '@firebase/util';\nimport { debugAssert } from '../util/assert';\nimport { Code, FirestoreError } from '../util/error';\nimport { logDebug, logError } from '../util/log';\nimport { Deferred } from '../util/promise';\nimport { SCHEMA_VERSION } from './indexeddb_schema';\nimport { PersistencePromise } from './persistence_promise';\n\n// References to `window` are guarded by SimpleDb.isAvailable()\n/* eslint-disable no-restricted-globals */\n\nconst LOG_TAG = 'SimpleDb';\n\n/**\n * The maximum number of retry attempts for an IndexedDb transaction that fails\n * with a DOMException.\n */\nconst TRANSACTION_RETRY_COUNT = 3;\n\n// The different modes supported by `SimpleDb.runTransaction()`\ntype SimpleDbTransactionMode = 'readonly' | 'readwrite';\n\nexport interface SimpleDbSchemaConverter {\n createOrUpgrade(\n db: IDBDatabase,\n txn: IDBTransaction,\n fromVersion: number,\n toVersion: number\n ): PersistencePromise;\n}\n\n/**\n * Provides a wrapper around IndexedDb with a simplified interface that uses\n * Promise-like return values to chain operations. Real promises cannot be used\n * since .then() continuations are executed asynchronously (e.g. via\n * .setImmediate), which would cause IndexedDB to end the transaction.\n * See PersistencePromise for more details.\n */\nexport class SimpleDb {\n /**\n * Opens the specified database, creating or upgrading it if necessary.\n *\n * Note that `version` must not be a downgrade. IndexedDB does not support downgrading the schema\n * version. We currently do not support any way to do versioning outside of IndexedDB's versioning\n * mechanism, as only version-upgrade transactions are allowed to do things like create\n * objectstores.\n */\n static openOrCreate(\n name: string,\n version: number,\n schemaConverter: SimpleDbSchemaConverter\n ): Promise {\n debugAssert(\n SimpleDb.isAvailable(),\n 'IndexedDB not supported in current environment.'\n );\n logDebug(LOG_TAG, 'Opening database:', name);\n return new PersistencePromise((resolve, reject) => {\n // TODO(mikelehen): Investigate browser compatibility.\n // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB\n // suggests IE9 and older WebKit browsers handle upgrade\n // differently. They expect setVersion, as described here:\n // https://developer.mozilla.org/en-US/docs/Web/API/IDBVersionChangeRequest/setVersion\n const request = indexedDB.open(name, version);\n\n request.onsuccess = (event: Event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n resolve(new SimpleDb(db));\n };\n\n request.onblocked = () => {\n reject(\n new FirestoreError(\n Code.FAILED_PRECONDITION,\n 'Cannot upgrade IndexedDB schema while another tab is open. ' +\n 'Close all tabs that access Firestore and reload this page to proceed.'\n )\n );\n };\n\n request.onerror = (event: Event) => {\n const error: DOMException = (event.target as IDBOpenDBRequest).error!;\n if (error.name === 'VersionError') {\n reject(\n new FirestoreError(\n Code.FAILED_PRECONDITION,\n 'A newer version of the Firestore SDK was previously used and so the persisted ' +\n 'data is not compatible with the version of the SDK you are now using. The SDK ' +\n 'will operate with persistence disabled. If you need persistence, please ' +\n 're-upgrade to a newer version of the SDK or else clear the persisted IndexedDB ' +\n 'data for your app to start fresh.'\n )\n );\n } else {\n reject(error);\n }\n };\n\n request.onupgradeneeded = (event: IDBVersionChangeEvent) => {\n logDebug(\n LOG_TAG,\n 'Database \"' + name + '\" requires upgrade from version:',\n event.oldVersion\n );\n const db = (event.target as IDBOpenDBRequest).result;\n schemaConverter\n .createOrUpgrade(\n db,\n request.transaction!,\n event.oldVersion,\n SCHEMA_VERSION\n )\n .next(() => {\n logDebug(\n LOG_TAG,\n 'Database upgrade to version ' + SCHEMA_VERSION + ' complete'\n );\n });\n };\n }).toPromise();\n }\n\n /** Deletes the specified database. */\n static delete(name: string): Promise {\n logDebug(LOG_TAG, 'Removing database:', name);\n return wrapRequest(window.indexedDB.deleteDatabase(name)).toPromise();\n }\n\n /** Returns true if IndexedDB is available in the current environment. */\n static isAvailable(): boolean {\n if (typeof indexedDB === 'undefined') {\n return false;\n }\n\n if (SimpleDb.isMockPersistence()) {\n return true;\n }\n\n // We extensively use indexed array values and compound keys,\n // which IE and Edge do not support. However, they still have indexedDB\n // defined on the window, so we need to check for them here and make sure\n // to return that persistence is not enabled for those browsers.\n // For tracking support of this feature, see here:\n // https://developer.microsoft.com/en-us/microsoft-edge/platform/status/indexeddbarraysandmultientrysupport/\n\n // Check the UA string to find out the browser.\n const ua = getUA();\n\n // IE 10\n // ua = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)';\n\n // IE 11\n // ua = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';\n\n // Edge\n // ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML,\n // like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0';\n\n // iOS Safari: Disable for users running iOS version < 10.\n const iOSVersion = SimpleDb.getIOSVersion(ua);\n const isUnsupportedIOS = 0 < iOSVersion && iOSVersion < 10;\n\n // Android browser: Disable for userse running version < 4.5.\n const androidVersion = SimpleDb.getAndroidVersion(ua);\n const isUnsupportedAndroid = 0 < androidVersion && androidVersion < 4.5;\n\n if (\n ua.indexOf('MSIE ') > 0 ||\n ua.indexOf('Trident/') > 0 ||\n ua.indexOf('Edge/') > 0 ||\n isUnsupportedIOS ||\n isUnsupportedAndroid\n ) {\n return false;\n } else {\n return true;\n }\n }\n\n /**\n * Returns true if the backing IndexedDB store is the Node IndexedDBShim\n * (see https://github.com/axemclion/IndexedDBShim).\n */\n static isMockPersistence(): boolean {\n return (\n typeof process !== 'undefined' &&\n process.env?.USE_MOCK_PERSISTENCE === 'YES'\n );\n }\n\n /** Helper to get a typed SimpleDbStore from a transaction. */\n static getStore(\n txn: SimpleDbTransaction,\n store: string\n ): SimpleDbStore {\n return txn.store(store);\n }\n\n // visible for testing\n /** Parse User Agent to determine iOS version. Returns -1 if not found. */\n static getIOSVersion(ua: string): number {\n const iOSVersionRegex = ua.match(/i(?:phone|pad|pod) os ([\\d_]+)/i);\n const version = iOSVersionRegex\n ? iOSVersionRegex[1].split('_').slice(0, 2).join('.')\n : '-1';\n return Number(version);\n }\n\n // visible for testing\n /** Parse User Agent to determine Android version. Returns -1 if not found. */\n static getAndroidVersion(ua: string): number {\n const androidVersionRegex = ua.match(/Android ([\\d.]+)/i);\n const version = androidVersionRegex\n ? androidVersionRegex[1].split('.').slice(0, 2).join('.')\n : '-1';\n return Number(version);\n }\n\n constructor(private db: IDBDatabase) {\n const iOSVersion = SimpleDb.getIOSVersion(getUA());\n // NOTE: According to https://bugs.webkit.org/show_bug.cgi?id=197050, the\n // bug we're checking for should exist in iOS >= 12.2 and < 13, but for\n // whatever reason it's much harder to hit after 12.2 so we only proactively\n // log on 12.2.\n if (iOSVersion === 12.2) {\n logError(\n 'Firestore persistence suffers from a bug in iOS 12.2 ' +\n 'Safari that may cause your app to stop working. See ' +\n 'https://stackoverflow.com/q/56496296/110915 for details ' +\n 'and a potential workaround.'\n );\n }\n }\n\n setVersionChangeListener(\n versionChangeListener: (event: IDBVersionChangeEvent) => void\n ): void {\n this.db.onversionchange = (event: IDBVersionChangeEvent) => {\n return versionChangeListener(event);\n };\n }\n\n async runTransaction(\n mode: SimpleDbTransactionMode,\n objectStores: string[],\n transactionFn: (transaction: SimpleDbTransaction) => PersistencePromise\n ): Promise {\n const readonly = mode === 'readonly';\n let attemptNumber = 0;\n\n while (true) {\n ++attemptNumber;\n\n const transaction = SimpleDbTransaction.open(\n this.db,\n readonly ? 'readonly' : 'readwrite',\n objectStores\n );\n try {\n const transactionFnResult = transactionFn(transaction)\n .catch(error => {\n // Abort the transaction if there was an error.\n transaction.abort(error);\n // We cannot actually recover, and calling `abort()` will cause the transaction's\n // completion promise to be rejected. This in turn means that we won't use\n // `transactionFnResult` below. We return a rejection here so that we don't add the\n // possibility of returning `void` to the type of `transactionFnResult`.\n return PersistencePromise.reject(error);\n })\n .toPromise();\n\n // As noted above, errors are propagated by aborting the transaction. So\n // we swallow any error here to avoid the browser logging it as unhandled.\n transactionFnResult.catch(() => {});\n\n // Wait for the transaction to complete (i.e. IndexedDb's onsuccess event to\n // fire), but still return the original transactionFnResult back to the\n // caller.\n await transaction.completionPromise;\n return transactionFnResult;\n } catch (error) {\n // TODO(schmidt-sebastian): We could probably be smarter about this and\n // not retry exceptions that are likely unrecoverable (such as quota\n // exceeded errors).\n\n // Note: We cannot use an instanceof check for FirestoreException, since the\n // exception is wrapped in a generic error by our async/await handling.\n const retryable =\n error.name !== 'FirebaseError' &&\n attemptNumber < TRANSACTION_RETRY_COUNT;\n logDebug(\n LOG_TAG,\n 'Transaction failed with error: %s. Retrying: %s.',\n error.message,\n retryable\n );\n\n if (!retryable) {\n return Promise.reject(error);\n }\n }\n }\n }\n\n close(): void {\n this.db.close();\n }\n}\n\n/**\n * A controller for iterating over a key range or index. It allows an iterate\n * callback to delete the currently-referenced object, or jump to a new key\n * within the key range or index.\n */\nexport class IterationController {\n private shouldStop = false;\n private nextKey: IDBValidKey | null = null;\n\n constructor(private dbCursor: IDBCursorWithValue) {}\n\n get isDone(): boolean {\n return this.shouldStop;\n }\n\n get skipToKey(): IDBValidKey | null {\n return this.nextKey;\n }\n\n set cursor(value: IDBCursorWithValue) {\n this.dbCursor = value;\n }\n\n /**\n * This function can be called to stop iteration at any point.\n */\n done(): void {\n this.shouldStop = true;\n }\n\n /**\n * This function can be called to skip to that next key, which could be\n * an index or a primary key.\n */\n skip(key: IDBValidKey): void {\n this.nextKey = key;\n }\n\n /**\n * Delete the current cursor value from the object store.\n *\n * NOTE: You CANNOT do this with a keysOnly query.\n */\n delete(): PersistencePromise {\n return wrapRequest(this.dbCursor.delete());\n }\n}\n\n/**\n * Callback used with iterate() method.\n */\nexport type IterateCallback = (\n key: KeyType,\n value: ValueType,\n control: IterationController\n) => void | PersistencePromise;\n\n/** Options available to the iterate() method. */\nexport interface IterateOptions {\n /** Index to iterate over (else primary keys will be iterated) */\n index?: string;\n\n /** IndxedDB Range to iterate over (else entire store will be iterated) */\n range?: IDBKeyRange;\n\n /** If true, values aren't read while iterating. */\n keysOnly?: boolean;\n\n /** If true, iterate over the store in reverse. */\n reverse?: boolean;\n}\n\n/** An error that wraps exceptions that thrown during IndexedDB execution. */\nexport class IndexedDbTransactionError extends FirestoreError {\n name = 'IndexedDbTransactionError';\n\n constructor(cause: Error) {\n super(Code.UNAVAILABLE, 'IndexedDB transaction failed: ' + cause);\n }\n}\n\n/** Verifies whether `e` is an IndexedDbTransactionError. */\nexport function isIndexedDbTransactionError(e: Error): boolean {\n // Use name equality, as instanceof checks on errors don't work with errors\n // that wrap other errors.\n return e.name === 'IndexedDbTransactionError';\n}\n\n/**\n * Wraps an IDBTransaction and exposes a store() method to get a handle to a\n * specific object store.\n */\nexport class SimpleDbTransaction {\n private aborted = false;\n\n /**\n * A promise that resolves with the result of the IndexedDb transaction.\n */\n private readonly completionDeferred = new Deferred();\n\n static open(\n db: IDBDatabase,\n mode: IDBTransactionMode,\n objectStoreNames: string[]\n ): SimpleDbTransaction {\n return new SimpleDbTransaction(db.transaction(objectStoreNames, mode));\n }\n\n constructor(private readonly transaction: IDBTransaction) {\n this.transaction.oncomplete = () => {\n this.completionDeferred.resolve();\n };\n this.transaction.onabort = () => {\n if (transaction.error) {\n this.completionDeferred.reject(\n new IndexedDbTransactionError(transaction.error)\n );\n } else {\n this.completionDeferred.resolve();\n }\n };\n this.transaction.onerror = (event: Event) => {\n const error = checkForAndReportiOSError(\n (event.target as IDBRequest).error!\n );\n this.completionDeferred.reject(new IndexedDbTransactionError(error));\n };\n }\n\n get completionPromise(): Promise {\n return this.completionDeferred.promise;\n }\n\n abort(error?: Error): void {\n if (error) {\n this.completionDeferred.reject(error);\n }\n\n if (!this.aborted) {\n logDebug(\n LOG_TAG,\n 'Aborting transaction:',\n error ? error.message : 'Client-initiated abort'\n );\n this.aborted = true;\n this.transaction.abort();\n }\n }\n\n /**\n * Returns a SimpleDbStore for the specified store. All\n * operations performed on the SimpleDbStore happen within the context of this\n * transaction and it cannot be used anymore once the transaction is\n * completed.\n *\n * Note that we can't actually enforce that the KeyType and ValueType are\n * correct, but they allow type safety through the rest of the consuming code.\n */\n store(\n storeName: string\n ): SimpleDbStore {\n const store = this.transaction.objectStore(storeName);\n debugAssert(!!store, 'Object store not part of transaction: ' + storeName);\n return new SimpleDbStore(store);\n }\n}\n\n/**\n * A wrapper around an IDBObjectStore providing an API that:\n *\n * 1) Has generic KeyType / ValueType parameters to provide strongly-typed\n * methods for acting against the object store.\n * 2) Deals with IndexedDB's onsuccess / onerror event callbacks, making every\n * method return a PersistencePromise instead.\n * 3) Provides a higher-level API to avoid needing to do excessive wrapping of\n * intermediate IndexedDB types (IDBCursorWithValue, etc.)\n */\nexport class SimpleDbStore<\n KeyType extends IDBValidKey,\n ValueType extends unknown\n> {\n constructor(private store: IDBObjectStore) {}\n\n /**\n * Writes a value into the Object Store.\n *\n * @param key Optional explicit key to use when writing the object, else the\n * key will be auto-assigned (e.g. via the defined keyPath for the store).\n * @param value The object to write.\n */\n put(value: ValueType): PersistencePromise;\n put(key: KeyType, value: ValueType): PersistencePromise;\n put(\n keyOrValue: KeyType | ValueType,\n value?: ValueType\n ): PersistencePromise {\n let request;\n if (value !== undefined) {\n logDebug(LOG_TAG, 'PUT', this.store.name, keyOrValue, value);\n request = this.store.put(value, keyOrValue as KeyType);\n } else {\n logDebug(LOG_TAG, 'PUT', this.store.name, '', keyOrValue);\n request = this.store.put(keyOrValue as ValueType);\n }\n return wrapRequest(request);\n }\n\n /**\n * Adds a new value into an Object Store and returns the new key. Similar to\n * IndexedDb's `add()`, this method will fail on primary key collisions.\n *\n * @param value The object to write.\n * @return The key of the value to add.\n */\n add(value: ValueType): PersistencePromise {\n logDebug(LOG_TAG, 'ADD', this.store.name, value, value);\n const request = this.store.add(value as ValueType);\n return wrapRequest(request);\n }\n\n /**\n * Gets the object with the specified key from the specified store, or null\n * if no object exists with the specified key.\n *\n * @key The key of the object to get.\n * @return The object with the specified key or null if no object exists.\n */\n get(key: KeyType): PersistencePromise {\n const request = this.store.get(key);\n // We're doing an unsafe cast to ValueType.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return wrapRequest(request).next(result => {\n // Normalize nonexistence to null.\n if (result === undefined) {\n result = null;\n }\n logDebug(LOG_TAG, 'GET', this.store.name, key, result);\n return result;\n });\n }\n\n delete(key: KeyType | IDBKeyRange): PersistencePromise {\n logDebug(LOG_TAG, 'DELETE', this.store.name, key);\n const request = this.store.delete(key);\n return wrapRequest(request);\n }\n\n /**\n * If we ever need more of the count variants, we can add overloads. For now,\n * all we need is to count everything in a store.\n *\n * Returns the number of rows in the store.\n */\n count(): PersistencePromise {\n logDebug(LOG_TAG, 'COUNT', this.store.name);\n const request = this.store.count();\n return wrapRequest(request);\n }\n\n loadAll(): PersistencePromise;\n loadAll(range: IDBKeyRange): PersistencePromise;\n loadAll(index: string, range: IDBKeyRange): PersistencePromise;\n loadAll(\n indexOrRange?: string | IDBKeyRange,\n range?: IDBKeyRange\n ): PersistencePromise {\n const cursor = this.cursor(this.options(indexOrRange, range));\n const results: ValueType[] = [];\n return this.iterateCursor(cursor, (key, value) => {\n results.push(value);\n }).next(() => {\n return results;\n });\n }\n\n deleteAll(): PersistencePromise;\n deleteAll(range: IDBKeyRange): PersistencePromise;\n deleteAll(index: string, range: IDBKeyRange): PersistencePromise;\n deleteAll(\n indexOrRange?: string | IDBKeyRange,\n range?: IDBKeyRange\n ): PersistencePromise {\n logDebug(LOG_TAG, 'DELETE ALL', this.store.name);\n const options = this.options(indexOrRange, range);\n options.keysOnly = false;\n const cursor = this.cursor(options);\n return this.iterateCursor(cursor, (key, value, control) => {\n // NOTE: Calling delete() on a cursor is documented as more efficient than\n // calling delete() on an object store with a single key\n // (https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/delete),\n // however, this requires us *not* to use a keysOnly cursor\n // (https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/delete). We\n // may want to compare the performance of each method.\n return control.delete();\n });\n }\n\n /**\n * Iterates over keys and values in an object store.\n *\n * @param options Options specifying how to iterate the objects in the store.\n * @param callback will be called for each iterated object. Iteration can be\n * canceled at any point by calling the doneFn passed to the callback.\n * The callback can return a PersistencePromise if it performs async\n * operations but note that iteration will continue without waiting for them\n * to complete.\n * @returns A PersistencePromise that resolves once all PersistencePromises\n * returned by callbacks resolve.\n */\n iterate(\n callback: IterateCallback\n ): PersistencePromise;\n iterate(\n options: IterateOptions,\n callback: IterateCallback\n ): PersistencePromise;\n iterate(\n optionsOrCallback: IterateOptions | IterateCallback,\n callback?: IterateCallback\n ): PersistencePromise {\n let options;\n if (!callback) {\n options = {};\n callback = optionsOrCallback as IterateCallback;\n } else {\n options = optionsOrCallback as IterateOptions;\n }\n const cursor = this.cursor(options);\n return this.iterateCursor(cursor, callback);\n }\n\n /**\n * Iterates over a store, but waits for the given callback to complete for\n * each entry before iterating the next entry. This allows the callback to do\n * asynchronous work to determine if this iteration should continue.\n *\n * The provided callback should return `true` to continue iteration, and\n * `false` otherwise.\n */\n iterateSerial(\n callback: (k: KeyType, v: ValueType) => PersistencePromise\n ): PersistencePromise {\n const cursorRequest = this.cursor({});\n return new PersistencePromise((resolve, reject) => {\n cursorRequest.onerror = (event: Event) => {\n const error = checkForAndReportiOSError(\n (event.target as IDBRequest).error!\n );\n reject(error);\n };\n cursorRequest.onsuccess = (event: Event) => {\n const cursor: IDBCursorWithValue = (event.target as IDBRequest).result;\n if (!cursor) {\n resolve();\n return;\n }\n\n callback(cursor.primaryKey as KeyType, cursor.value).next(\n shouldContinue => {\n if (shouldContinue) {\n cursor.continue();\n } else {\n resolve();\n }\n }\n );\n };\n });\n }\n\n private iterateCursor(\n cursorRequest: IDBRequest,\n fn: IterateCallback\n ): PersistencePromise {\n const results: Array> = [];\n return new PersistencePromise((resolve, reject) => {\n cursorRequest.onerror = (event: Event) => {\n reject((event.target as IDBRequest).error!);\n };\n cursorRequest.onsuccess = (event: Event) => {\n const cursor: IDBCursorWithValue = (event.target as IDBRequest).result;\n if (!cursor) {\n resolve();\n return;\n }\n const controller = new IterationController(cursor);\n const userResult = fn(\n cursor.primaryKey as KeyType,\n cursor.value,\n controller\n );\n if (userResult instanceof PersistencePromise) {\n const userPromise: PersistencePromise = userResult.catch(\n err => {\n controller.done();\n return PersistencePromise.reject(err);\n }\n );\n results.push(userPromise);\n }\n if (controller.isDone) {\n resolve();\n } else if (controller.skipToKey === null) {\n cursor.continue();\n } else {\n cursor.continue(controller.skipToKey);\n }\n };\n }).next(() => {\n return PersistencePromise.waitFor(results);\n });\n }\n\n private options(\n indexOrRange?: string | IDBKeyRange,\n range?: IDBKeyRange\n ): IterateOptions {\n let indexName: string | undefined = undefined;\n if (indexOrRange !== undefined) {\n if (typeof indexOrRange === 'string') {\n indexName = indexOrRange;\n } else {\n debugAssert(\n range === undefined,\n '3rd argument must not be defined if 2nd is a range.'\n );\n range = indexOrRange;\n }\n }\n return { index: indexName, range };\n }\n\n private cursor(options: IterateOptions): IDBRequest {\n let direction: IDBCursorDirection = 'next';\n if (options.reverse) {\n direction = 'prev';\n }\n if (options.index) {\n const index = this.store.index(options.index);\n if (options.keysOnly) {\n return index.openKeyCursor(options.range, direction);\n } else {\n return index.openCursor(options.range, direction);\n }\n } else {\n return this.store.openCursor(options.range, direction);\n }\n }\n}\n\n/**\n * Wraps an IDBRequest in a PersistencePromise, using the onsuccess / onerror\n * handlers to resolve / reject the PersistencePromise as appropriate.\n */\nfunction wrapRequest(request: IDBRequest): PersistencePromise {\n return new PersistencePromise((resolve, reject) => {\n request.onsuccess = (event: Event) => {\n const result = (event.target as IDBRequest).result;\n resolve(result);\n };\n\n request.onerror = (event: Event) => {\n const error = checkForAndReportiOSError(\n (event.target as IDBRequest).error!\n );\n reject(error);\n };\n });\n}\n\n// Guard so we only report the error once.\nlet reportedIOSError = false;\nfunction checkForAndReportiOSError(error: DOMException): Error {\n const iOSVersion = SimpleDb.getIOSVersion(getUA());\n if (iOSVersion >= 12.2 && iOSVersion < 13) {\n const IOS_ERROR =\n 'An internal error was encountered in the Indexed Database server';\n if (error.message.indexOf(IOS_ERROR) >= 0) {\n // Wrap error in a more descriptive one.\n const newError = new FirestoreError(\n 'internal',\n `IOS_INDEXEDDB_BUG1: IndexedDb has thrown '${IOS_ERROR}'. This is likely ` +\n `due to an unavoidable bug in iOS. See https://stackoverflow.com/q/56496296/110915 ` +\n `for details and a potential workaround.`\n );\n if (!reportedIOSError) {\n reportedIOSError = true;\n // Throw a global exception outside of this promise chain, for the user to\n // potentially catch.\n setTimeout(() => {\n throw newError;\n }, 0);\n }\n return newError;\n }\n }\n return error;\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/** The Platform's 'window' implementation or null if not available. */\nexport function getWindow(): Window | null {\n // `window` is not always available, e.g. in ReactNative and WebWorkers.\n // eslint-disable-next-line no-restricted-globals\n return typeof window !== 'undefined' ? window : null;\n}\n\n/** The Platform's 'document' implementation or null if not available. */\nexport function getDocument(): Document | null {\n // `document` is not always available, e.g. in ReactNative and WebWorkers.\n // eslint-disable-next-line no-restricted-globals\n return typeof document !== 'undefined' ? document : null;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { debugAssert, fail } from './assert';\nimport { Code, FirestoreError } from './error';\nimport { logDebug, logError } from './log';\nimport { Deferred } from './promise';\nimport { ExponentialBackoff } from '../remote/backoff';\nimport { isIndexedDbTransactionError } from '../local/simple_db';\nimport { getWindow } from '../platform/dom';\n\nconst LOG_TAG = 'AsyncQueue';\n\n// Accept any return type from setTimeout().\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype TimerHandle = any;\n\n/**\n * Wellknown \"timer\" IDs used when scheduling delayed operations on the\n * AsyncQueue. These IDs can then be used from tests to check for the presence\n * of operations or to run them early.\n *\n * The string values are used when encoding these timer IDs in JSON spec tests.\n */\nexport const enum TimerId {\n /** All can be used with runDelayedOperationsEarly() to run all timers. */\n All = 'all',\n\n /**\n * The following 4 timers are used in persistent_stream.ts for the listen and\n * write streams. The \"Idle\" timer is used to close the stream due to\n * inactivity. The \"ConnectionBackoff\" timer is used to restart a stream once\n * the appropriate backoff delay has elapsed.\n */\n ListenStreamIdle = 'listen_stream_idle',\n ListenStreamConnectionBackoff = 'listen_stream_connection_backoff',\n WriteStreamIdle = 'write_stream_idle',\n WriteStreamConnectionBackoff = 'write_stream_connection_backoff',\n\n /**\n * A timer used in online_state_tracker.ts to transition from\n * OnlineState.Unknown to Offline after a set timeout, rather than waiting\n * indefinitely for success or failure.\n */\n OnlineStateTimeout = 'online_state_timeout',\n\n /**\n * A timer used to update the client metadata in IndexedDb, which is used\n * to determine the primary leaseholder.\n */\n ClientMetadataRefresh = 'client_metadata_refresh',\n\n /** A timer used to periodically attempt LRU Garbage collection */\n LruGarbageCollection = 'lru_garbage_collection',\n\n /**\n * A timer used to retry transactions. Since there can be multiple concurrent\n * transactions, multiple of these may be in the queue at a given time.\n */\n TransactionRetry = 'transaction_retry',\n\n /**\n * A timer used to retry operations scheduled via retryable AsyncQueue\n * operations.\n */\n AsyncQueueRetry = 'async_queue_retry'\n}\n\n/**\n * Represents an operation scheduled to be run in the future on an AsyncQueue.\n *\n * It is created via DelayedOperation.createAndSchedule().\n *\n * Supports cancellation (via cancel()) and early execution (via skipDelay()).\n *\n * Note: We implement `PromiseLike` instead of `Promise`, as the `Promise` type\n * in newer versions of TypeScript defines `finally`, which is not available in\n * IE.\n */\nexport class DelayedOperation implements PromiseLike {\n // handle for use with clearTimeout(), or null if the operation has been\n // executed or canceled already.\n private timerHandle: TimerHandle | null;\n\n private readonly deferred = new Deferred();\n\n private constructor(\n private readonly asyncQueue: AsyncQueue,\n readonly timerId: TimerId,\n readonly targetTimeMs: number,\n private readonly op: () => Promise,\n private readonly removalCallback: (op: DelayedOperation) => void\n ) {\n // It's normal for the deferred promise to be canceled (due to cancellation)\n // and so we attach a dummy catch callback to avoid\n // 'UnhandledPromiseRejectionWarning' log spam.\n this.deferred.promise.catch(err => {});\n }\n\n /**\n * Creates and returns a DelayedOperation that has been scheduled to be\n * executed on the provided asyncQueue after the provided delayMs.\n *\n * @param asyncQueue The queue to schedule the operation on.\n * @param id A Timer ID identifying the type of operation this is.\n * @param delayMs The delay (ms) before the operation should be scheduled.\n * @param op The operation to run.\n * @param removalCallback A callback to be called synchronously once the\n * operation is executed or canceled, notifying the AsyncQueue to remove it\n * from its delayedOperations list.\n * PORTING NOTE: This exists to prevent making removeDelayedOperation() and\n * the DelayedOperation class public.\n */\n static createAndSchedule(\n asyncQueue: AsyncQueue,\n timerId: TimerId,\n delayMs: number,\n op: () => Promise,\n removalCallback: (op: DelayedOperation) => void\n ): DelayedOperation {\n const targetTime = Date.now() + delayMs;\n const delayedOp = new DelayedOperation(\n asyncQueue,\n timerId,\n targetTime,\n op,\n removalCallback\n );\n delayedOp.start(delayMs);\n return delayedOp;\n }\n\n /**\n * Starts the timer. This is called immediately after construction by\n * createAndSchedule().\n */\n private start(delayMs: number): void {\n this.timerHandle = setTimeout(() => this.handleDelayElapsed(), delayMs);\n }\n\n /**\n * Queues the operation to run immediately (if it hasn't already been run or\n * canceled).\n */\n skipDelay(): void {\n return this.handleDelayElapsed();\n }\n\n /**\n * Cancels the operation if it hasn't already been executed or canceled. The\n * promise will be rejected.\n *\n * As long as the operation has not yet been run, calling cancel() provides a\n * guarantee that the operation will not be run.\n */\n cancel(reason?: string): void {\n if (this.timerHandle !== null) {\n this.clearTimeout();\n this.deferred.reject(\n new FirestoreError(\n Code.CANCELLED,\n 'Operation cancelled' + (reason ? ': ' + reason : '')\n )\n );\n }\n }\n\n then = this.deferred.promise.then.bind(this.deferred.promise);\n\n private handleDelayElapsed(): void {\n this.asyncQueue.enqueueAndForget(() => {\n if (this.timerHandle !== null) {\n this.clearTimeout();\n return this.op().then(result => {\n return this.deferred.resolve(result);\n });\n } else {\n return Promise.resolve();\n }\n });\n }\n\n private clearTimeout(): void {\n if (this.timerHandle !== null) {\n this.removalCallback(this);\n clearTimeout(this.timerHandle);\n this.timerHandle = null;\n }\n }\n}\n\nexport class AsyncQueue {\n // The last promise in the queue.\n private tail: Promise = Promise.resolve();\n\n // A list of retryable operations. Retryable operations are run in order and\n // retried with backoff.\n private retryableOps: Array<() => Promise> = [];\n\n // Is this AsyncQueue being shut down? Once it is set to true, it will not\n // be changed again.\n private _isShuttingDown: boolean = false;\n\n // Operations scheduled to be queued in the future. Operations are\n // automatically removed after they are run or canceled.\n private delayedOperations: Array> = [];\n\n // visible for testing\n failure: Error | null = null;\n\n // Flag set while there's an outstanding AsyncQueue operation, used for\n // assertion sanity-checks.\n private operationInProgress = false;\n\n // List of TimerIds to fast-forward delays for.\n private timerIdsToSkip: TimerId[] = [];\n\n // Backoff timer used to schedule retries for retryable operations\n private backoff = new ExponentialBackoff(this, TimerId.AsyncQueueRetry);\n\n // Visibility handler that triggers an immediate retry of all retryable\n // operations. Meant to speed up recovery when we regain file system access\n // after page comes into foreground.\n private visibilityHandler = (): void => this.backoff.skipBackoff();\n\n constructor() {\n const window = getWindow();\n if (window && typeof window.addEventListener === 'function') {\n window.addEventListener('visibilitychange', this.visibilityHandler);\n }\n }\n\n // Is this AsyncQueue being shut down? If true, this instance will not enqueue\n // any new operations, Promises from enqueue requests will not resolve.\n get isShuttingDown(): boolean {\n return this._isShuttingDown;\n }\n\n /**\n * Adds a new operation to the queue without waiting for it to complete (i.e.\n * we ignore the Promise result).\n */\n enqueueAndForget(op: () => Promise): void {\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.enqueue(op);\n }\n\n /**\n * Regardless if the queue has initialized shutdown, adds a new operation to the\n * queue without waiting for it to complete (i.e. we ignore the Promise result).\n */\n enqueueAndForgetEvenAfterShutdown(\n op: () => Promise\n ): void {\n this.verifyNotFailed();\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.enqueueInternal(op);\n }\n\n /**\n * Regardless if the queue has initialized shutdown, adds a new operation to the\n * queue.\n */\n private enqueueEvenAfterShutdown(\n op: () => Promise\n ): Promise {\n this.verifyNotFailed();\n return this.enqueueInternal(op);\n }\n\n /**\n * Adds a new operation to the queue and initialize the shut down of this queue.\n * Returns a promise that will be resolved when the promise returned by the new\n * operation is (with its value).\n * Once this method is called, the only possible way to request running an operation\n * is through `enqueueAndForgetEvenAfterShutdown`.\n */\n async enqueueAndInitiateShutdown(op: () => Promise): Promise {\n this.verifyNotFailed();\n if (!this._isShuttingDown) {\n this._isShuttingDown = true;\n const window = getWindow();\n if (window) {\n window.removeEventListener('visibilitychange', this.visibilityHandler);\n }\n await this.enqueueEvenAfterShutdown(op);\n }\n }\n\n /**\n * Adds a new operation to the queue. Returns a promise that will be resolved\n * when the promise returned by the new operation is (with its value).\n */\n enqueue(op: () => Promise): Promise {\n this.verifyNotFailed();\n if (this._isShuttingDown) {\n // Return a Promise which never resolves.\n return new Promise(resolve => {});\n }\n return this.enqueueInternal(op);\n }\n\n /**\n * Enqueue a retryable operation.\n *\n * A retryable operation is rescheduled with backoff if it fails with a\n * IndexedDbTransactionError (the error type used by SimpleDb). All\n * retryable operations are executed in order and only run if all prior\n * operations were retried successfully.\n */\n enqueueRetryable(op: () => Promise): void {\n this.retryableOps.push(op);\n this.enqueueAndForget(() => this.retryNextOp());\n }\n\n /**\n * Runs the next operation from the retryable queue. If the operation fails,\n * reschedules with backoff.\n */\n private async retryNextOp(): Promise {\n if (this.retryableOps.length === 0) {\n return;\n }\n\n try {\n await this.retryableOps[0]();\n this.retryableOps.shift();\n this.backoff.reset();\n } catch (e) {\n if (isIndexedDbTransactionError(e)) {\n logDebug(LOG_TAG, 'Operation failed with retryable error: ' + e);\n } else {\n throw e; // Failure will be handled by AsyncQueue\n }\n }\n\n if (this.retryableOps.length > 0) {\n // If there are additional operations, we re-schedule `retryNextOp()`.\n // This is necessary to run retryable operations that failed during\n // their initial attempt since we don't know whether they are already\n // enqueued. If, for example, `op1`, `op2`, `op3` are enqueued and `op1`\n // needs to be re-run, we will run `op1`, `op1`, `op2` using the\n // already enqueued calls to `retryNextOp()`. `op3()` will then run in the\n // call scheduled here.\n // Since `backoffAndRun()` cancels an existing backoff and schedules a\n // new backoff on every call, there is only ever a single additional\n // operation in the queue.\n this.backoff.backoffAndRun(() => this.retryNextOp());\n }\n }\n\n private enqueueInternal(op: () => Promise): Promise {\n const newTail = this.tail.then(() => {\n this.operationInProgress = true;\n return op()\n .catch((error: FirestoreError) => {\n this.failure = error;\n this.operationInProgress = false;\n const message = getMessageOrStack(error);\n logError('INTERNAL UNHANDLED ERROR: ', message);\n\n // Re-throw the error so that this.tail becomes a rejected Promise and\n // all further attempts to chain (via .then) will just short-circuit\n // and return the rejected Promise.\n throw error;\n })\n .then(result => {\n this.operationInProgress = false;\n return result;\n });\n });\n this.tail = newTail;\n return newTail;\n }\n\n /**\n * Schedules an operation to be queued on the AsyncQueue once the specified\n * `delayMs` has elapsed. The returned DelayedOperation can be used to cancel\n * or fast-forward the operation prior to its running.\n */\n enqueueAfterDelay(\n timerId: TimerId,\n delayMs: number,\n op: () => Promise\n ): DelayedOperation {\n this.verifyNotFailed();\n\n debugAssert(\n delayMs >= 0,\n `Attempted to schedule an operation with a negative delay of ${delayMs}`\n );\n\n // Fast-forward delays for timerIds that have been overriden.\n if (this.timerIdsToSkip.indexOf(timerId) > -1) {\n delayMs = 0;\n }\n\n const delayedOp = DelayedOperation.createAndSchedule(\n this,\n timerId,\n delayMs,\n op,\n removedOp =>\n this.removeDelayedOperation(removedOp as DelayedOperation)\n );\n this.delayedOperations.push(delayedOp as DelayedOperation);\n return delayedOp;\n }\n\n private verifyNotFailed(): void {\n if (this.failure) {\n fail('AsyncQueue is already failed: ' + getMessageOrStack(this.failure));\n }\n }\n\n /**\n * Verifies there's an operation currently in-progress on the AsyncQueue.\n * Unfortunately we can't verify that the running code is in the promise chain\n * of that operation, so this isn't a foolproof check, but it should be enough\n * to catch some bugs.\n */\n verifyOperationInProgress(): void {\n debugAssert(\n this.operationInProgress,\n 'verifyOpInProgress() called when no op in progress on this queue.'\n );\n }\n\n /**\n * Waits until all currently queued tasks are finished executing. Delayed\n * operations are not run.\n */\n async drain(): Promise {\n // Operations in the queue prior to draining may have enqueued additional\n // operations. Keep draining the queue until the tail is no longer advanced,\n // which indicates that no more new operations were enqueued and that all\n // operations were executed.\n let currentTail: Promise;\n do {\n currentTail = this.tail;\n await currentTail;\n } while (currentTail !== this.tail);\n }\n\n /**\n * For Tests: Determine if a delayed operation with a particular TimerId\n * exists.\n */\n containsDelayedOperation(timerId: TimerId): boolean {\n for (const op of this.delayedOperations) {\n if (op.timerId === timerId) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * For Tests: Runs some or all delayed operations early.\n *\n * @param lastTimerId Delayed operations up to and including this TimerId will\n * be drained. Pass TimerId.All to run all delayed operations.\n * @returns a Promise that resolves once all operations have been run.\n */\n runAllDelayedOperationsUntil(lastTimerId: TimerId): Promise {\n // Note that draining may generate more delayed ops, so we do that first.\n return this.drain().then(() => {\n // Run ops in the same order they'd run if they ran naturally.\n this.delayedOperations.sort((a, b) => a.targetTimeMs - b.targetTimeMs);\n\n for (const op of this.delayedOperations) {\n op.skipDelay();\n if (lastTimerId !== TimerId.All && op.timerId === lastTimerId) {\n break;\n }\n }\n\n return this.drain();\n });\n }\n\n /**\n * For Tests: Skip all subsequent delays for a timer id.\n */\n skipDelaysForTimerId(timerId: TimerId): void {\n this.timerIdsToSkip.push(timerId);\n }\n\n /** Called once a DelayedOperation is run or canceled. */\n private removeDelayedOperation(op: DelayedOperation): void {\n // NOTE: indexOf / slice are O(n), but delayedOperations is expected to be small.\n const index = this.delayedOperations.indexOf(op);\n debugAssert(index >= 0, 'Delayed operation not found.');\n this.delayedOperations.splice(index, 1);\n }\n}\n\n/**\n * Returns a FirestoreError that can be surfaced to the user if the provided\n * error is an IndexedDbTransactionError. Re-throws the error otherwise.\n */\nexport function wrapInUserErrorIfRecoverable(\n e: Error,\n msg: string\n): FirestoreError {\n logError(LOG_TAG, `${msg}: ${e}`);\n if (isIndexedDbTransactionError(e)) {\n return new FirestoreError(Code.UNAVAILABLE, `${msg}: ${e}`);\n } else {\n throw e;\n }\n}\n\n/**\n * Chrome includes Error.message in Error.stack. Other browsers do not.\n * This returns expected output of message + stack when available.\n * @param error Error or FirestoreError\n */\nfunction getMessageOrStack(error: Error): string {\n let message = error.message || '';\n if (error.stack) {\n if (error.stack.includes(error.message)) {\n message = error.stack;\n } else {\n message = error.message + '\\n' + error.stack;\n }\n }\n return message;\n}\n","/**\n * @license\n * Copyright 2018 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ListenSequence } from '../core/listen_sequence';\nimport { ListenSequenceNumber, TargetId } from '../core/types';\nimport { debugAssert } from '../util/assert';\nimport { AsyncQueue, DelayedOperation, TimerId } from '../util/async_queue';\nimport { getLogLevel, logDebug, LogLevel } from '../util/log';\nimport { primitiveComparator } from '../util/misc';\nimport { SortedMap } from '../util/sorted_map';\nimport { SortedSet } from '../util/sorted_set';\nimport { ignoreIfPrimaryLeaseLoss, LocalStore } from './local_store';\nimport {\n GarbageCollectionScheduler,\n PersistenceTransaction\n} from './persistence';\nimport { PersistencePromise } from './persistence_promise';\nimport { TargetData } from './target_data';\nimport { isIndexedDbTransactionError } from './simple_db';\n\nconst LOG_TAG = 'LruGarbageCollector';\n\n/**\n * Persistence layers intending to use LRU Garbage collection should have reference delegates that\n * implement this interface. This interface defines the operations that the LRU garbage collector\n * needs from the persistence layer.\n */\nexport interface LruDelegate {\n readonly garbageCollector: LruGarbageCollector;\n\n /** Enumerates all the targets in the TargetCache. */\n forEachTarget(\n txn: PersistenceTransaction,\n f: (target: TargetData) => void\n ): PersistencePromise;\n\n getSequenceNumberCount(\n txn: PersistenceTransaction\n ): PersistencePromise;\n\n /**\n * Enumerates sequence numbers for documents not associated with a target.\n * Note that this may include duplicate sequence numbers.\n */\n forEachOrphanedDocumentSequenceNumber(\n txn: PersistenceTransaction,\n f: (sequenceNumber: ListenSequenceNumber) => void\n ): PersistencePromise;\n\n /**\n * Removes all targets that have a sequence number less than or equal to `upperBound`, and are not\n * present in the `activeTargetIds` set.\n *\n * @return the number of targets removed.\n */\n removeTargets(\n txn: PersistenceTransaction,\n upperBound: ListenSequenceNumber,\n activeTargetIds: ActiveTargets\n ): PersistencePromise;\n\n /**\n * Removes all unreferenced documents from the cache that have a sequence number less than or\n * equal to the given `upperBound`.\n *\n * @return the number of documents removed.\n */\n removeOrphanedDocuments(\n txn: PersistenceTransaction,\n upperBound: ListenSequenceNumber\n ): PersistencePromise;\n\n getCacheSize(txn: PersistenceTransaction): PersistencePromise;\n}\n\n/**\n * Describes a map whose keys are active target ids. We do not care about the type of the\n * values.\n */\nexport type ActiveTargets = SortedMap;\n\n// The type and comparator for the items contained in the SortedSet used in\n// place of a priority queue for the RollingSequenceNumberBuffer.\ntype BufferEntry = [ListenSequenceNumber, number];\nfunction bufferEntryComparator(\n [aSequence, aIndex]: BufferEntry,\n [bSequence, bIndex]: BufferEntry\n): number {\n const seqCmp = primitiveComparator(aSequence, bSequence);\n if (seqCmp === 0) {\n // This order doesn't matter, but we can bias against churn by sorting\n // entries created earlier as less than newer entries.\n return primitiveComparator(aIndex, bIndex);\n } else {\n return seqCmp;\n }\n}\n\n/**\n * Used to calculate the nth sequence number. Keeps a rolling buffer of the\n * lowest n values passed to `addElement`, and finally reports the largest of\n * them in `maxValue`.\n */\nclass RollingSequenceNumberBuffer {\n private buffer: SortedSet = new SortedSet(\n bufferEntryComparator\n );\n\n private previousIndex = 0;\n\n constructor(private readonly maxElements: number) {}\n\n private nextIndex(): number {\n return ++this.previousIndex;\n }\n\n addElement(sequenceNumber: ListenSequenceNumber): void {\n const entry: BufferEntry = [sequenceNumber, this.nextIndex()];\n if (this.buffer.size < this.maxElements) {\n this.buffer = this.buffer.add(entry);\n } else {\n const highestValue = this.buffer.last()!;\n if (bufferEntryComparator(entry, highestValue) < 0) {\n this.buffer = this.buffer.delete(highestValue).add(entry);\n }\n }\n }\n\n get maxValue(): ListenSequenceNumber {\n // Guaranteed to be non-empty. If we decide we are not collecting any\n // sequence numbers, nthSequenceNumber below short-circuits. If we have\n // decided that we are collecting n sequence numbers, it's because n is some\n // percentage of the existing sequence numbers. That means we should never\n // be in a situation where we are collecting sequence numbers but don't\n // actually have any.\n return this.buffer.last()![0];\n }\n}\n\n/**\n * Describes the results of a garbage collection run. `didRun` will be set to\n * `false` if collection was skipped (either it is disabled or the cache size\n * has not hit the threshold). If collection ran, the other fields will be\n * filled in with the details of the results.\n */\nexport interface LruResults {\n readonly didRun: boolean;\n readonly sequenceNumbersCollected: number;\n readonly targetsRemoved: number;\n readonly documentsRemoved: number;\n}\n\nconst GC_DID_NOT_RUN: LruResults = {\n didRun: false,\n sequenceNumbersCollected: 0,\n targetsRemoved: 0,\n documentsRemoved: 0\n};\n\nexport class LruParams {\n static readonly COLLECTION_DISABLED = -1;\n static readonly MINIMUM_CACHE_SIZE_BYTES = 1 * 1024 * 1024;\n static readonly DEFAULT_CACHE_SIZE_BYTES = 40 * 1024 * 1024;\n private static readonly DEFAULT_COLLECTION_PERCENTILE = 10;\n private static readonly DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT = 1000;\n\n static withCacheSize(cacheSize: number): LruParams {\n return new LruParams(\n cacheSize,\n LruParams.DEFAULT_COLLECTION_PERCENTILE,\n LruParams.DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT\n );\n }\n\n static readonly DEFAULT: LruParams = new LruParams(\n LruParams.DEFAULT_CACHE_SIZE_BYTES,\n LruParams.DEFAULT_COLLECTION_PERCENTILE,\n LruParams.DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT\n );\n\n static readonly DISABLED: LruParams = new LruParams(\n LruParams.COLLECTION_DISABLED,\n 0,\n 0\n );\n\n constructor(\n // When we attempt to collect, we will only do so if the cache size is greater than this\n // threshold. Passing `COLLECTION_DISABLED` here will cause collection to always be skipped.\n readonly cacheSizeCollectionThreshold: number,\n // The percentage of sequence numbers that we will attempt to collect\n readonly percentileToCollect: number,\n // A cap on the total number of sequence numbers that will be collected. This prevents\n // us from collecting a huge number of sequence numbers if the cache has grown very large.\n readonly maximumSequenceNumbersToCollect: number\n ) {}\n}\n\n/** How long we wait to try running LRU GC after SDK initialization. */\nconst INITIAL_GC_DELAY_MS = 1 * 60 * 1000;\n/** Minimum amount of time between GC checks, after the first one. */\nconst REGULAR_GC_DELAY_MS = 5 * 60 * 1000;\n\n/**\n * This class is responsible for the scheduling of LRU garbage collection. It handles checking\n * whether or not GC is enabled, as well as which delay to use before the next run.\n */\nexport class LruScheduler implements GarbageCollectionScheduler {\n private hasRun: boolean = false;\n private gcTask: DelayedOperation | null;\n\n constructor(\n private readonly garbageCollector: LruGarbageCollector,\n private readonly asyncQueue: AsyncQueue\n ) {\n this.gcTask = null;\n }\n\n start(localStore: LocalStore): void {\n debugAssert(\n this.gcTask === null,\n 'Cannot start an already started LruScheduler'\n );\n if (\n this.garbageCollector.params.cacheSizeCollectionThreshold !==\n LruParams.COLLECTION_DISABLED\n ) {\n this.scheduleGC(localStore);\n }\n }\n\n stop(): void {\n if (this.gcTask) {\n this.gcTask.cancel();\n this.gcTask = null;\n }\n }\n\n get started(): boolean {\n return this.gcTask !== null;\n }\n\n private scheduleGC(localStore: LocalStore): void {\n debugAssert(\n this.gcTask === null,\n 'Cannot schedule GC while a task is pending'\n );\n const delay = this.hasRun ? REGULAR_GC_DELAY_MS : INITIAL_GC_DELAY_MS;\n logDebug(\n 'LruGarbageCollector',\n `Garbage collection scheduled in ${delay}ms`\n );\n this.gcTask = this.asyncQueue.enqueueAfterDelay(\n TimerId.LruGarbageCollection,\n delay,\n async () => {\n this.gcTask = null;\n this.hasRun = true;\n try {\n await localStore.collectGarbage(this.garbageCollector);\n } catch (e) {\n if (isIndexedDbTransactionError(e)) {\n logDebug(\n LOG_TAG,\n 'Ignoring IndexedDB error during garbage collection: ',\n e\n );\n } else {\n await ignoreIfPrimaryLeaseLoss(e);\n }\n }\n await this.scheduleGC(localStore);\n }\n );\n }\n}\n\n/** Implements the steps for LRU garbage collection. */\nexport class LruGarbageCollector {\n constructor(\n private readonly delegate: LruDelegate,\n readonly params: LruParams\n ) {}\n\n /** Given a percentile of target to collect, returns the number of targets to collect. */\n calculateTargetCount(\n txn: PersistenceTransaction,\n percentile: number\n ): PersistencePromise {\n return this.delegate.getSequenceNumberCount(txn).next(targetCount => {\n return Math.floor((percentile / 100.0) * targetCount);\n });\n }\n\n /** Returns the nth sequence number, counting in order from the smallest. */\n nthSequenceNumber(\n txn: PersistenceTransaction,\n n: number\n ): PersistencePromise {\n if (n === 0) {\n return PersistencePromise.resolve(ListenSequence.INVALID);\n }\n\n const buffer = new RollingSequenceNumberBuffer(n);\n return this.delegate\n .forEachTarget(txn, target => buffer.addElement(target.sequenceNumber))\n .next(() => {\n return this.delegate.forEachOrphanedDocumentSequenceNumber(\n txn,\n sequenceNumber => buffer.addElement(sequenceNumber)\n );\n })\n .next(() => buffer.maxValue);\n }\n\n /**\n * Removes targets with a sequence number equal to or less than the given upper bound, and removes\n * document associations with those targets.\n */\n removeTargets(\n txn: PersistenceTransaction,\n upperBound: ListenSequenceNumber,\n activeTargetIds: ActiveTargets\n ): PersistencePromise {\n return this.delegate.removeTargets(txn, upperBound, activeTargetIds);\n }\n\n /**\n * Removes documents that have a sequence number equal to or less than the upper bound and are not\n * otherwise pinned.\n */\n removeOrphanedDocuments(\n txn: PersistenceTransaction,\n upperBound: ListenSequenceNumber\n ): PersistencePromise {\n return this.delegate.removeOrphanedDocuments(txn, upperBound);\n }\n\n collect(\n txn: PersistenceTransaction,\n activeTargetIds: ActiveTargets\n ): PersistencePromise {\n if (\n this.params.cacheSizeCollectionThreshold === LruParams.COLLECTION_DISABLED\n ) {\n logDebug('LruGarbageCollector', 'Garbage collection skipped; disabled');\n return PersistencePromise.resolve(GC_DID_NOT_RUN);\n }\n\n return this.getCacheSize(txn).next(cacheSize => {\n if (cacheSize < this.params.cacheSizeCollectionThreshold) {\n logDebug(\n 'LruGarbageCollector',\n `Garbage collection skipped; Cache size ${cacheSize} ` +\n `is lower than threshold ${this.params.cacheSizeCollectionThreshold}`\n );\n return GC_DID_NOT_RUN;\n } else {\n return this.runGarbageCollection(txn, activeTargetIds);\n }\n });\n }\n\n getCacheSize(txn: PersistenceTransaction): PersistencePromise {\n return this.delegate.getCacheSize(txn);\n }\n\n private runGarbageCollection(\n txn: PersistenceTransaction,\n activeTargetIds: ActiveTargets\n ): PersistencePromise {\n let upperBoundSequenceNumber: number;\n let sequenceNumbersToCollect: number, targetsRemoved: number;\n // Timestamps for various pieces of the process\n let countedTargetsTs: number,\n foundUpperBoundTs: number,\n removedTargetsTs: number,\n removedDocumentsTs: number;\n const startTs = Date.now();\n return this.calculateTargetCount(txn, this.params.percentileToCollect)\n .next(sequenceNumbers => {\n // Cap at the configured max\n if (sequenceNumbers > this.params.maximumSequenceNumbersToCollect) {\n logDebug(\n 'LruGarbageCollector',\n 'Capping sequence numbers to collect down ' +\n `to the maximum of ${this.params.maximumSequenceNumbersToCollect} ` +\n `from ${sequenceNumbers}`\n );\n sequenceNumbersToCollect = this.params\n .maximumSequenceNumbersToCollect;\n } else {\n sequenceNumbersToCollect = sequenceNumbers;\n }\n countedTargetsTs = Date.now();\n\n return this.nthSequenceNumber(txn, sequenceNumbersToCollect);\n })\n .next(upperBound => {\n upperBoundSequenceNumber = upperBound;\n foundUpperBoundTs = Date.now();\n\n return this.removeTargets(\n txn,\n upperBoundSequenceNumber,\n activeTargetIds\n );\n })\n .next(numTargetsRemoved => {\n targetsRemoved = numTargetsRemoved;\n removedTargetsTs = Date.now();\n\n return this.removeOrphanedDocuments(txn, upperBoundSequenceNumber);\n })\n .next(documentsRemoved => {\n removedDocumentsTs = Date.now();\n\n if (getLogLevel() <= LogLevel.DEBUG) {\n const desc =\n 'LRU Garbage Collection\\n' +\n `\\tCounted targets in ${countedTargetsTs - startTs}ms\\n` +\n `\\tDetermined least recently used ${sequenceNumbersToCollect} in ` +\n `${foundUpperBoundTs - countedTargetsTs}ms\\n` +\n `\\tRemoved ${targetsRemoved} targets in ` +\n `${removedTargetsTs - foundUpperBoundTs}ms\\n` +\n `\\tRemoved ${documentsRemoved} documents in ` +\n `${removedDocumentsTs - removedTargetsTs}ms\\n` +\n `Total Duration: ${removedDocumentsTs - startTs}ms`;\n logDebug('LruGarbageCollector', desc);\n }\n\n return PersistencePromise.resolve({\n didRun: true,\n sequenceNumbersCollected: sequenceNumbersToCollect,\n targetsRemoved,\n documentsRemoved\n });\n });\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Timestamp } from '../api/timestamp';\nimport { User } from '../auth/user';\nimport { Query } from '../core/query';\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { canonifyTarget, Target, targetEquals } from '../core/target';\nimport { BatchId, TargetId } from '../core/types';\nimport {\n DocumentKeySet,\n documentKeySet,\n DocumentMap,\n maybeDocumentMap,\n MaybeDocumentMap\n} from '../model/collections';\nimport { MaybeDocument, NoDocument } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport {\n Mutation,\n PatchMutation,\n Precondition,\n extractMutationBaseValue\n} from '../model/mutation';\nimport {\n BATCHID_UNKNOWN,\n MutationBatch,\n MutationBatchResult\n} from '../model/mutation_batch';\nimport { RemoteEvent, TargetChange } from '../remote/remote_event';\nimport { debugAssert, hardAssert } from '../util/assert';\nimport { Code, FirestoreError } from '../util/error';\nimport { logDebug } from '../util/log';\nimport { primitiveComparator } from '../util/misc';\nimport { ObjectMap } from '../util/obj_map';\nimport { SortedMap } from '../util/sorted_map';\n\nimport { LocalDocumentsView } from './local_documents_view';\nimport { LocalViewChanges } from './local_view_changes';\nimport { LruGarbageCollector, LruResults } from './lru_garbage_collector';\nimport { MutationQueue } from './mutation_queue';\nimport {\n Persistence,\n PersistenceTransaction,\n PRIMARY_LEASE_LOST_ERROR_MSG\n} from './persistence';\nimport { PersistencePromise } from './persistence_promise';\nimport { TargetCache } from './target_cache';\nimport { QueryEngine } from './query_engine';\nimport { RemoteDocumentCache } from './remote_document_cache';\nimport { RemoteDocumentChangeBuffer } from './remote_document_change_buffer';\nimport { ClientId } from './shared_client_state';\nimport { TargetData, TargetPurpose } from './target_data';\nimport { IndexedDbPersistence } from './indexeddb_persistence';\nimport { IndexedDbMutationQueue } from './indexeddb_mutation_queue';\nimport { IndexedDbRemoteDocumentCache } from './indexeddb_remote_document_cache';\nimport { IndexedDbTargetCache } from './indexeddb_target_cache';\nimport { extractFieldMask } from '../model/object_value';\nimport { isIndexedDbTransactionError } from './simple_db';\n\nconst LOG_TAG = 'LocalStore';\n\n/** The result of a write to the local store. */\nexport interface LocalWriteResult {\n batchId: BatchId;\n changes: MaybeDocumentMap;\n}\n\n/** The result of a user-change operation in the local store. */\nexport interface UserChangeResult {\n readonly affectedDocuments: MaybeDocumentMap;\n readonly removedBatchIds: BatchId[];\n readonly addedBatchIds: BatchId[];\n}\n\n/** The result of executing a query against the local store. */\nexport interface QueryResult {\n readonly documents: DocumentMap;\n readonly remoteKeys: DocumentKeySet;\n}\n\n/**\n * Local storage in the Firestore client. Coordinates persistence components\n * like the mutation queue and remote document cache to present a\n * latency-compensated view of stored data.\n *\n * The LocalStore is responsible for accepting mutations from the Sync Engine.\n * Writes from the client are put into a queue as provisional Mutations until\n * they are processed by the RemoteStore and confirmed as having been written\n * to the server.\n *\n * The local store provides the local version of documents that have been\n * modified locally. It maintains the constraint:\n *\n * LocalDocument = RemoteDocument + Active(LocalMutations)\n *\n * (Active mutations are those that are enqueued and have not been previously\n * acknowledged or rejected).\n *\n * The RemoteDocument (\"ground truth\") state is provided via the\n * applyChangeBatch method. It will be some version of a server-provided\n * document OR will be a server-provided document PLUS acknowledged mutations:\n *\n * RemoteDocument' = RemoteDocument + Acknowledged(LocalMutations)\n *\n * Note that this \"dirty\" version of a RemoteDocument will not be identical to a\n * server base version, since it has LocalMutations added to it pending getting\n * an authoritative copy from the server.\n *\n * Since LocalMutations can be rejected by the server, we have to be able to\n * revert a LocalMutation that has already been applied to the LocalDocument\n * (typically done by replaying all remaining LocalMutations to the\n * RemoteDocument to re-apply).\n *\n * The LocalStore is responsible for the garbage collection of the documents it\n * contains. For now, it every doc referenced by a view, the mutation queue, or\n * the RemoteStore.\n *\n * It also maintains the persistence of mapping queries to resume tokens and\n * target ids. It needs to know this data about queries to properly know what\n * docs it would be allowed to garbage collect.\n *\n * The LocalStore must be able to efficiently execute queries against its local\n * cache of the documents, to provide the initial set of results before any\n * remote changes have been received.\n *\n * Note: In TypeScript, most methods return Promises since the implementation\n * may rely on fetching data from IndexedDB which is async.\n * These Promises will only be rejected on an I/O error or other internal\n * (unexpected) failure (e.g. failed assert) and always represent an\n * unrecoverable error (should be caught / reported by the async_queue).\n */\nexport interface LocalStore {\n /** Starts the LocalStore. */\n start(): Promise;\n\n /**\n * Tells the LocalStore that the currently authenticated user has changed.\n *\n * In response the local store switches the mutation queue to the new user and\n * returns any resulting document changes.\n */\n // PORTING NOTE: Android and iOS only return the documents affected by the\n // change.\n handleUserChange(user: User): Promise;\n\n /* Accept locally generated Mutations and commit them to storage. */\n localWrite(mutations: Mutation[]): Promise;\n\n /**\n * Acknowledge the given batch.\n *\n * On the happy path when a batch is acknowledged, the local store will\n *\n * + remove the batch from the mutation queue;\n * + apply the changes to the remote document cache;\n * + recalculate the latency compensated view implied by those changes (there\n * may be mutations in the queue that affect the documents but haven't been\n * acknowledged yet); and\n * + give the changed documents back the sync engine\n *\n * @returns The resulting (modified) documents.\n */\n acknowledgeBatch(batchResult: MutationBatchResult): Promise;\n\n /**\n * Remove mutations from the MutationQueue for the specified batch;\n * LocalDocuments will be recalculated.\n *\n * @returns The resulting modified documents.\n */\n rejectBatch(batchId: BatchId): Promise;\n\n /**\n * Returns the largest (latest) batch id in mutation queue that is pending\n * server response.\n *\n * Returns `BATCHID_UNKNOWN` if the queue is empty.\n */\n getHighestUnacknowledgedBatchId(): Promise;\n\n /**\n * Returns the last consistent snapshot processed (used by the RemoteStore to\n * determine whether to buffer incoming snapshots from the backend).\n */\n getLastRemoteSnapshotVersion(): Promise;\n\n /**\n * Update the \"ground-state\" (remote) documents. We assume that the remote\n * event reflects any write batches that have been acknowledged or rejected\n * (i.e. we do not re-apply local mutations to updates from this event).\n *\n * LocalDocuments are re-calculated if there are remaining mutations in the\n * queue.\n */\n applyRemoteEvent(remoteEvent: RemoteEvent): Promise;\n\n /**\n * Notify local store of the changed views to locally pin documents.\n */\n notifyLocalViewChanges(viewChanges: LocalViewChanges[]): Promise;\n\n /**\n * Gets the mutation batch after the passed in batchId in the mutation queue\n * or null if empty.\n * @param afterBatchId If provided, the batch to search after.\n * @returns The next mutation or null if there wasn't one.\n */\n nextMutationBatch(afterBatchId?: BatchId): Promise;\n\n /**\n * Read the current value of a Document with a given key or null if not\n * found - used for testing.\n */\n readDocument(key: DocumentKey): Promise;\n\n /**\n * Assigns the given target an internal ID so that its results can be pinned so\n * they don't get GC'd. A target must be allocated in the local store before\n * the store can be used to manage its view.\n *\n * Allocating an already allocated `Target` will return the existing `TargetData`\n * for that `Target`.\n */\n allocateTarget(target: Target): Promise;\n\n /**\n * Returns the TargetData as seen by the LocalStore, including updates that may\n * have not yet been persisted to the TargetCache.\n */\n // Visible for testing.\n getTargetData(\n transaction: PersistenceTransaction,\n target: Target\n ): PersistencePromise;\n\n /**\n * Unpin all the documents associated with the given target. If\n * `keepPersistedTargetData` is set to false and Eager GC enabled, the method\n * directly removes the associated target data from the target cache.\n *\n * Releasing a non-existing `Target` is a no-op.\n */\n // PORTING NOTE: `keepPersistedTargetData` is multi-tab only.\n releaseTarget(\n targetId: number,\n keepPersistedTargetData: boolean\n ): Promise;\n\n /**\n * Runs the specified query against the local store and returns the results,\n * potentially taking advantage of query data from previous executions (such\n * as the set of remote keys).\n *\n * @param usePreviousResults Whether results from previous executions can\n * be used to optimize this query execution.\n */\n executeQuery(query: Query, usePreviousResults: boolean): Promise;\n\n collectGarbage(garbageCollector: LruGarbageCollector): Promise;\n}\n\n/**\n * Implements `LocalStore` interface.\n *\n * Note: some field defined in this class might have public access level, but\n * the class is not exported so they are only accessible from this module.\n * This is useful to implement optional features (like bundles) in free\n * functions, such that they are tree-shakeable.\n */\nclass LocalStoreImpl implements LocalStore {\n /**\n * The maximum time to leave a resume token buffered without writing it out.\n * This value is arbitrary: it's long enough to avoid several writes\n * (possibly indefinitely if updates come more frequently than this) but\n * short enough that restarting after crashing will still have a pretty\n * recent resume token.\n */\n private static readonly RESUME_TOKEN_MAX_AGE_MICROS = 5 * 60 * 1e6;\n\n /**\n * The set of all mutations that have been sent but not yet been applied to\n * the backend.\n */\n protected mutationQueue: MutationQueue;\n\n /** The set of all cached remote documents. */\n protected remoteDocuments: RemoteDocumentCache;\n\n /**\n * The \"local\" view of all documents (layering mutationQueue on top of\n * remoteDocumentCache).\n */\n protected localDocuments: LocalDocumentsView;\n\n /** Maps a target to its `TargetData`. */\n protected targetCache: TargetCache;\n\n /**\n * Maps a targetID to data about its target.\n *\n * PORTING NOTE: We are using an immutable data structure on Web to make re-runs\n * of `applyRemoteEvent()` idempotent.\n */\n protected targetDataByTarget = new SortedMap(\n primitiveComparator\n );\n\n /** Maps a target to its targetID. */\n // TODO(wuandy): Evaluate if TargetId can be part of Target.\n private targetIdByTarget = new ObjectMap(\n t => canonifyTarget(t),\n targetEquals\n );\n\n /**\n * The read time of the last entry processed by `getNewDocumentChanges()`.\n *\n * PORTING NOTE: This is only used for multi-tab synchronization.\n */\n protected lastDocumentChangeReadTime = SnapshotVersion.min();\n\n constructor(\n /** Manages our in-memory or durable persistence. */\n protected persistence: Persistence,\n private queryEngine: QueryEngine,\n initialUser: User\n ) {\n debugAssert(\n persistence.started,\n 'LocalStore was passed an unstarted persistence implementation'\n );\n this.mutationQueue = persistence.getMutationQueue(initialUser);\n this.remoteDocuments = persistence.getRemoteDocumentCache();\n this.targetCache = persistence.getTargetCache();\n this.localDocuments = new LocalDocumentsView(\n this.remoteDocuments,\n this.mutationQueue,\n this.persistence.getIndexManager()\n );\n this.queryEngine.setLocalDocumentsView(this.localDocuments);\n }\n\n start(): Promise {\n return Promise.resolve();\n }\n\n async handleUserChange(user: User): Promise {\n let newMutationQueue = this.mutationQueue;\n let newLocalDocuments = this.localDocuments;\n\n const result = await this.persistence.runTransaction(\n 'Handle user change',\n 'readonly',\n txn => {\n // Swap out the mutation queue, grabbing the pending mutation batches\n // before and after.\n let oldBatches: MutationBatch[];\n return this.mutationQueue\n .getAllMutationBatches(txn)\n .next(promisedOldBatches => {\n oldBatches = promisedOldBatches;\n\n newMutationQueue = this.persistence.getMutationQueue(user);\n\n // Recreate our LocalDocumentsView using the new\n // MutationQueue.\n newLocalDocuments = new LocalDocumentsView(\n this.remoteDocuments,\n newMutationQueue,\n this.persistence.getIndexManager()\n );\n return newMutationQueue.getAllMutationBatches(txn);\n })\n .next(newBatches => {\n const removedBatchIds: BatchId[] = [];\n const addedBatchIds: BatchId[] = [];\n\n // Union the old/new changed keys.\n let changedKeys = documentKeySet();\n\n for (const batch of oldBatches) {\n removedBatchIds.push(batch.batchId);\n for (const mutation of batch.mutations) {\n changedKeys = changedKeys.add(mutation.key);\n }\n }\n\n for (const batch of newBatches) {\n addedBatchIds.push(batch.batchId);\n for (const mutation of batch.mutations) {\n changedKeys = changedKeys.add(mutation.key);\n }\n }\n\n // Return the set of all (potentially) changed documents and the list\n // of mutation batch IDs that were affected by change.\n return newLocalDocuments\n .getDocuments(txn, changedKeys)\n .next(affectedDocuments => {\n return {\n affectedDocuments,\n removedBatchIds,\n addedBatchIds\n };\n });\n });\n }\n );\n\n this.mutationQueue = newMutationQueue;\n this.localDocuments = newLocalDocuments;\n this.queryEngine.setLocalDocumentsView(this.localDocuments);\n\n return result;\n }\n\n localWrite(mutations: Mutation[]): Promise {\n const localWriteTime = Timestamp.now();\n const keys = mutations.reduce(\n (keys, m) => keys.add(m.key),\n documentKeySet()\n );\n\n let existingDocs: MaybeDocumentMap;\n\n return this.persistence\n .runTransaction('Locally write mutations', 'readwrite', txn => {\n // Load and apply all existing mutations. This lets us compute the\n // current base state for all non-idempotent transforms before applying\n // any additional user-provided writes.\n return this.localDocuments.getDocuments(txn, keys).next(docs => {\n existingDocs = docs;\n\n // For non-idempotent mutations (such as `FieldValue.increment()`),\n // we record the base state in a separate patch mutation. This is\n // later used to guarantee consistent values and prevents flicker\n // even if the backend sends us an update that already includes our\n // transform.\n const baseMutations: Mutation[] = [];\n\n for (const mutation of mutations) {\n const baseValue = extractMutationBaseValue(\n mutation,\n existingDocs.get(mutation.key)\n );\n if (baseValue != null) {\n // NOTE: The base state should only be applied if there's some\n // existing document to override, so use a Precondition of\n // exists=true\n baseMutations.push(\n new PatchMutation(\n mutation.key,\n baseValue,\n extractFieldMask(baseValue.proto.mapValue!),\n Precondition.exists(true)\n )\n );\n }\n }\n\n return this.mutationQueue.addMutationBatch(\n txn,\n localWriteTime,\n baseMutations,\n mutations\n );\n });\n })\n .then(batch => {\n const changes = batch.applyToLocalDocumentSet(existingDocs);\n return { batchId: batch.batchId, changes };\n });\n }\n\n acknowledgeBatch(\n batchResult: MutationBatchResult\n ): Promise {\n return this.persistence.runTransaction(\n 'Acknowledge batch',\n 'readwrite-primary',\n txn => {\n const affected = batchResult.batch.keys();\n const documentBuffer = this.remoteDocuments.newChangeBuffer({\n trackRemovals: true // Make sure document removals show up in `getNewDocumentChanges()`\n });\n return this.applyWriteToRemoteDocuments(\n txn,\n batchResult,\n documentBuffer\n )\n .next(() => documentBuffer.apply(txn))\n .next(() => this.mutationQueue.performConsistencyCheck(txn))\n .next(() => this.localDocuments.getDocuments(txn, affected));\n }\n );\n }\n\n rejectBatch(batchId: BatchId): Promise {\n return this.persistence.runTransaction(\n 'Reject batch',\n 'readwrite-primary',\n txn => {\n let affectedKeys: DocumentKeySet;\n return this.mutationQueue\n .lookupMutationBatch(txn, batchId)\n .next((batch: MutationBatch | null) => {\n hardAssert(batch !== null, 'Attempt to reject nonexistent batch!');\n affectedKeys = batch.keys();\n return this.mutationQueue.removeMutationBatch(txn, batch);\n })\n .next(() => {\n return this.mutationQueue.performConsistencyCheck(txn);\n })\n .next(() => {\n return this.localDocuments.getDocuments(txn, affectedKeys);\n });\n }\n );\n }\n\n getHighestUnacknowledgedBatchId(): Promise {\n return this.persistence.runTransaction(\n 'Get highest unacknowledged batch id',\n 'readonly',\n txn => {\n return this.mutationQueue.getHighestUnacknowledgedBatchId(txn);\n }\n );\n }\n\n getLastRemoteSnapshotVersion(): Promise {\n return this.persistence.runTransaction(\n 'Get last remote snapshot version',\n 'readonly',\n txn => this.targetCache.getLastRemoteSnapshotVersion(txn)\n );\n }\n\n applyRemoteEvent(remoteEvent: RemoteEvent): Promise {\n const remoteVersion = remoteEvent.snapshotVersion;\n let newTargetDataByTargetMap = this.targetDataByTarget;\n\n return this.persistence\n .runTransaction('Apply remote event', 'readwrite-primary', txn => {\n const documentBuffer = this.remoteDocuments.newChangeBuffer({\n trackRemovals: true // Make sure document removals show up in `getNewDocumentChanges()`\n });\n\n // Reset newTargetDataByTargetMap in case this transaction gets re-run.\n newTargetDataByTargetMap = this.targetDataByTarget;\n\n const promises = [] as Array>;\n remoteEvent.targetChanges.forEach((change, targetId) => {\n const oldTargetData = newTargetDataByTargetMap.get(targetId);\n if (!oldTargetData) {\n return;\n }\n\n // Only update the remote keys if the target is still active. This\n // ensures that we can persist the updated target data along with\n // the updated assignment.\n promises.push(\n this.targetCache\n .removeMatchingKeys(txn, change.removedDocuments, targetId)\n .next(() => {\n return this.targetCache.addMatchingKeys(\n txn,\n change.addedDocuments,\n targetId\n );\n })\n );\n\n const resumeToken = change.resumeToken;\n // Update the resume token if the change includes one.\n if (resumeToken.approximateByteSize() > 0) {\n const newTargetData = oldTargetData\n .withResumeToken(resumeToken, remoteVersion)\n .withSequenceNumber(txn.currentSequenceNumber);\n newTargetDataByTargetMap = newTargetDataByTargetMap.insert(\n targetId,\n newTargetData\n );\n\n // Update the target data if there are target changes (or if\n // sufficient time has passed since the last update).\n if (\n LocalStoreImpl.shouldPersistTargetData(\n oldTargetData,\n newTargetData,\n change\n )\n ) {\n promises.push(\n this.targetCache.updateTargetData(txn, newTargetData)\n );\n }\n }\n });\n\n let changedDocs = maybeDocumentMap();\n let updatedKeys = documentKeySet();\n remoteEvent.documentUpdates.forEach((key, doc) => {\n updatedKeys = updatedKeys.add(key);\n });\n\n // Each loop iteration only affects its \"own\" doc, so it's safe to get all the remote\n // documents in advance in a single call.\n promises.push(\n documentBuffer.getEntries(txn, updatedKeys).next(existingDocs => {\n remoteEvent.documentUpdates.forEach((key, doc) => {\n const existingDoc = existingDocs.get(key);\n\n // Note: The order of the steps below is important, since we want\n // to ensure that rejected limbo resolutions (which fabricate\n // NoDocuments with SnapshotVersion.min()) never add documents to\n // cache.\n if (\n doc instanceof NoDocument &&\n doc.version.isEqual(SnapshotVersion.min())\n ) {\n // NoDocuments with SnapshotVersion.min() are used in manufactured\n // events. We remove these documents from cache since we lost\n // access.\n documentBuffer.removeEntry(key, remoteVersion);\n changedDocs = changedDocs.insert(key, doc);\n } else if (\n existingDoc == null ||\n doc.version.compareTo(existingDoc.version) > 0 ||\n (doc.version.compareTo(existingDoc.version) === 0 &&\n existingDoc.hasPendingWrites)\n ) {\n debugAssert(\n !SnapshotVersion.min().isEqual(remoteVersion),\n 'Cannot add a document when the remote version is zero'\n );\n documentBuffer.addEntry(doc, remoteVersion);\n changedDocs = changedDocs.insert(key, doc);\n } else {\n logDebug(\n LOG_TAG,\n 'Ignoring outdated watch update for ',\n key,\n '. Current version:',\n existingDoc.version,\n ' Watch version:',\n doc.version\n );\n }\n\n if (remoteEvent.resolvedLimboDocuments.has(key)) {\n promises.push(\n this.persistence.referenceDelegate.updateLimboDocument(\n txn,\n key\n )\n );\n }\n });\n })\n );\n\n // HACK: The only reason we allow a null snapshot version is so that we\n // can synthesize remote events when we get permission denied errors while\n // trying to resolve the state of a locally cached document that is in\n // limbo.\n if (!remoteVersion.isEqual(SnapshotVersion.min())) {\n const updateRemoteVersion = this.targetCache\n .getLastRemoteSnapshotVersion(txn)\n .next(lastRemoteSnapshotVersion => {\n debugAssert(\n remoteVersion.compareTo(lastRemoteSnapshotVersion) >= 0,\n 'Watch stream reverted to previous snapshot?? ' +\n remoteVersion +\n ' < ' +\n lastRemoteSnapshotVersion\n );\n return this.targetCache.setTargetsMetadata(\n txn,\n txn.currentSequenceNumber,\n remoteVersion\n );\n });\n promises.push(updateRemoteVersion);\n }\n\n return PersistencePromise.waitFor(promises)\n .next(() => documentBuffer.apply(txn))\n .next(() => {\n return this.localDocuments.getLocalViewOfDocuments(\n txn,\n changedDocs\n );\n });\n })\n .then(changedDocs => {\n this.targetDataByTarget = newTargetDataByTargetMap;\n return changedDocs;\n });\n }\n\n /**\n * Returns true if the newTargetData should be persisted during an update of\n * an active target. TargetData should always be persisted when a target is\n * being released and should not call this function.\n *\n * While the target is active, TargetData updates can be omitted when nothing\n * about the target has changed except metadata like the resume token or\n * snapshot version. Occasionally it's worth the extra write to prevent these\n * values from getting too stale after a crash, but this doesn't have to be\n * too frequent.\n */\n private static shouldPersistTargetData(\n oldTargetData: TargetData,\n newTargetData: TargetData,\n change: TargetChange\n ): boolean {\n hardAssert(\n newTargetData.resumeToken.approximateByteSize() > 0,\n 'Attempted to persist target data with no resume token'\n );\n\n // Always persist target data if we don't already have a resume token.\n if (oldTargetData.resumeToken.approximateByteSize() === 0) {\n return true;\n }\n\n // Don't allow resume token changes to be buffered indefinitely. This\n // allows us to be reasonably up-to-date after a crash and avoids needing\n // to loop over all active queries on shutdown. Especially in the browser\n // we may not get time to do anything interesting while the current tab is\n // closing.\n const timeDelta =\n newTargetData.snapshotVersion.toMicroseconds() -\n oldTargetData.snapshotVersion.toMicroseconds();\n if (timeDelta >= this.RESUME_TOKEN_MAX_AGE_MICROS) {\n return true;\n }\n\n // Otherwise if the only thing that has changed about a target is its resume\n // token it's not worth persisting. Note that the RemoteStore keeps an\n // in-memory view of the currently active targets which includes the current\n // resume token, so stream failure or user changes will still use an\n // up-to-date resume token regardless of what we do here.\n const changes =\n change.addedDocuments.size +\n change.modifiedDocuments.size +\n change.removedDocuments.size;\n return changes > 0;\n }\n\n async notifyLocalViewChanges(viewChanges: LocalViewChanges[]): Promise {\n try {\n await this.persistence.runTransaction(\n 'notifyLocalViewChanges',\n 'readwrite',\n txn => {\n return PersistencePromise.forEach(\n viewChanges,\n (viewChange: LocalViewChanges) => {\n return PersistencePromise.forEach(\n viewChange.addedKeys,\n (key: DocumentKey) =>\n this.persistence.referenceDelegate.addReference(\n txn,\n viewChange.targetId,\n key\n )\n ).next(() =>\n PersistencePromise.forEach(\n viewChange.removedKeys,\n (key: DocumentKey) =>\n this.persistence.referenceDelegate.removeReference(\n txn,\n viewChange.targetId,\n key\n )\n )\n );\n }\n );\n }\n );\n } catch (e) {\n if (isIndexedDbTransactionError(e)) {\n // If `notifyLocalViewChanges` fails, we did not advance the sequence\n // number for the documents that were included in this transaction.\n // This might trigger them to be deleted earlier than they otherwise\n // would have, but it should not invalidate the integrity of the data.\n logDebug(LOG_TAG, 'Failed to update sequence numbers: ' + e);\n } else {\n throw e;\n }\n }\n\n for (const viewChange of viewChanges) {\n const targetId = viewChange.targetId;\n\n if (!viewChange.fromCache) {\n const targetData = this.targetDataByTarget.get(targetId);\n debugAssert(\n targetData !== null,\n `Can't set limbo-free snapshot version for unknown target: ${targetId}`\n );\n\n // Advance the last limbo free snapshot version\n const lastLimboFreeSnapshotVersion = targetData.snapshotVersion;\n const updatedTargetData = targetData.withLastLimboFreeSnapshotVersion(\n lastLimboFreeSnapshotVersion\n );\n this.targetDataByTarget = this.targetDataByTarget.insert(\n targetId,\n updatedTargetData\n );\n }\n }\n }\n\n nextMutationBatch(afterBatchId?: BatchId): Promise {\n return this.persistence.runTransaction(\n 'Get next mutation batch',\n 'readonly',\n txn => {\n if (afterBatchId === undefined) {\n afterBatchId = BATCHID_UNKNOWN;\n }\n return this.mutationQueue.getNextMutationBatchAfterBatchId(\n txn,\n afterBatchId\n );\n }\n );\n }\n\n readDocument(key: DocumentKey): Promise {\n return this.persistence.runTransaction('read document', 'readonly', txn => {\n return this.localDocuments.getDocument(txn, key);\n });\n }\n\n allocateTarget(target: Target): Promise {\n return this.persistence\n .runTransaction('Allocate target', 'readwrite', txn => {\n let targetData: TargetData;\n return this.targetCache\n .getTargetData(txn, target)\n .next((cached: TargetData | null) => {\n if (cached) {\n // This target has been listened to previously, so reuse the\n // previous targetID.\n // TODO(mcg): freshen last accessed date?\n targetData = cached;\n return PersistencePromise.resolve(targetData);\n } else {\n return this.targetCache.allocateTargetId(txn).next(targetId => {\n targetData = new TargetData(\n target,\n targetId,\n TargetPurpose.Listen,\n txn.currentSequenceNumber\n );\n return this.targetCache\n .addTargetData(txn, targetData)\n .next(() => targetData);\n });\n }\n });\n })\n .then(targetData => {\n // If Multi-Tab is enabled, the existing target data may be newer than\n // the in-memory data\n const cachedTargetData = this.targetDataByTarget.get(\n targetData.targetId\n );\n if (\n cachedTargetData === null ||\n targetData.snapshotVersion.compareTo(\n cachedTargetData.snapshotVersion\n ) > 0\n ) {\n this.targetDataByTarget = this.targetDataByTarget.insert(\n targetData.targetId,\n targetData\n );\n this.targetIdByTarget.set(target, targetData.targetId);\n }\n return targetData;\n });\n }\n\n getTargetData(\n transaction: PersistenceTransaction,\n target: Target\n ): PersistencePromise {\n const targetId = this.targetIdByTarget.get(target);\n if (targetId !== undefined) {\n return PersistencePromise.resolve(\n this.targetDataByTarget.get(targetId)\n );\n } else {\n return this.targetCache.getTargetData(transaction, target);\n }\n }\n\n async releaseTarget(\n targetId: number,\n keepPersistedTargetData: boolean\n ): Promise {\n const targetData = this.targetDataByTarget.get(targetId);\n debugAssert(\n targetData !== null,\n `Tried to release nonexistent target: ${targetId}`\n );\n\n const mode = keepPersistedTargetData ? 'readwrite' : 'readwrite-primary';\n\n try {\n if (!keepPersistedTargetData) {\n await this.persistence.runTransaction('Release target', mode, txn => {\n return this.persistence.referenceDelegate.removeTarget(\n txn,\n targetData!\n );\n });\n }\n } catch (e) {\n if (isIndexedDbTransactionError(e)) {\n // All `releaseTarget` does is record the final metadata state for the\n // target, but we've been recording this periodically during target\n // activity. If we lose this write this could cause a very slight\n // difference in the order of target deletion during GC, but we\n // don't define exact LRU semantics so this is acceptable.\n logDebug(\n LOG_TAG,\n `Failed to update sequence numbers for target ${targetId}: ${e}`\n );\n } else {\n throw e;\n }\n }\n\n this.targetDataByTarget = this.targetDataByTarget.remove(targetId);\n this.targetIdByTarget.delete(targetData!.target);\n }\n\n executeQuery(\n query: Query,\n usePreviousResults: boolean\n ): Promise {\n let lastLimboFreeSnapshotVersion = SnapshotVersion.min();\n let remoteKeys = documentKeySet();\n\n return this.persistence.runTransaction('Execute query', 'readonly', txn => {\n return this.getTargetData(txn, query.toTarget())\n .next(targetData => {\n if (targetData) {\n lastLimboFreeSnapshotVersion =\n targetData.lastLimboFreeSnapshotVersion;\n return this.targetCache\n .getMatchingKeysForTargetId(txn, targetData.targetId)\n .next(result => {\n remoteKeys = result;\n });\n }\n })\n .next(() =>\n this.queryEngine.getDocumentsMatchingQuery(\n txn,\n query,\n usePreviousResults\n ? lastLimboFreeSnapshotVersion\n : SnapshotVersion.min(),\n usePreviousResults ? remoteKeys : documentKeySet()\n )\n )\n .next(documents => {\n return { documents, remoteKeys };\n });\n });\n }\n\n private applyWriteToRemoteDocuments(\n txn: PersistenceTransaction,\n batchResult: MutationBatchResult,\n documentBuffer: RemoteDocumentChangeBuffer\n ): PersistencePromise {\n const batch = batchResult.batch;\n const docKeys = batch.keys();\n let promiseChain = PersistencePromise.resolve();\n docKeys.forEach(docKey => {\n promiseChain = promiseChain\n .next(() => {\n return documentBuffer.getEntry(txn, docKey);\n })\n .next((remoteDoc: MaybeDocument | null) => {\n let doc = remoteDoc;\n const ackVersion = batchResult.docVersions.get(docKey);\n hardAssert(\n ackVersion !== null,\n 'ackVersions should contain every doc in the write.'\n );\n if (!doc || doc.version.compareTo(ackVersion!) < 0) {\n doc = batch.applyToRemoteDocument(docKey, doc, batchResult);\n if (!doc) {\n debugAssert(\n !remoteDoc,\n 'Mutation batch ' +\n batch +\n ' applied to document ' +\n remoteDoc +\n ' resulted in null'\n );\n } else {\n // We use the commitVersion as the readTime rather than the\n // document's updateTime since the updateTime is not advanced\n // for updates that do not modify the underlying document.\n documentBuffer.addEntry(doc, batchResult.commitVersion);\n }\n }\n });\n });\n return promiseChain.next(() =>\n this.mutationQueue.removeMutationBatch(txn, batch)\n );\n }\n\n collectGarbage(garbageCollector: LruGarbageCollector): Promise {\n return this.persistence.runTransaction(\n 'Collect garbage',\n 'readwrite-primary',\n txn => garbageCollector.collect(txn, this.targetDataByTarget)\n );\n }\n}\n\nexport function newLocalStore(\n /** Manages our in-memory or durable persistence. */\n persistence: Persistence,\n queryEngine: QueryEngine,\n initialUser: User\n): LocalStore {\n return new LocalStoreImpl(persistence, queryEngine, initialUser);\n}\n\n/**\n * An interface on top of LocalStore that provides additional functionality\n * for MultiTabSyncEngine.\n */\nexport interface MultiTabLocalStore extends LocalStore {\n /** Returns the local view of the documents affected by a mutation batch. */\n lookupMutationDocuments(batchId: BatchId): Promise;\n\n removeCachedMutationBatchMetadata(batchId: BatchId): void;\n\n setNetworkEnabled(networkEnabled: boolean): void;\n\n getActiveClients(): Promise;\n\n getTarget(targetId: TargetId): Promise;\n\n /**\n * Returns the set of documents that have been updated since the last call.\n * If this is the first call, returns the set of changes since client\n * initialization. Further invocations will return document changes since\n * the point of rejection.\n */\n getNewDocumentChanges(): Promise;\n\n /**\n * Reads the newest document change from persistence and forwards the internal\n * synchronization marker so that calls to `getNewDocumentChanges()`\n * only return changes that happened after client initialization.\n */\n synchronizeLastDocumentChangeReadTime(): Promise;\n}\n\n/**\n * An implementation of LocalStore that provides additional functionality\n * for MultiTabSyncEngine.\n *\n * Note: some field defined in this class might have public access level, but\n * the class is not exported so they are only accessible from this module.\n * This is useful to implement optional features (like bundles) in free\n * functions, such that they are tree-shakeable.\n */\n// PORTING NOTE: Web only.\nclass MultiTabLocalStoreImpl extends LocalStoreImpl\n implements MultiTabLocalStore {\n protected mutationQueue: IndexedDbMutationQueue;\n protected remoteDocuments: IndexedDbRemoteDocumentCache;\n protected targetCache: IndexedDbTargetCache;\n\n constructor(\n protected persistence: IndexedDbPersistence,\n queryEngine: QueryEngine,\n initialUser: User\n ) {\n super(persistence, queryEngine, initialUser);\n\n this.mutationQueue = persistence.getMutationQueue(initialUser);\n this.remoteDocuments = persistence.getRemoteDocumentCache();\n this.targetCache = persistence.getTargetCache();\n }\n\n /** Starts the LocalStore. */\n start(): Promise {\n return this.synchronizeLastDocumentChangeReadTime();\n }\n\n lookupMutationDocuments(batchId: BatchId): Promise {\n return this.persistence.runTransaction(\n 'Lookup mutation documents',\n 'readonly',\n txn => {\n return this.mutationQueue\n .lookupMutationKeys(txn, batchId)\n .next(keys => {\n if (keys) {\n return this.localDocuments.getDocuments(\n txn,\n keys\n ) as PersistencePromise;\n } else {\n return PersistencePromise.resolve(null);\n }\n });\n }\n );\n }\n\n removeCachedMutationBatchMetadata(batchId: BatchId): void {\n this.mutationQueue.removeCachedMutationKeys(batchId);\n }\n\n setNetworkEnabled(networkEnabled: boolean): void {\n this.persistence.setNetworkEnabled(networkEnabled);\n }\n\n getActiveClients(): Promise {\n return this.persistence.getActiveClients();\n }\n\n getTarget(targetId: TargetId): Promise {\n const cachedTargetData = this.targetDataByTarget.get(targetId);\n\n if (cachedTargetData) {\n return Promise.resolve(cachedTargetData.target);\n } else {\n return this.persistence.runTransaction(\n 'Get target data',\n 'readonly',\n txn => {\n return this.targetCache\n .getTargetDataForTarget(txn, targetId)\n .next(targetData => (targetData ? targetData.target : null));\n }\n );\n }\n }\n\n getNewDocumentChanges(): Promise {\n return this.persistence\n .runTransaction('Get new document changes', 'readonly', txn =>\n this.remoteDocuments.getNewDocumentChanges(\n txn,\n this.lastDocumentChangeReadTime\n )\n )\n .then(({ changedDocs, readTime }) => {\n this.lastDocumentChangeReadTime = readTime;\n return changedDocs;\n });\n }\n\n async synchronizeLastDocumentChangeReadTime(): Promise {\n this.lastDocumentChangeReadTime = await this.persistence.runTransaction(\n 'Synchronize last document change read time',\n 'readonly',\n txn => this.remoteDocuments.getLastReadTime(txn)\n );\n }\n}\n\nexport function newMultiTabLocalStore(\n /** Manages our in-memory or durable persistence. */\n persistence: IndexedDbPersistence,\n queryEngine: QueryEngine,\n initialUser: User\n): MultiTabLocalStore {\n return new MultiTabLocalStoreImpl(persistence, queryEngine, initialUser);\n}\n\n/**\n * Verifies the error thrown by a LocalStore operation. If a LocalStore\n * operation fails because the primary lease has been taken by another client,\n * we ignore the error (the persistence layer will immediately call\n * `applyPrimaryLease` to propagate the primary state change). All other errors\n * are re-thrown.\n *\n * @param err An error returned by a LocalStore operation.\n * @return A Promise that resolves after we recovered, or the original error.\n */\nexport async function ignoreIfPrimaryLeaseLoss(\n err: FirestoreError\n): Promise {\n if (\n err.code === Code.FAILED_PRECONDITION &&\n err.message === PRIMARY_LEASE_LOST_ERROR_MSG\n ) {\n logDebug(LOG_TAG, 'Unexpectedly lost primary lease');\n } else {\n throw err;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { User } from '../auth/user';\nimport { ListenSequenceNumber, TargetId } from '../core/types';\nimport { DocumentKey } from '../model/document_key';\nimport { IndexManager } from './index_manager';\nimport { LocalStore } from './local_store';\nimport { MutationQueue } from './mutation_queue';\nimport { PersistencePromise } from './persistence_promise';\nimport { TargetCache } from './target_cache';\nimport { RemoteDocumentCache } from './remote_document_cache';\nimport { TargetData } from './target_data';\n\nexport const PRIMARY_LEASE_LOST_ERROR_MSG =\n 'The current tab is not in the required state to perform this operation. ' +\n 'It might be necessary to refresh the browser tab.';\n\n/**\n * A base class representing a persistence transaction, encapsulating both the\n * transaction's sequence numbers as well as a list of onCommitted listeners.\n *\n * When you call Persistence.runTransaction(), it will create a transaction and\n * pass it to your callback. You then pass it to any method that operates\n * on persistence.\n */\nexport abstract class PersistenceTransaction {\n private readonly onCommittedListeners: Array<() => void> = [];\n\n abstract readonly currentSequenceNumber: ListenSequenceNumber;\n\n addOnCommittedListener(listener: () => void): void {\n this.onCommittedListeners.push(listener);\n }\n\n raiseOnCommittedEvent(): void {\n this.onCommittedListeners.forEach(listener => listener());\n }\n}\n\n/** The different modes supported by `IndexedDbPersistence.runTransaction()`. */\nexport type PersistenceTransactionMode =\n | 'readonly'\n | 'readwrite'\n | 'readwrite-primary';\n\n/**\n * Callback type for primary state notifications. This callback can be\n * registered with the persistence layer to get notified when we transition from\n * primary to secondary state and vice versa.\n *\n * Note: Instances can only toggle between Primary and Secondary state if\n * IndexedDB persistence is enabled and multiple clients are active. If this\n * listener is registered with MemoryPersistence, the callback will be called\n * exactly once marking the current instance as Primary.\n */\nexport type PrimaryStateListener = (isPrimary: boolean) => Promise;\n\n/**\n * A ReferenceDelegate instance handles all of the hooks into the document-reference lifecycle. This\n * includes being added to a target, being removed from a target, being subject to mutation, and\n * being mutated by the user.\n *\n * Different implementations may do different things with each of these events. Not every\n * implementation needs to do something with every lifecycle hook.\n *\n * PORTING NOTE: since sequence numbers are attached to transactions in this\n * client, the ReferenceDelegate does not need to deal in transactional\n * semantics (onTransactionStarted/Committed()), nor does it need to track and\n * generate sequence numbers (getCurrentSequenceNumber()).\n */\nexport interface ReferenceDelegate {\n /** Notify the delegate that the given document was added to a target. */\n addReference(\n txn: PersistenceTransaction,\n targetId: TargetId,\n doc: DocumentKey\n ): PersistencePromise;\n\n /** Notify the delegate that the given document was removed from a target. */\n removeReference(\n txn: PersistenceTransaction,\n targetId: TargetId,\n doc: DocumentKey\n ): PersistencePromise;\n\n /**\n * Notify the delegate that a target was removed. The delegate may, but is not obligated to,\n * actually delete the target and associated data.\n */\n removeTarget(\n txn: PersistenceTransaction,\n targetData: TargetData\n ): PersistencePromise;\n\n /**\n * Notify the delegate that a document may no longer be part of any views or\n * have any mutations associated.\n */\n markPotentiallyOrphaned(\n txn: PersistenceTransaction,\n doc: DocumentKey\n ): PersistencePromise;\n\n /** Notify the delegate that a limbo document was updated. */\n updateLimboDocument(\n txn: PersistenceTransaction,\n doc: DocumentKey\n ): PersistencePromise;\n}\n\n/**\n * Persistence is the lowest-level shared interface to persistent storage in\n * Firestore.\n *\n * Persistence is used to create MutationQueue and RemoteDocumentCache\n * instances backed by persistence (which might be in-memory or LevelDB).\n *\n * Persistence also exposes an API to create and run PersistenceTransactions\n * against persistence. All read / write operations must be wrapped in a\n * transaction. Implementations of PersistenceTransaction / Persistence only\n * need to guarantee that writes made against the transaction are not made to\n * durable storage until the transaction resolves its PersistencePromise.\n * Since memory-only storage components do not alter durable storage, they are\n * free to ignore the transaction.\n *\n * This contract is enough to allow the LocalStore be be written\n * independently of whether or not the stored state actually is durably\n * persisted. If persistent storage is enabled, writes are grouped together to\n * avoid inconsistent state that could cause crashes.\n *\n * Concretely, when persistent storage is enabled, the persistent versions of\n * MutationQueue, RemoteDocumentCache, and others (the mutators) will\n * defer their writes into a transaction. Once the local store has completed\n * one logical operation, it commits the transaction.\n *\n * When persistent storage is disabled, the non-persistent versions of the\n * mutators ignore the transaction. This short-cut is allowed because\n * memory-only storage leaves no state so it cannot be inconsistent.\n *\n * This simplifies the implementations of the mutators and allows memory-only\n * implementations to supplement the persistent ones without requiring any\n * special dual-store implementation of Persistence. The cost is that the\n * LocalStore needs to be slightly careful about the order of its reads and\n * writes in order to avoid relying on being able to read back uncommitted\n * writes.\n */\nexport interface Persistence {\n /**\n * Whether or not this persistence instance has been started.\n */\n readonly started: boolean;\n\n readonly referenceDelegate: ReferenceDelegate;\n\n /** Starts persistence. */\n start(): Promise;\n\n /**\n * Releases any resources held during eager shutdown.\n */\n shutdown(): Promise;\n\n /**\n * Registers a listener that gets called when the database receives a\n * version change event indicating that it has deleted.\n *\n * PORTING NOTE: This is only used for Web multi-tab.\n */\n setDatabaseDeletedListener(\n databaseDeletedListener: () => Promise\n ): void;\n\n /**\n * Returns a MutationQueue representing the persisted mutations for the\n * given user.\n *\n * Note: The implementation is free to return the same instance every time\n * this is called for a given user. In particular, the memory-backed\n * implementation does this to emulate the persisted implementation to the\n * extent possible (e.g. in the case of uid switching from\n * sally=>jack=>sally, sally's mutation queue will be preserved).\n */\n getMutationQueue(user: User): MutationQueue;\n\n /**\n * Returns a TargetCache representing the persisted cache of targets.\n *\n * Note: The implementation is free to return the same instance every time\n * this is called. In particular, the memory-backed implementation does this\n * to emulate the persisted implementation to the extent possible.\n */\n getTargetCache(): TargetCache;\n\n /**\n * Returns a RemoteDocumentCache representing the persisted cache of remote\n * documents.\n *\n * Note: The implementation is free to return the same instance every time\n * this is called. In particular, the memory-backed implementation does this\n * to emulate the persisted implementation to the extent possible.\n */\n getRemoteDocumentCache(): RemoteDocumentCache;\n\n /**\n * Returns an IndexManager instance that manages our persisted query indexes.\n *\n * Note: The implementation is free to return the same instance every time\n * this is called. In particular, the memory-backed implementation does this\n * to emulate the persisted implementation to the extent possible.\n */\n getIndexManager(): IndexManager;\n\n /**\n * Performs an operation inside a persistence transaction. Any reads or writes\n * against persistence must be performed within a transaction. Writes will be\n * committed atomically once the transaction completes.\n *\n * Persistence operations are asynchronous and therefore the provided\n * transactionOperation must return a PersistencePromise. When it is resolved,\n * the transaction will be committed and the Promise returned by this method\n * will resolve.\n *\n * @param action A description of the action performed by this transaction,\n * used for logging.\n * @param mode The underlying mode of the IndexedDb transaction. Can be\n * 'readonly`, 'readwrite' or 'readwrite-primary'. Transactions marked\n * 'readwrite-primary' can only be executed by the primary client. In this\n * mode, the transactionOperation will not be run if the primary lease cannot\n * be acquired and the returned promise will be rejected with a\n * FAILED_PRECONDITION error.\n * @param transactionOperation The operation to run inside a transaction.\n * @return A promise that is resolved once the transaction completes.\n */\n runTransaction(\n action: string,\n mode: PersistenceTransactionMode,\n transactionOperation: (\n transaction: PersistenceTransaction\n ) => PersistencePromise\n ): Promise;\n}\n\n/**\n * Interface implemented by the LRU scheduler to start(), stop() and restart\n * garbage collection.\n */\nexport interface GarbageCollectionScheduler {\n readonly started: boolean;\n start(localStore: LocalStore): void;\n stop(): void;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { BatchId, TargetId } from '../core/types';\nimport { documentKeySet, DocumentKeySet } from '../model/collections';\nimport { DocumentKey } from '../model/document_key';\nimport { primitiveComparator } from '../util/misc';\nimport { SortedSet } from '../util/sorted_set';\nimport { ResourcePath } from '../model/path';\n\n/**\n * A collection of references to a document from some kind of numbered entity\n * (either a target ID or batch ID). As references are added to or removed from\n * the set corresponding events are emitted to a registered garbage collector.\n *\n * Each reference is represented by a DocumentReference object. Each of them\n * contains enough information to uniquely identify the reference. They are all\n * stored primarily in a set sorted by key. A document is considered garbage if\n * there's no references in that set (this can be efficiently checked thanks to\n * sorting by key).\n *\n * ReferenceSet also keeps a secondary set that contains references sorted by\n * IDs. This one is used to efficiently implement removal of all references by\n * some target ID.\n */\nexport class ReferenceSet {\n // A set of outstanding references to a document sorted by key.\n private refsByKey = new SortedSet(DocReference.compareByKey);\n\n // A set of outstanding references to a document sorted by target id.\n private refsByTarget = new SortedSet(DocReference.compareByTargetId);\n\n /** Returns true if the reference set contains no references. */\n isEmpty(): boolean {\n return this.refsByKey.isEmpty();\n }\n\n /** Adds a reference to the given document key for the given ID. */\n addReference(key: DocumentKey, id: TargetId | BatchId): void {\n const ref = new DocReference(key, id);\n this.refsByKey = this.refsByKey.add(ref);\n this.refsByTarget = this.refsByTarget.add(ref);\n }\n\n /** Add references to the given document keys for the given ID. */\n addReferences(keys: DocumentKeySet, id: TargetId | BatchId): void {\n keys.forEach(key => this.addReference(key, id));\n }\n\n /**\n * Removes a reference to the given document key for the given\n * ID.\n */\n removeReference(key: DocumentKey, id: TargetId | BatchId): void {\n this.removeRef(new DocReference(key, id));\n }\n\n removeReferences(keys: DocumentKeySet, id: TargetId | BatchId): void {\n keys.forEach(key => this.removeReference(key, id));\n }\n\n /**\n * Clears all references with a given ID. Calls removeRef() for each key\n * removed.\n */\n removeReferencesForId(id: TargetId | BatchId): DocumentKey[] {\n const emptyKey = new DocumentKey(new ResourcePath([]));\n const startRef = new DocReference(emptyKey, id);\n const endRef = new DocReference(emptyKey, id + 1);\n const keys: DocumentKey[] = [];\n this.refsByTarget.forEachInRange([startRef, endRef], ref => {\n this.removeRef(ref);\n keys.push(ref.key);\n });\n return keys;\n }\n\n removeAllReferences(): void {\n this.refsByKey.forEach(ref => this.removeRef(ref));\n }\n\n private removeRef(ref: DocReference): void {\n this.refsByKey = this.refsByKey.delete(ref);\n this.refsByTarget = this.refsByTarget.delete(ref);\n }\n\n referencesForId(id: TargetId | BatchId): DocumentKeySet {\n const emptyKey = new DocumentKey(new ResourcePath([]));\n const startRef = new DocReference(emptyKey, id);\n const endRef = new DocReference(emptyKey, id + 1);\n let keys = documentKeySet();\n this.refsByTarget.forEachInRange([startRef, endRef], ref => {\n keys = keys.add(ref.key);\n });\n return keys;\n }\n\n containsKey(key: DocumentKey): boolean {\n const ref = new DocReference(key, 0);\n const firstRef = this.refsByKey.firstAfterOrEqual(ref);\n return firstRef !== null && key.isEqual(firstRef.key);\n }\n}\n\nexport class DocReference {\n constructor(\n public key: DocumentKey,\n public targetOrBatchId: TargetId | BatchId\n ) {}\n\n /** Compare by key then by ID */\n static compareByKey(left: DocReference, right: DocReference): number {\n return (\n DocumentKey.comparator(left.key, right.key) ||\n primitiveComparator(left.targetOrBatchId, right.targetOrBatchId)\n );\n }\n\n /** Compare by ID then by key */\n static compareByTargetId(left: DocReference, right: DocReference): number {\n return (\n primitiveComparator(left.targetOrBatchId, right.targetOrBatchId) ||\n DocumentKey.comparator(left.key, right.key)\n );\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { fail } from './assert';\nimport { Code, FirestoreError } from './error';\nimport { Dict, forEach } from './obj';\nimport { DocumentKey } from '../model/document_key';\nimport { ResourcePath } from '../model/path';\n\n/** Types accepted by validateType() and related methods for validation. */\nexport type ValidationType =\n | 'undefined'\n | 'object'\n | 'function'\n | 'boolean'\n | 'number'\n | 'string'\n | 'non-empty string';\n\n/**\n * Validates that no arguments were passed in the invocation of functionName.\n *\n * Forward the magic \"arguments\" variable as second parameter on which the\n * parameter validation is performed:\n * validateNoArgs('myFunction', arguments);\n */\nexport function validateNoArgs(functionName: string, args: IArguments): void {\n if (args.length !== 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Function ${functionName}() does not support arguments, ` +\n 'but was called with ' +\n formatPlural(args.length, 'argument') +\n '.'\n );\n }\n}\n\n/**\n * Validates the invocation of functionName has the exact number of arguments.\n *\n * Forward the magic \"arguments\" variable as second parameter on which the\n * parameter validation is performed:\n * validateExactNumberOfArgs('myFunction', arguments, 2);\n */\nexport function validateExactNumberOfArgs(\n functionName: string,\n args: ArrayLike,\n numberOfArgs: number\n): void {\n if (args.length !== numberOfArgs) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Function ${functionName}() requires ` +\n formatPlural(numberOfArgs, 'argument') +\n ', but was called with ' +\n formatPlural(args.length, 'argument') +\n '.'\n );\n }\n}\n\n/**\n * Validates the invocation of functionName has at least the provided number of\n * arguments (but can have many more).\n *\n * Forward the magic \"arguments\" variable as second parameter on which the\n * parameter validation is performed:\n * validateAtLeastNumberOfArgs('myFunction', arguments, 2);\n */\nexport function validateAtLeastNumberOfArgs(\n functionName: string,\n args: IArguments,\n minNumberOfArgs: number\n): void {\n if (args.length < minNumberOfArgs) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Function ${functionName}() requires at least ` +\n formatPlural(minNumberOfArgs, 'argument') +\n ', but was called with ' +\n formatPlural(args.length, 'argument') +\n '.'\n );\n }\n}\n\n/**\n * Validates the invocation of functionName has number of arguments between\n * the values provided.\n *\n * Forward the magic \"arguments\" variable as second parameter on which the\n * parameter validation is performed:\n * validateBetweenNumberOfArgs('myFunction', arguments, 2, 3);\n */\nexport function validateBetweenNumberOfArgs(\n functionName: string,\n args: IArguments,\n minNumberOfArgs: number,\n maxNumberOfArgs: number\n): void {\n if (args.length < minNumberOfArgs || args.length > maxNumberOfArgs) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Function ${functionName}() requires between ${minNumberOfArgs} and ` +\n `${maxNumberOfArgs} arguments, but was called with ` +\n formatPlural(args.length, 'argument') +\n '.'\n );\n }\n}\n\n/**\n * Validates the provided argument is an array and has as least the expected\n * number of elements.\n */\nexport function validateNamedArrayAtLeastNumberOfElements(\n functionName: string,\n value: T[],\n name: string,\n minNumberOfElements: number\n): void {\n if (!(value instanceof Array) || value.length < minNumberOfElements) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Function ${functionName}() requires its ${name} argument to be an ` +\n 'array with at least ' +\n `${formatPlural(minNumberOfElements, 'element')}.`\n );\n }\n}\n\n/**\n * Validates the provided positional argument has the native JavaScript type\n * using typeof checks.\n */\nexport function validateArgType(\n functionName: string,\n type: ValidationType,\n position: number,\n argument: unknown\n): void {\n validateType(functionName, type, `${ordinal(position)} argument`, argument);\n}\n\n/**\n * Validates the provided argument has the native JavaScript type using\n * typeof checks or is undefined.\n */\nexport function validateOptionalArgType(\n functionName: string,\n type: ValidationType,\n position: number,\n argument: unknown\n): void {\n if (argument !== undefined) {\n validateArgType(functionName, type, position, argument);\n }\n}\n\n/**\n * Validates the provided named option has the native JavaScript type using\n * typeof checks.\n */\nexport function validateNamedType(\n functionName: string,\n type: ValidationType,\n optionName: string,\n argument: unknown\n): void {\n validateType(functionName, type, `${optionName} option`, argument);\n}\n\n/**\n * Validates the provided named option has the native JavaScript type using\n * typeof checks or is undefined.\n */\nexport function validateNamedOptionalType(\n functionName: string,\n type: ValidationType,\n optionName: string,\n argument: unknown\n): void {\n if (argument !== undefined) {\n validateNamedType(functionName, type, optionName, argument);\n }\n}\n\nexport function validateArrayElements(\n functionName: string,\n optionName: string,\n typeDescription: string,\n argument: T[],\n validator: (arg0: T) => boolean\n): void {\n if (!(argument instanceof Array)) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Function ${functionName}() requires its ${optionName} ` +\n `option to be an array, but it was: ${valueDescription(argument)}`\n );\n }\n\n for (let i = 0; i < argument.length; ++i) {\n if (!validator(argument[i])) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Function ${functionName}() requires all ${optionName} ` +\n `elements to be ${typeDescription}, but the value at index ${i} ` +\n `was: ${valueDescription(argument[i])}`\n );\n }\n }\n}\n\nexport function validateOptionalArrayElements(\n functionName: string,\n optionName: string,\n typeDescription: string,\n argument: T[] | undefined,\n validator: (arg0: T) => boolean\n): void {\n if (argument !== undefined) {\n validateArrayElements(\n functionName,\n optionName,\n typeDescription,\n argument,\n validator\n );\n }\n}\n\n/**\n * Validates that the provided named option equals one of the expected values.\n */\nexport function validateNamedPropertyEquals(\n functionName: string,\n inputName: string,\n optionName: string,\n input: T,\n expected: T[]\n): void {\n const expectedDescription: string[] = [];\n\n for (const val of expected) {\n if (val === input) {\n return;\n }\n expectedDescription.push(valueDescription(val));\n }\n\n const actualDescription = valueDescription(input);\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid value ${actualDescription} provided to function ${functionName}() for option ` +\n `\"${optionName}\". Acceptable values: ${expectedDescription.join(', ')}`\n );\n}\n\n/**\n * Validates that the provided named option equals one of the expected values or\n * is undefined.\n */\nexport function validateNamedOptionalPropertyEquals(\n functionName: string,\n inputName: string,\n optionName: string,\n input: T,\n expected: T[]\n): void {\n if (input !== undefined) {\n validateNamedPropertyEquals(\n functionName,\n inputName,\n optionName,\n input,\n expected\n );\n }\n}\n\n/**\n * Validates that the provided argument is a valid enum.\n *\n * @param functionName Function making the validation call.\n * @param enums Array containing all possible values for the enum.\n * @param position Position of the argument in `functionName`.\n * @param argument Argument to validate.\n * @return The value as T if the argument can be converted.\n */\nexport function validateStringEnum(\n functionName: string,\n enums: T[],\n position: number,\n argument: unknown\n): T {\n if (!enums.some(element => element === argument)) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid value ${valueDescription(argument)} provided to function ` +\n `${functionName}() for its ${ordinal(position)} argument. Acceptable ` +\n `values: ${enums.join(', ')}`\n );\n }\n return argument as T;\n}\n\n/**\n * Validates that `path` refers to a document (indicated by the fact it contains\n * an even numbers of segments).\n */\nexport function validateDocumentPath(path: ResourcePath): void {\n if (!DocumentKey.isDocumentKey(path)) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid document path (${path}). Path points to a collection.`\n );\n }\n}\n\n/**\n * Validates that `path` refers to a collection (indicated by the fact it\n * contains an odd numbers of segments).\n */\nexport function validateCollectionPath(path: ResourcePath): void {\n if (DocumentKey.isDocumentKey(path)) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid collection path (${path}). Path points to a document.`\n );\n }\n}\n\n/** Helper to validate the type of a provided input. */\nfunction validateType(\n functionName: string,\n type: ValidationType,\n inputName: string,\n input: unknown\n): void {\n let valid = false;\n if (type === 'object') {\n valid = isPlainObject(input);\n } else if (type === 'non-empty string') {\n valid = typeof input === 'string' && input !== '';\n } else {\n valid = typeof input === type;\n }\n\n if (!valid) {\n const description = valueDescription(input);\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Function ${functionName}() requires its ${inputName} ` +\n `to be of type ${type}, but it was: ${description}`\n );\n }\n}\n\n/**\n * Returns true if it's a non-null object without a custom prototype\n * (i.e. excludes Array, Date, etc.).\n */\nexport function isPlainObject(input: unknown): boolean {\n return (\n typeof input === 'object' &&\n input !== null &&\n (Object.getPrototypeOf(input) === Object.prototype ||\n Object.getPrototypeOf(input) === null)\n );\n}\n\n/** Returns a string describing the type / value of the provided input. */\nexport function valueDescription(input: unknown): string {\n if (input === undefined) {\n return 'undefined';\n } else if (input === null) {\n return 'null';\n } else if (typeof input === 'string') {\n if (input.length > 20) {\n input = `${input.substring(0, 20)}...`;\n }\n return JSON.stringify(input);\n } else if (typeof input === 'number' || typeof input === 'boolean') {\n return '' + input;\n } else if (typeof input === 'object') {\n if (input instanceof Array) {\n return 'an array';\n } else {\n const customObjectName = tryGetCustomObjectType(input!);\n if (customObjectName) {\n return `a custom ${customObjectName} object`;\n } else {\n return 'an object';\n }\n }\n } else if (typeof input === 'function') {\n return 'a function';\n } else {\n return fail('Unknown wrong type: ' + typeof input);\n }\n}\n\n/** Hacky method to try to get the constructor name for an object. */\nexport function tryGetCustomObjectType(input: object): string | null {\n if (input.constructor) {\n const funcNameRegex = /function\\s+([^\\s(]+)\\s*\\(/;\n const results = funcNameRegex.exec(input.constructor.toString());\n if (results && results.length > 1) {\n return results[1];\n }\n }\n return null;\n}\n\n/** Validates the provided argument is defined. */\nexport function validateDefined(\n functionName: string,\n position: number,\n argument: unknown\n): void {\n if (argument === undefined) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Function ${functionName}() requires a valid ${ordinal(position)} ` +\n `argument, but it was undefined.`\n );\n }\n}\n\n/**\n * Validates the provided positional argument is an object, and its keys and\n * values match the expected keys and types provided in optionTypes.\n */\nexport function validateOptionNames(\n functionName: string,\n options: object,\n optionNames: string[]\n): void {\n forEach(options as Dict, (key, _) => {\n if (optionNames.indexOf(key) < 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Unknown option '${key}' passed to function ${functionName}(). ` +\n 'Available options: ' +\n optionNames.join(', ')\n );\n }\n });\n}\n\n/**\n * Helper method to throw an error that the provided argument did not pass\n * an instanceof check.\n */\nexport function invalidClassError(\n functionName: string,\n type: string,\n position: number,\n argument: unknown\n): Error {\n const description = valueDescription(argument);\n return new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Function ${functionName}() requires its ${ordinal(position)} ` +\n `argument to be a ${type}, but it was: ${description}`\n );\n}\n\nexport function validatePositiveNumber(\n functionName: string,\n position: number,\n n: number\n): void {\n if (n <= 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Function ${functionName}() requires its ${ordinal(\n position\n )} argument to be a positive number, but it was: ${n}.`\n );\n }\n}\n\n/** Converts a number to its english word representation */\nfunction ordinal(num: number): string {\n switch (num) {\n case 1:\n return 'first';\n case 2:\n return 'second';\n case 3:\n return 'third';\n default:\n return num + 'th';\n }\n}\n\n/**\n * Formats the given word as plural conditionally given the preceding number.\n */\nfunction formatPlural(num: number, str: string): string {\n return `${num} ${str}` + (num === 1 ? '' : 's');\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isBase64Available } from '../platform/base64';\nimport { Code, FirestoreError } from '../util/error';\nimport {\n invalidClassError,\n validateArgType,\n validateExactNumberOfArgs\n} from '../util/input_validation';\nimport { ByteString } from '../util/byte_string';\n\n/** Helper function to assert Uint8Array is available at runtime. */\nfunction assertUint8ArrayAvailable(): void {\n if (typeof Uint8Array === 'undefined') {\n throw new FirestoreError(\n Code.UNIMPLEMENTED,\n 'Uint8Arrays are not available in this environment.'\n );\n }\n}\n\n/** Helper function to assert Base64 functions are available at runtime. */\nfunction assertBase64Available(): void {\n if (!isBase64Available()) {\n throw new FirestoreError(\n Code.UNIMPLEMENTED,\n 'Blobs are unavailable in Firestore in this environment.'\n );\n }\n}\n\n/**\n * Immutable class holding a blob (binary data).\n * This class is directly exposed in the public API.\n *\n * Note that while you can't hide the constructor in JavaScript code, we are\n * using the hack above to make sure no-one outside this module can call it.\n */\nexport class Blob {\n // Prefix with underscore to signal that we consider this not part of the\n // public API and to prevent it from showing up for autocompletion.\n _byteString: ByteString;\n\n constructor(byteString: ByteString) {\n assertBase64Available();\n this._byteString = byteString;\n }\n\n static fromBase64String(base64: string): Blob {\n validateExactNumberOfArgs('Blob.fromBase64String', arguments, 1);\n validateArgType('Blob.fromBase64String', 'string', 1, base64);\n assertBase64Available();\n try {\n return new Blob(ByteString.fromBase64String(base64));\n } catch (e) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Failed to construct Blob from Base64 string: ' + e\n );\n }\n }\n\n static fromUint8Array(array: Uint8Array): Blob {\n validateExactNumberOfArgs('Blob.fromUint8Array', arguments, 1);\n assertUint8ArrayAvailable();\n if (!(array instanceof Uint8Array)) {\n throw invalidClassError('Blob.fromUint8Array', 'Uint8Array', 1, array);\n }\n return new Blob(ByteString.fromUint8Array(array));\n }\n\n toBase64(): string {\n validateExactNumberOfArgs('Blob.toBase64', arguments, 0);\n assertBase64Available();\n return this._byteString.toBase64();\n }\n\n toUint8Array(): Uint8Array {\n validateExactNumberOfArgs('Blob.toUint8Array', arguments, 0);\n assertUint8ArrayAvailable();\n return this._byteString.toUint8Array();\n }\n\n toString(): string {\n return 'Blob(base64: ' + this.toBase64() + ')';\n }\n\n isEqual(other: Blob): boolean {\n return this._byteString.isEqual(other._byteString);\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as firestore from '@firebase/firestore-types';\n\nimport { FieldPath as InternalFieldPath } from '../model/path';\nimport { Code, FirestoreError } from '../util/error';\nimport {\n invalidClassError,\n validateArgType,\n validateNamedArrayAtLeastNumberOfElements\n} from '../util/input_validation';\n\n// The objects that are a part of this API are exposed to third-parties as\n// compiled javascript so we want to flag our private members with a leading\n// underscore to discourage their use.\n\n/**\n * A field class base class that is shared by the lite, full and legacy SDK,\n * which supports shared code that deals with FieldPaths.\n */\nexport abstract class BaseFieldPath {\n /** Internal representation of a Firestore field path. */\n readonly _internalPath: InternalFieldPath;\n\n constructor(fieldNames: string[]) {\n validateNamedArrayAtLeastNumberOfElements(\n 'FieldPath',\n fieldNames,\n 'fieldNames',\n 1\n );\n\n for (let i = 0; i < fieldNames.length; ++i) {\n validateArgType('FieldPath', 'string', i, fieldNames[i]);\n if (fieldNames[i].length === 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid field name at argument $(i + 1). ` +\n 'Field names must not be empty.'\n );\n }\n }\n\n this._internalPath = new InternalFieldPath(fieldNames);\n }\n}\n\n/**\n * A FieldPath refers to a field in a document. The path may consist of a single\n * field name (referring to a top-level field in the document), or a list of\n * field names (referring to a nested field in the document).\n */\nexport class FieldPath extends BaseFieldPath implements firestore.FieldPath {\n /**\n * Creates a FieldPath from the provided field names. If more than one field\n * name is provided, the path will point to a nested field in a document.\n *\n * @param fieldNames A list of field names.\n */\n constructor(...fieldNames: string[]) {\n super(fieldNames);\n }\n\n static documentId(): FieldPath {\n /**\n * Internal Note: The backend doesn't technically support querying by\n * document ID. Instead it queries by the entire document name (full path\n * included), but in the cases we currently support documentId(), the net\n * effect is the same.\n */\n return new FieldPath(InternalFieldPath.keyField().canonicalString());\n }\n\n isEqual(other: firestore.FieldPath): boolean {\n if (!(other instanceof FieldPath)) {\n throw invalidClassError('isEqual', 'FieldPath', 1, other);\n }\n return this._internalPath.isEqual(other._internalPath);\n }\n}\n\n/**\n * Matches any characters in a field path string that are reserved.\n */\nconst RESERVED = new RegExp('[~\\\\*/\\\\[\\\\]]');\n\n/**\n * Parses a field path string into a FieldPath, treating dots as separators.\n */\nexport function fromDotSeparatedString(path: string): FieldPath {\n const found = path.search(RESERVED);\n if (found >= 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid field path (${path}). Paths must not contain ` +\n `'~', '*', '/', '[', or ']'`\n );\n }\n try {\n return new FieldPath(...path.split('.'));\n } catch (e) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid field path (${path}). Paths must not be empty, ` +\n `begin with '.', end with '.', or contain '..'`\n );\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as firestore from '@firebase/firestore-types';\nimport {\n validateArgType,\n validateAtLeastNumberOfArgs,\n validateExactNumberOfArgs,\n validateNoArgs\n} from '../util/input_validation';\nimport { FieldTransform } from '../model/mutation';\nimport {\n ArrayRemoveTransformOperation,\n ArrayUnionTransformOperation,\n NumericIncrementTransformOperation,\n ServerTimestampTransform\n} from '../model/transform_operation';\nimport { ParseContext, parseData, UserDataSource } from './user_data_reader';\nimport { debugAssert } from '../util/assert';\nimport { toNumber } from '../remote/serializer';\n\n/**\n * An opaque base class for FieldValue sentinel objects in our public API that\n * is shared between the full, lite and legacy SDK.\n */\nexport abstract class SerializableFieldValue {\n /** The public API endpoint that returns this class. */\n abstract readonly _methodName: string;\n\n /** A pointer to the implementing class. */\n readonly _delegate: SerializableFieldValue = this;\n\n abstract _toFieldTransform(context: ParseContext): FieldTransform | null;\n\n abstract isEqual(other: SerializableFieldValue): boolean;\n}\n\nexport class DeleteFieldValueImpl extends SerializableFieldValue {\n constructor(readonly _methodName: string) {\n super();\n }\n\n _toFieldTransform(context: ParseContext): null {\n if (context.dataSource === UserDataSource.MergeSet) {\n // No transform to add for a delete, but we need to add it to our\n // fieldMask so it gets deleted.\n context.fieldMask.push(context.path!);\n } else if (context.dataSource === UserDataSource.Update) {\n debugAssert(\n context.path!.length > 0,\n `${this._methodName}() at the top level should have already ` +\n 'been handled.'\n );\n throw context.createError(\n `${this._methodName}() can only appear at the top level ` +\n 'of your update data'\n );\n } else {\n // We shouldn't encounter delete sentinels for queries or non-merge set() calls.\n throw context.createError(\n `${this._methodName}() cannot be used with set() unless you pass ` +\n '{merge:true}'\n );\n }\n return null;\n }\n\n isEqual(other: FieldValue): boolean {\n return other instanceof DeleteFieldValueImpl;\n }\n}\n\n/**\n * Creates a child context for parsing SerializableFieldValues.\n *\n * This is different than calling `ParseContext.contextWith` because it keeps\n * the fieldTransforms and fieldMask separate.\n *\n * The created context has its `dataSource` set to `UserDataSource.Argument`.\n * Although these values are used with writes, any elements in these FieldValues\n * are not considered writes since they cannot contain any FieldValue sentinels,\n * etc.\n *\n * @param fieldValue The sentinel FieldValue for which to create a child\n * context.\n * @param context The parent context.\n * @param arrayElement Whether or not the FieldValue has an array.\n */\nfunction createSentinelChildContext(\n fieldValue: SerializableFieldValue,\n context: ParseContext,\n arrayElement: boolean\n): ParseContext {\n return new ParseContext(\n {\n dataSource: UserDataSource.Argument,\n targetDoc: context.settings.targetDoc,\n methodName: fieldValue._methodName,\n arrayElement\n },\n context.databaseId,\n context.serializer,\n context.ignoreUndefinedProperties\n );\n}\n\nexport class ServerTimestampFieldValueImpl extends SerializableFieldValue {\n constructor(readonly _methodName: string) {\n super();\n }\n\n _toFieldTransform(context: ParseContext): FieldTransform {\n return new FieldTransform(context.path!, new ServerTimestampTransform());\n }\n\n isEqual(other: FieldValue): boolean {\n return other instanceof ServerTimestampFieldValueImpl;\n }\n}\n\nexport class ArrayUnionFieldValueImpl extends SerializableFieldValue {\n constructor(\n readonly _methodName: string,\n private readonly _elements: unknown[]\n ) {\n super();\n }\n\n _toFieldTransform(context: ParseContext): FieldTransform {\n const parseContext = createSentinelChildContext(\n this,\n context,\n /*array=*/ true\n );\n const parsedElements = this._elements.map(\n element => parseData(element, parseContext)!\n );\n const arrayUnion = new ArrayUnionTransformOperation(parsedElements);\n return new FieldTransform(context.path!, arrayUnion);\n }\n\n isEqual(other: FieldValue): boolean {\n // TODO(mrschmidt): Implement isEquals\n return this === other;\n }\n}\n\nexport class ArrayRemoveFieldValueImpl extends SerializableFieldValue {\n constructor(readonly _methodName: string, readonly _elements: unknown[]) {\n super();\n }\n\n _toFieldTransform(context: ParseContext): FieldTransform {\n const parseContext = createSentinelChildContext(\n this,\n context,\n /*array=*/ true\n );\n const parsedElements = this._elements.map(\n element => parseData(element, parseContext)!\n );\n const arrayUnion = new ArrayRemoveTransformOperation(parsedElements);\n return new FieldTransform(context.path!, arrayUnion);\n }\n\n isEqual(other: FieldValue): boolean {\n // TODO(mrschmidt): Implement isEquals\n return this === other;\n }\n}\n\nexport class NumericIncrementFieldValueImpl extends SerializableFieldValue {\n constructor(readonly _methodName: string, private readonly _operand: number) {\n super();\n }\n\n _toFieldTransform(context: ParseContext): FieldTransform {\n const numericIncrement = new NumericIncrementTransformOperation(\n context.serializer,\n toNumber(context.serializer, this._operand)\n );\n return new FieldTransform(context.path!, numericIncrement);\n }\n\n isEqual(other: FieldValue): boolean {\n // TODO(mrschmidt): Implement isEquals\n return this === other;\n }\n}\n\n/** The public FieldValue class of the lite API. */\nexport abstract class FieldValue extends SerializableFieldValue\n implements firestore.FieldValue {\n protected constructor() {\n super();\n }\n\n static delete(): firestore.FieldValue {\n validateNoArgs('FieldValue.delete', arguments);\n return new FieldValueDelegate(\n new DeleteFieldValueImpl('FieldValue.delete')\n );\n }\n\n static serverTimestamp(): firestore.FieldValue {\n validateNoArgs('FieldValue.serverTimestamp', arguments);\n return new FieldValueDelegate(\n new ServerTimestampFieldValueImpl('FieldValue.serverTimestamp')\n );\n }\n\n static arrayUnion(...elements: unknown[]): firestore.FieldValue {\n validateAtLeastNumberOfArgs('FieldValue.arrayUnion', arguments, 1);\n // NOTE: We don't actually parse the data until it's used in set() or\n // update() since we'd need the Firestore instance to do this.\n return new FieldValueDelegate(\n new ArrayUnionFieldValueImpl('FieldValue.arrayUnion', elements)\n );\n }\n\n static arrayRemove(...elements: unknown[]): firestore.FieldValue {\n validateAtLeastNumberOfArgs('FieldValue.arrayRemove', arguments, 1);\n // NOTE: We don't actually parse the data until it's used in set() or\n // update() since we'd need the Firestore instance to do this.\n return new FieldValueDelegate(\n new ArrayRemoveFieldValueImpl('FieldValue.arrayRemove', elements)\n );\n }\n\n static increment(n: number): firestore.FieldValue {\n validateArgType('FieldValue.increment', 'number', 1, n);\n validateExactNumberOfArgs('FieldValue.increment', arguments, 1);\n return new FieldValueDelegate(\n new NumericIncrementFieldValueImpl('FieldValue.increment', n)\n );\n }\n}\n\n/**\n * A delegate class that allows the FieldValue implementations returned by\n * deleteField(), serverTimestamp(), arrayUnion(), arrayRemove() and\n * increment() to be an instance of the legacy FieldValue class declared above.\n *\n * We don't directly subclass `FieldValue` in the various field value\n * implementations as the base FieldValue class differs between the lite, full\n * and legacy SDK.\n */\nclass FieldValueDelegate extends FieldValue implements firestore.FieldValue {\n readonly _methodName: string;\n\n constructor(readonly _delegate: SerializableFieldValue) {\n super();\n this._methodName = _delegate._methodName;\n }\n\n _toFieldTransform(context: ParseContext): FieldTransform | null {\n return this._delegate._toFieldTransform(context);\n }\n\n isEqual(other: firestore.FieldValue): boolean {\n if (!(other instanceof FieldValueDelegate)) {\n return false;\n }\n return this._delegate.isEqual(other._delegate);\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Code, FirestoreError } from '../util/error';\nimport {\n validateArgType,\n validateExactNumberOfArgs\n} from '../util/input_validation';\nimport { primitiveComparator } from '../util/misc';\n\n/**\n * Immutable class representing a geo point as latitude-longitude pair.\n * This class is directly exposed in the public API, including its constructor.\n */\nexport class GeoPoint {\n // Prefix with underscore to signal this is a private variable in JS and\n // prevent it showing up for autocompletion when typing latitude or longitude.\n private _lat: number;\n private _long: number;\n\n constructor(latitude: number, longitude: number) {\n validateExactNumberOfArgs('GeoPoint', arguments, 2);\n validateArgType('GeoPoint', 'number', 1, latitude);\n validateArgType('GeoPoint', 'number', 2, longitude);\n if (!isFinite(latitude) || latitude < -90 || latitude > 90) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Latitude must be a number between -90 and 90, but was: ' + latitude\n );\n }\n if (!isFinite(longitude) || longitude < -180 || longitude > 180) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Longitude must be a number between -180 and 180, but was: ' + longitude\n );\n }\n\n this._lat = latitude;\n this._long = longitude;\n }\n\n /**\n * Returns the latitude of this geo point, a number between -90 and 90.\n */\n get latitude(): number {\n return this._lat;\n }\n\n /**\n * Returns the longitude of this geo point, a number between -180 and 180.\n */\n get longitude(): number {\n return this._long;\n }\n\n isEqual(other: GeoPoint): boolean {\n return this._lat === other._lat && this._long === other._long;\n }\n\n /**\n * Actually private to JS consumers of our API, so this function is prefixed\n * with an underscore.\n */\n _compareTo(other: GeoPoint): number {\n return (\n primitiveComparator(this._lat, other._lat) ||\n primitiveComparator(this._long, other._long)\n );\n }\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/** Return the Platform-specific serializer monitor. */\nimport { DatabaseId } from '../../core/database_info';\nimport { JsonProtoSerializer } from '../../remote/serializer';\n\nexport function newSerializer(databaseId: DatabaseId): JsonProtoSerializer {\n return new JsonProtoSerializer(databaseId, /* useProto3Json= */ true);\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as firestore from '@firebase/firestore-types';\n\nimport * as api from '../protos/firestore_proto_api';\n\nimport { Timestamp } from './timestamp';\nimport { DatabaseId } from '../core/database_info';\nimport { DocumentKey } from '../model/document_key';\nimport {\n FieldMask,\n FieldTransform,\n Mutation,\n PatchMutation,\n Precondition,\n SetMutation,\n TransformMutation\n} from '../model/mutation';\nimport { FieldPath } from '../model/path';\nimport { debugAssert, fail } from '../util/assert';\nimport { Code, FirestoreError } from '../util/error';\nimport { isPlainObject, valueDescription } from '../util/input_validation';\nimport { Dict, forEach, isEmpty } from '../util/obj';\nimport { ObjectValue, ObjectValueBuilder } from '../model/object_value';\nimport {\n JsonProtoSerializer,\n toBytes,\n toNumber,\n toResourceName,\n toTimestamp\n} from '../remote/serializer';\nimport { Blob } from './blob';\nimport { BaseFieldPath, fromDotSeparatedString } from './field_path';\nimport { DeleteFieldValueImpl, SerializableFieldValue } from './field_value';\nimport { GeoPoint } from './geo_point';\nimport { newSerializer } from '../platform/serializer';\n\nconst RESERVED_FIELD_REGEX = /^__.*__$/;\n\n/**\n * An untyped Firestore Data Converter interface that is shared between the\n * lite, full and legacy SDK.\n */\nexport interface UntypedFirestoreDataConverter {\n toFirestore(modelObject: T): firestore.DocumentData;\n toFirestore(\n modelObject: Partial,\n options: firestore.SetOptions\n ): firestore.DocumentData;\n fromFirestore(snapshot: unknown, options?: unknown): T;\n}\n\n/**\n * A reference to a document in a Firebase project.\n *\n * This class serves as a common base class for the public DocumentReferences\n * exposed in the lite, full and legacy SDK.\n */\nexport class DocumentKeyReference {\n constructor(\n readonly _databaseId: DatabaseId,\n readonly _key: DocumentKey,\n readonly _converter: UntypedFirestoreDataConverter | null\n ) {}\n}\n\n/** The result of parsing document data (e.g. for a setData call). */\nexport class ParsedSetData {\n constructor(\n readonly data: ObjectValue,\n readonly fieldMask: FieldMask | null,\n readonly fieldTransforms: FieldTransform[]\n ) {}\n\n toMutations(key: DocumentKey, precondition: Precondition): Mutation[] {\n const mutations = [] as Mutation[];\n if (this.fieldMask !== null) {\n mutations.push(\n new PatchMutation(key, this.data, this.fieldMask, precondition)\n );\n } else {\n mutations.push(new SetMutation(key, this.data, precondition));\n }\n if (this.fieldTransforms.length > 0) {\n mutations.push(new TransformMutation(key, this.fieldTransforms));\n }\n return mutations;\n }\n}\n\n/** The result of parsing \"update\" data (i.e. for an updateData call). */\nexport class ParsedUpdateData {\n constructor(\n readonly data: ObjectValue,\n readonly fieldMask: FieldMask,\n readonly fieldTransforms: FieldTransform[]\n ) {}\n\n toMutations(key: DocumentKey, precondition: Precondition): Mutation[] {\n const mutations = [\n new PatchMutation(key, this.data, this.fieldMask, precondition)\n ] as Mutation[];\n if (this.fieldTransforms.length > 0) {\n mutations.push(new TransformMutation(key, this.fieldTransforms));\n }\n return mutations;\n }\n}\n\n/*\n * Represents what type of API method provided the data being parsed; useful\n * for determining which error conditions apply during parsing and providing\n * better error messages.\n */\nexport const enum UserDataSource {\n Set,\n Update,\n MergeSet,\n /**\n * Indicates the source is a where clause, cursor bound, arrayUnion()\n * element, etc. Of note, isWrite(source) will return false.\n */\n Argument,\n /**\n * Indicates that the source is an Argument that may directly contain nested\n * arrays (e.g. the operand of an `in` query).\n */\n ArrayArgument\n}\n\nfunction isWrite(dataSource: UserDataSource): boolean {\n switch (dataSource) {\n case UserDataSource.Set: // fall through\n case UserDataSource.MergeSet: // fall through\n case UserDataSource.Update:\n return true;\n case UserDataSource.Argument:\n case UserDataSource.ArrayArgument:\n return false;\n default:\n throw fail(`Unexpected case for UserDataSource: ${dataSource}`);\n }\n}\n\n/** Contains the settings that are mutated as we parse user data. */\ninterface ContextSettings {\n /** Indicates what kind of API method this data came from. */\n readonly dataSource: UserDataSource;\n /** The name of the method the user called to create the ParseContext. */\n readonly methodName: string;\n /** The document the user is attempting to modify, if that applies. */\n readonly targetDoc?: DocumentKey;\n /**\n * A path within the object being parsed. This could be an empty path (in\n * which case the context represents the root of the data being parsed), or a\n * nonempty path (indicating the context represents a nested location within\n * the data).\n */\n readonly path?: FieldPath;\n /**\n * Whether or not this context corresponds to an element of an array.\n * If not set, elements are treated as if they were outside of arrays.\n */\n readonly arrayElement?: boolean;\n /**\n * Whether or not a converter was specified in this context. If true, error\n * messages will reference the converter when invalid data is provided.\n */\n readonly hasConverter?: boolean;\n}\n\n/** A \"context\" object passed around while parsing user data. */\nexport class ParseContext {\n readonly fieldTransforms: FieldTransform[];\n readonly fieldMask: FieldPath[];\n /**\n * Initializes a ParseContext with the given source and path.\n *\n * @param settings The settings for the parser.\n * @param databaseId The database ID of the Firestore instance.\n * @param serializer The serializer to use to generate the Value proto.\n * @param ignoreUndefinedProperties Whether to ignore undefined properties\n * rather than throw.\n * @param fieldTransforms A mutable list of field transforms encountered while\n * parsing the data.\n * @param fieldMask A mutable list of field paths encountered while parsing\n * the data.\n *\n * TODO(b/34871131): We don't support array paths right now, so path can be\n * null to indicate the context represents any location within an array (in\n * which case certain features will not work and errors will be somewhat\n * compromised).\n */\n constructor(\n readonly settings: ContextSettings,\n readonly databaseId: DatabaseId,\n readonly serializer: JsonProtoSerializer,\n readonly ignoreUndefinedProperties: boolean,\n fieldTransforms?: FieldTransform[],\n fieldMask?: FieldPath[]\n ) {\n // Minor hack: If fieldTransforms is undefined, we assume this is an\n // external call and we need to validate the entire path.\n if (fieldTransforms === undefined) {\n this.validatePath();\n }\n this.fieldTransforms = fieldTransforms || [];\n this.fieldMask = fieldMask || [];\n }\n\n get path(): FieldPath | undefined {\n return this.settings.path;\n }\n\n get dataSource(): UserDataSource {\n return this.settings.dataSource;\n }\n\n /** Returns a new context with the specified settings overwritten. */\n contextWith(configuration: Partial): ParseContext {\n return new ParseContext(\n { ...this.settings, ...configuration },\n this.databaseId,\n this.serializer,\n this.ignoreUndefinedProperties,\n this.fieldTransforms,\n this.fieldMask\n );\n }\n\n childContextForField(field: string): ParseContext {\n const childPath = this.path?.child(field);\n const context = this.contextWith({ path: childPath, arrayElement: false });\n context.validatePathSegment(field);\n return context;\n }\n\n childContextForFieldPath(field: FieldPath): ParseContext {\n const childPath = this.path?.child(field);\n const context = this.contextWith({ path: childPath, arrayElement: false });\n context.validatePath();\n return context;\n }\n\n childContextForArray(index: number): ParseContext {\n // TODO(b/34871131): We don't support array paths right now; so make path\n // undefined.\n return this.contextWith({ path: undefined, arrayElement: true });\n }\n\n createError(reason: string): Error {\n return createError(\n reason,\n this.settings.methodName,\n this.settings.hasConverter || false,\n this.path,\n this.settings.targetDoc\n );\n }\n\n /** Returns 'true' if 'fieldPath' was traversed when creating this context. */\n contains(fieldPath: FieldPath): boolean {\n return (\n this.fieldMask.find(field => fieldPath.isPrefixOf(field)) !== undefined ||\n this.fieldTransforms.find(transform =>\n fieldPath.isPrefixOf(transform.field)\n ) !== undefined\n );\n }\n\n private validatePath(): void {\n // TODO(b/34871131): Remove null check once we have proper paths for fields\n // within arrays.\n if (!this.path) {\n return;\n }\n for (let i = 0; i < this.path.length; i++) {\n this.validatePathSegment(this.path.get(i));\n }\n }\n\n private validatePathSegment(segment: string): void {\n if (segment.length === 0) {\n throw this.createError('Document fields must not be empty');\n }\n if (isWrite(this.dataSource) && RESERVED_FIELD_REGEX.test(segment)) {\n throw this.createError('Document fields cannot begin and end with \"__\"');\n }\n }\n}\n\n/**\n * Helper for parsing raw user input (provided via the API) into internal model\n * classes.\n */\nexport class UserDataReader {\n private readonly serializer: JsonProtoSerializer;\n\n constructor(\n private readonly databaseId: DatabaseId,\n private readonly ignoreUndefinedProperties: boolean,\n serializer?: JsonProtoSerializer\n ) {\n this.serializer = serializer || newSerializer(databaseId);\n }\n\n /** Creates a new top-level parse context. */\n createContext(\n dataSource: UserDataSource,\n methodName: string,\n targetDoc?: DocumentKey,\n hasConverter = false\n ): ParseContext {\n return new ParseContext(\n {\n dataSource,\n methodName,\n targetDoc,\n path: FieldPath.emptyPath(),\n arrayElement: false,\n hasConverter\n },\n this.databaseId,\n this.serializer,\n this.ignoreUndefinedProperties\n );\n }\n}\n\n/** Parse document data from a set() call. */\nexport function parseSetData(\n userDataReader: UserDataReader,\n methodName: string,\n targetDoc: DocumentKey,\n input: unknown,\n hasConverter: boolean,\n options: firestore.SetOptions = {}\n): ParsedSetData {\n const context = userDataReader.createContext(\n options.merge || options.mergeFields\n ? UserDataSource.MergeSet\n : UserDataSource.Set,\n methodName,\n targetDoc,\n hasConverter\n );\n validatePlainObject('Data must be an object, but it was:', context, input);\n const updateData = parseObject(input, context)!;\n\n let fieldMask: FieldMask | null;\n let fieldTransforms: FieldTransform[];\n\n if (options.merge) {\n fieldMask = new FieldMask(context.fieldMask);\n fieldTransforms = context.fieldTransforms;\n } else if (options.mergeFields) {\n const validatedFieldPaths: FieldPath[] = [];\n\n for (const stringOrFieldPath of options.mergeFields) {\n let fieldPath: FieldPath;\n\n if (stringOrFieldPath instanceof BaseFieldPath) {\n fieldPath = stringOrFieldPath._internalPath;\n } else if (typeof stringOrFieldPath === 'string') {\n fieldPath = fieldPathFromDotSeparatedString(\n methodName,\n stringOrFieldPath,\n targetDoc\n );\n } else {\n throw fail('Expected stringOrFieldPath to be a string or a FieldPath');\n }\n\n if (!context.contains(fieldPath)) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Field '${fieldPath}' is specified in your field mask but missing from your input data.`\n );\n }\n\n if (!fieldMaskContains(validatedFieldPaths, fieldPath)) {\n validatedFieldPaths.push(fieldPath);\n }\n }\n\n fieldMask = new FieldMask(validatedFieldPaths);\n fieldTransforms = context.fieldTransforms.filter(transform =>\n fieldMask!.covers(transform.field)\n );\n } else {\n fieldMask = null;\n fieldTransforms = context.fieldTransforms;\n }\n\n return new ParsedSetData(\n new ObjectValue(updateData),\n fieldMask,\n fieldTransforms\n );\n}\n\n/** Parse update data from an update() call. */\nexport function parseUpdateData(\n userDataReader: UserDataReader,\n methodName: string,\n targetDoc: DocumentKey,\n input: unknown\n): ParsedUpdateData {\n const context = userDataReader.createContext(\n UserDataSource.Update,\n methodName,\n targetDoc\n );\n validatePlainObject('Data must be an object, but it was:', context, input);\n\n const fieldMaskPaths: FieldPath[] = [];\n const updateData = new ObjectValueBuilder();\n forEach(input as Dict, (key, value) => {\n const path = fieldPathFromDotSeparatedString(methodName, key, targetDoc);\n\n const childContext = context.childContextForFieldPath(path);\n if (\n value instanceof SerializableFieldValue &&\n value._delegate instanceof DeleteFieldValueImpl\n ) {\n // Add it to the field mask, but don't add anything to updateData.\n fieldMaskPaths.push(path);\n } else {\n const parsedValue = parseData(value, childContext);\n if (parsedValue != null) {\n fieldMaskPaths.push(path);\n updateData.set(path, parsedValue);\n }\n }\n });\n\n const mask = new FieldMask(fieldMaskPaths);\n return new ParsedUpdateData(\n updateData.build(),\n mask,\n context.fieldTransforms\n );\n}\n\n/** Parse update data from a list of field/value arguments. */\nexport function parseUpdateVarargs(\n userDataReader: UserDataReader,\n methodName: string,\n targetDoc: DocumentKey,\n field: string | BaseFieldPath,\n value: unknown,\n moreFieldsAndValues: unknown[]\n): ParsedUpdateData {\n const context = userDataReader.createContext(\n UserDataSource.Update,\n methodName,\n targetDoc\n );\n const keys = [fieldPathFromArgument(methodName, field, targetDoc)];\n const values = [value];\n\n if (moreFieldsAndValues.length % 2 !== 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Function ${methodName}() needs to be called with an even number ` +\n 'of arguments that alternate between field names and values.'\n );\n }\n\n for (let i = 0; i < moreFieldsAndValues.length; i += 2) {\n keys.push(\n fieldPathFromArgument(\n methodName,\n moreFieldsAndValues[i] as string | BaseFieldPath\n )\n );\n values.push(moreFieldsAndValues[i + 1]);\n }\n\n const fieldMaskPaths: FieldPath[] = [];\n const updateData = new ObjectValueBuilder();\n\n // We iterate in reverse order to pick the last value for a field if the\n // user specified the field multiple times.\n for (let i = keys.length - 1; i >= 0; --i) {\n if (!fieldMaskContains(fieldMaskPaths, keys[i])) {\n const path = keys[i];\n const value = values[i];\n const childContext = context.childContextForFieldPath(path);\n if (\n value instanceof SerializableFieldValue &&\n value._delegate instanceof DeleteFieldValueImpl\n ) {\n // Add it to the field mask, but don't add anything to updateData.\n fieldMaskPaths.push(path);\n } else {\n const parsedValue = parseData(value, childContext);\n if (parsedValue != null) {\n fieldMaskPaths.push(path);\n updateData.set(path, parsedValue);\n }\n }\n }\n }\n\n const mask = new FieldMask(fieldMaskPaths);\n return new ParsedUpdateData(\n updateData.build(),\n mask,\n context.fieldTransforms\n );\n}\n\n/**\n * Parse a \"query value\" (e.g. value in a where filter or a value in a cursor\n * bound).\n *\n * @param allowArrays Whether the query value is an array that may directly\n * contain additional arrays (e.g. the operand of an `in` query).\n */\nexport function parseQueryValue(\n userDataReader: UserDataReader,\n methodName: string,\n input: unknown,\n allowArrays = false\n): api.Value {\n const context = userDataReader.createContext(\n allowArrays ? UserDataSource.ArrayArgument : UserDataSource.Argument,\n methodName\n );\n const parsed = parseData(input, context);\n debugAssert(parsed != null, 'Parsed data should not be null.');\n debugAssert(\n context.fieldTransforms.length === 0,\n 'Field transforms should have been disallowed.'\n );\n return parsed;\n}\n\n/**\n * Parses user data to Protobuf Values.\n *\n * @param input Data to be parsed.\n * @param context A context object representing the current path being parsed,\n * the source of the data being parsed, etc.\n * @return The parsed value, or null if the value was a FieldValue sentinel\n * that should not be included in the resulting parsed data.\n */\nexport function parseData(\n input: unknown,\n context: ParseContext\n): api.Value | null {\n if (looksLikeJsonObject(input)) {\n validatePlainObject('Unsupported field value:', context, input);\n return parseObject(input, context);\n } else if (input instanceof SerializableFieldValue) {\n // FieldValues usually parse into transforms (except FieldValue.delete())\n // in which case we do not want to include this field in our parsed data\n // (as doing so will overwrite the field directly prior to the transform\n // trying to transform it). So we don't add this location to\n // context.fieldMask and we return null as our parsing result.\n parseSentinelFieldValue(input, context);\n return null;\n } else {\n // If context.path is null we are inside an array and we don't support\n // field mask paths more granular than the top-level array.\n if (context.path) {\n context.fieldMask.push(context.path);\n }\n\n if (input instanceof Array) {\n // TODO(b/34871131): Include the path containing the array in the error\n // message.\n // In the case of IN queries, the parsed data is an array (representing\n // the set of values to be included for the IN query) that may directly\n // contain additional arrays (each representing an individual field\n // value), so we disable this validation.\n if (\n context.settings.arrayElement &&\n context.dataSource !== UserDataSource.ArrayArgument\n ) {\n throw context.createError('Nested arrays are not supported');\n }\n return parseArray(input as unknown[], context);\n } else {\n return parseScalarValue(input, context);\n }\n }\n}\n\nfunction parseObject(\n obj: Dict,\n context: ParseContext\n): { mapValue: api.MapValue } {\n const fields: Dict = {};\n\n if (isEmpty(obj)) {\n // If we encounter an empty object, we explicitly add it to the update\n // mask to ensure that the server creates a map entry.\n if (context.path && context.path.length > 0) {\n context.fieldMask.push(context.path);\n }\n } else {\n forEach(obj, (key: string, val: unknown) => {\n const parsedValue = parseData(val, context.childContextForField(key));\n if (parsedValue != null) {\n fields[key] = parsedValue;\n }\n });\n }\n\n return { mapValue: { fields } };\n}\n\nfunction parseArray(array: unknown[], context: ParseContext): api.Value {\n const values: api.Value[] = [];\n let entryIndex = 0;\n for (const entry of array) {\n let parsedEntry = parseData(\n entry,\n context.childContextForArray(entryIndex)\n );\n if (parsedEntry == null) {\n // Just include nulls in the array for fields being replaced with a\n // sentinel.\n parsedEntry = { nullValue: 'NULL_VALUE' };\n }\n values.push(parsedEntry);\n entryIndex++;\n }\n return { arrayValue: { values } };\n}\n\n/**\n * \"Parses\" the provided FieldValueImpl, adding any necessary transforms to\n * context.fieldTransforms.\n */\nfunction parseSentinelFieldValue(\n value: SerializableFieldValue,\n context: ParseContext\n): void {\n // Sentinels are only supported with writes, and not within arrays.\n if (!isWrite(context.dataSource)) {\n throw context.createError(\n `${value._methodName}() can only be used with update() and set()`\n );\n }\n if (!context.path) {\n throw context.createError(\n `${value._methodName}() is not currently supported inside arrays`\n );\n }\n\n const fieldTransform = value._toFieldTransform(context);\n if (fieldTransform) {\n context.fieldTransforms.push(fieldTransform);\n }\n}\n\n/**\n * Helper to parse a scalar value (i.e. not an Object, Array, or FieldValue)\n *\n * @return The parsed value\n */\nfunction parseScalarValue(\n value: unknown,\n context: ParseContext\n): api.Value | null {\n if (value === null) {\n return { nullValue: 'NULL_VALUE' };\n } else if (typeof value === 'number') {\n return toNumber(context.serializer, value);\n } else if (typeof value === 'boolean') {\n return { booleanValue: value };\n } else if (typeof value === 'string') {\n return { stringValue: value };\n } else if (value instanceof Date) {\n const timestamp = Timestamp.fromDate(value);\n return {\n timestampValue: toTimestamp(context.serializer, timestamp)\n };\n } else if (value instanceof Timestamp) {\n // Firestore backend truncates precision down to microseconds. To ensure\n // offline mode works the same with regards to truncation, perform the\n // truncation immediately without waiting for the backend to do that.\n const timestamp = new Timestamp(\n value.seconds,\n Math.floor(value.nanoseconds / 1000) * 1000\n );\n return {\n timestampValue: toTimestamp(context.serializer, timestamp)\n };\n } else if (value instanceof GeoPoint) {\n return {\n geoPointValue: {\n latitude: value.latitude,\n longitude: value.longitude\n }\n };\n } else if (value instanceof Blob) {\n return { bytesValue: toBytes(context.serializer, value) };\n } else if (value instanceof DocumentKeyReference) {\n const thisDb = context.databaseId;\n const otherDb = value._databaseId;\n if (!otherDb.isEqual(thisDb)) {\n throw context.createError(\n 'Document reference is for database ' +\n `${otherDb.projectId}/${otherDb.database} but should be ` +\n `for database ${thisDb.projectId}/${thisDb.database}`\n );\n }\n return {\n referenceValue: toResourceName(\n value._databaseId || context.databaseId,\n value._key.path\n )\n };\n } else if (value === undefined && context.ignoreUndefinedProperties) {\n return null;\n } else {\n throw context.createError(\n `Unsupported field value: ${valueDescription(value)}`\n );\n }\n}\n\n/**\n * Checks whether an object looks like a JSON object that should be converted\n * into a struct. Normal class/prototype instances are considered to look like\n * JSON objects since they should be converted to a struct value. Arrays, Dates,\n * GeoPoints, etc. are not considered to look like JSON objects since they map\n * to specific FieldValue types other than ObjectValue.\n */\nfunction looksLikeJsonObject(input: unknown): boolean {\n return (\n typeof input === 'object' &&\n input !== null &&\n !(input instanceof Array) &&\n !(input instanceof Date) &&\n !(input instanceof Timestamp) &&\n !(input instanceof GeoPoint) &&\n !(input instanceof Blob) &&\n !(input instanceof DocumentKeyReference) &&\n !(input instanceof SerializableFieldValue)\n );\n}\n\nfunction validatePlainObject(\n message: string,\n context: ParseContext,\n input: unknown\n): asserts input is Dict {\n if (!looksLikeJsonObject(input) || !isPlainObject(input)) {\n const description = valueDescription(input);\n if (description === 'an object') {\n // Massage the error if it was an object.\n throw context.createError(message + ' a custom object');\n } else {\n throw context.createError(message + ' ' + description);\n }\n }\n}\n\n/**\n * Helper that calls fromDotSeparatedString() but wraps any error thrown.\n */\nexport function fieldPathFromArgument(\n methodName: string,\n path: string | BaseFieldPath,\n targetDoc?: DocumentKey\n): FieldPath {\n if (path instanceof BaseFieldPath) {\n return path._internalPath;\n } else if (typeof path === 'string') {\n return fieldPathFromDotSeparatedString(methodName, path);\n } else {\n const message = 'Field path arguments must be of type string or FieldPath.';\n throw createError(\n message,\n methodName,\n /* hasConverter= */ false,\n /* path= */ undefined,\n targetDoc\n );\n }\n}\n\n/**\n * Wraps fromDotSeparatedString with an error message about the method that\n * was thrown.\n * @param methodName The publicly visible method name\n * @param path The dot-separated string form of a field path which will be split\n * on dots.\n * @param targetDoc The document against which the field path will be evaluated.\n */\nexport function fieldPathFromDotSeparatedString(\n methodName: string,\n path: string,\n targetDoc?: DocumentKey\n): FieldPath {\n try {\n return fromDotSeparatedString(path)._internalPath;\n } catch (e) {\n const message = errorMessage(e);\n throw createError(\n message,\n methodName,\n /* hasConverter= */ false,\n /* path= */ undefined,\n targetDoc\n );\n }\n}\n\nfunction createError(\n reason: string,\n methodName: string,\n hasConverter: boolean,\n path?: FieldPath,\n targetDoc?: DocumentKey\n): Error {\n const hasPath = path && !path.isEmpty();\n const hasDocument = targetDoc !== undefined;\n let message = `Function ${methodName}() called with invalid data`;\n if (hasConverter) {\n message += ' (via `toFirestore()`)';\n }\n message += '. ';\n\n let description = '';\n if (hasPath || hasDocument) {\n description += ' (found';\n\n if (hasPath) {\n description += ` in field ${path}`;\n }\n if (hasDocument) {\n description += ` in document ${targetDoc}`;\n }\n description += ')';\n }\n\n return new FirestoreError(\n Code.INVALID_ARGUMENT,\n message + reason + description\n );\n}\n\n/**\n * Extracts the message from a caught exception, which should be an Error object\n * though JS doesn't guarantee that.\n */\nfunction errorMessage(error: Error | object): string {\n return error instanceof Error ? error.message : error.toString();\n}\n\n/** Checks `haystack` if FieldPath `needle` is present. Runs in O(n). */\nfunction fieldMaskContains(haystack: FieldPath[], needle: FieldPath): boolean {\n return haystack.some(v => v.isEqual(needle));\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Simple wrapper around a nullable UID. Mostly exists to make code more\n * readable.\n */\nexport class User {\n /** A user with a null UID. */\n static readonly UNAUTHENTICATED = new User(null);\n\n // TODO(mikelehen): Look into getting a proper uid-equivalent for\n // non-FirebaseAuth providers.\n static readonly GOOGLE_CREDENTIALS = new User('google-credentials-uid');\n static readonly FIRST_PARTY = new User('first-party-uid');\n\n constructor(readonly uid: string | null) {}\n\n isAuthenticated(): boolean {\n return this.uid != null;\n }\n\n /**\n * Returns a key representing this user, suitable for inclusion in a\n * dictionary.\n */\n toKey(): string {\n if (this.isAuthenticated()) {\n return 'uid:' + this.uid;\n } else {\n return 'anonymous-user';\n }\n }\n\n isEqual(otherUser: User): boolean {\n return otherUser.uid === this.uid;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { User } from '../auth/user';\nimport { hardAssert, debugAssert } from '../util/assert';\nimport { Code, FirestoreError } from '../util/error';\nimport {\n FirebaseAuthInternal,\n FirebaseAuthInternalName\n} from '@firebase/auth-interop-types';\nimport { Provider } from '@firebase/component';\nimport { logDebug } from '../util/log';\n\n// TODO(mikelehen): This should be split into multiple files and probably\n// moved to an auth/ folder to match other platforms.\n\nexport interface FirstPartyCredentialsSettings {\n type: 'gapi';\n client: unknown;\n sessionIndex: string;\n}\n\nexport interface ProviderCredentialsSettings {\n type: 'provider';\n client: CredentialsProvider;\n}\n\n/** Settings for private credentials */\nexport type CredentialsSettings =\n | FirstPartyCredentialsSettings\n | ProviderCredentialsSettings;\n\nexport type TokenType = 'OAuth' | 'FirstParty';\nexport interface Token {\n /** Type of token. */\n type: TokenType;\n\n /**\n * The user with which the token is associated (used for persisting user\n * state on disk, etc.).\n */\n user: User;\n\n /** Extra header values to be passed along with a request */\n authHeaders: { [header: string]: string };\n}\n\nexport class OAuthToken implements Token {\n type = 'OAuth' as TokenType;\n authHeaders: { [header: string]: string };\n constructor(value: string, public user: User) {\n this.authHeaders = {};\n // Set the headers using Object Literal notation to avoid minification\n this.authHeaders['Authorization'] = `Bearer ${value}`;\n }\n}\n\n/**\n * A Listener for credential change events. The listener should fetch a new\n * token and may need to invalidate other state if the current user has also\n * changed.\n */\nexport type CredentialChangeListener = (user: User) => void;\n\n/**\n * Provides methods for getting the uid and token for the current user and\n * listening for changes.\n */\nexport interface CredentialsProvider {\n /** Requests a token for the current user. */\n getToken(): Promise;\n\n /**\n * Marks the last retrieved token as invalid, making the next GetToken request\n * force-refresh the token.\n */\n invalidateToken(): void;\n\n /**\n * Specifies a listener to be notified of credential changes\n * (sign-in / sign-out, token changes). It is immediately called once with the\n * initial user.\n */\n setChangeListener(changeListener: CredentialChangeListener): void;\n\n /** Removes the previously-set change listener. */\n removeChangeListener(): void;\n}\n\n/** A CredentialsProvider that always yields an empty token. */\nexport class EmptyCredentialsProvider implements CredentialsProvider {\n /**\n * Stores the listener registered with setChangeListener()\n * This isn't actually necessary since the UID never changes, but we use this\n * to verify the listen contract is adhered to in tests.\n */\n private changeListener: CredentialChangeListener | null = null;\n\n getToken(): Promise {\n return Promise.resolve(null);\n }\n\n invalidateToken(): void {}\n\n setChangeListener(changeListener: CredentialChangeListener): void {\n debugAssert(\n !this.changeListener,\n 'Can only call setChangeListener() once.'\n );\n this.changeListener = changeListener;\n // Fire with initial user.\n changeListener(User.UNAUTHENTICATED);\n }\n\n removeChangeListener(): void {\n debugAssert(\n this.changeListener !== null,\n 'removeChangeListener() when no listener registered'\n );\n this.changeListener = null;\n }\n}\n\nexport class FirebaseCredentialsProvider implements CredentialsProvider {\n /**\n * The auth token listener registered with FirebaseApp, retained here so we\n * can unregister it.\n */\n private tokenListener: ((token: string | null) => void) | null = null;\n\n /** Tracks the current User. */\n private currentUser: User = User.UNAUTHENTICATED;\n private receivedInitialUser: boolean = false;\n\n /**\n * Counter used to detect if the token changed while a getToken request was\n * outstanding.\n */\n private tokenCounter = 0;\n\n /** The listener registered with setChangeListener(). */\n private changeListener: CredentialChangeListener | null = null;\n\n private forceRefresh = false;\n\n private auth: FirebaseAuthInternal | null;\n\n constructor(authProvider: Provider) {\n this.tokenListener = () => {\n this.tokenCounter++;\n this.currentUser = this.getUser();\n this.receivedInitialUser = true;\n if (this.changeListener) {\n this.changeListener(this.currentUser);\n }\n };\n\n this.tokenCounter = 0;\n\n this.auth = authProvider.getImmediate({ optional: true });\n\n if (this.auth) {\n this.auth.addAuthTokenListener(this.tokenListener!);\n } else {\n // if auth is not available, invoke tokenListener once with null token\n this.tokenListener(null);\n authProvider.get().then(\n auth => {\n this.auth = auth;\n if (this.tokenListener) {\n // tokenListener can be removed by removeChangeListener()\n this.auth.addAuthTokenListener(this.tokenListener);\n }\n },\n () => {\n /* this.authProvider.get() never rejects */\n }\n );\n }\n }\n\n getToken(): Promise {\n debugAssert(\n this.tokenListener != null,\n 'getToken cannot be called after listener removed.'\n );\n\n // Take note of the current value of the tokenCounter so that this method\n // can fail (with an ABORTED error) if there is a token change while the\n // request is outstanding.\n const initialTokenCounter = this.tokenCounter;\n const forceRefresh = this.forceRefresh;\n this.forceRefresh = false;\n\n if (!this.auth) {\n return Promise.resolve(null);\n }\n\n return this.auth.getToken(forceRefresh).then(tokenData => {\n // Cancel the request since the token changed while the request was\n // outstanding so the response is potentially for a previous user (which\n // user, we can't be sure).\n if (this.tokenCounter !== initialTokenCounter) {\n logDebug(\n 'FirebaseCredentialsProvider',\n 'getToken aborted due to token change.'\n );\n return this.getToken();\n } else {\n if (tokenData) {\n hardAssert(\n typeof tokenData.accessToken === 'string',\n 'Invalid tokenData returned from getToken():' + tokenData\n );\n return new OAuthToken(tokenData.accessToken, this.currentUser);\n } else {\n return null;\n }\n }\n });\n }\n\n invalidateToken(): void {\n this.forceRefresh = true;\n }\n\n setChangeListener(changeListener: CredentialChangeListener): void {\n debugAssert(\n !this.changeListener,\n 'Can only call setChangeListener() once.'\n );\n this.changeListener = changeListener;\n\n // Fire the initial event\n if (this.receivedInitialUser) {\n changeListener(this.currentUser);\n }\n }\n\n removeChangeListener(): void {\n debugAssert(\n this.tokenListener != null,\n 'removeChangeListener() called twice'\n );\n debugAssert(\n this.changeListener !== null,\n 'removeChangeListener() called when no listener registered'\n );\n\n if (this.auth) {\n this.auth.removeAuthTokenListener(this.tokenListener!);\n }\n this.tokenListener = null;\n this.changeListener = null;\n }\n\n // Auth.getUid() can return null even with a user logged in. It is because\n // getUid() is synchronous, but the auth code populating Uid is asynchronous.\n // This method should only be called in the AuthTokenListener callback\n // to guarantee to get the actual user.\n private getUser(): User {\n const currentUid = this.auth && this.auth.getUid();\n hardAssert(\n currentUid === null || typeof currentUid === 'string',\n 'Received invalid UID: ' + currentUid\n );\n return new User(currentUid);\n }\n}\n\n// Manual type definition for the subset of Gapi we use.\ninterface Gapi {\n auth: {\n getAuthHeaderValueForFirstParty: (\n userIdentifiers: Array<{ [key: string]: string }>\n ) => string | null;\n };\n}\n\n/*\n * FirstPartyToken provides a fresh token each time its value\n * is requested, because if the token is too old, requests will be rejected.\n * Technically this may no longer be necessary since the SDK should gracefully\n * recover from unauthenticated errors (see b/33147818 for context), but it's\n * safer to keep the implementation as-is.\n */\nexport class FirstPartyToken implements Token {\n type = 'FirstParty' as TokenType;\n user = User.FIRST_PARTY;\n\n constructor(private gapi: Gapi, private sessionIndex: string) {}\n\n get authHeaders(): { [header: string]: string } {\n const headers: { [header: string]: string } = {\n 'X-Goog-AuthUser': this.sessionIndex\n };\n const authHeader = this.gapi.auth.getAuthHeaderValueForFirstParty([]);\n if (authHeader) {\n headers['Authorization'] = authHeader;\n }\n return headers;\n }\n}\n\n/*\n * Provides user credentials required for the Firestore JavaScript SDK\n * to authenticate the user, using technique that is only available\n * to applications hosted by Google.\n */\nexport class FirstPartyCredentialsProvider implements CredentialsProvider {\n constructor(private gapi: Gapi, private sessionIndex: string) {}\n\n getToken(): Promise {\n return Promise.resolve(new FirstPartyToken(this.gapi, this.sessionIndex));\n }\n\n setChangeListener(changeListener: CredentialChangeListener): void {\n // Fire with initial uid.\n changeListener(User.FIRST_PARTY);\n }\n\n removeChangeListener(): void {}\n\n invalidateToken(): void {}\n}\n\n/**\n * Builds a CredentialsProvider depending on the type of\n * the credentials passed in.\n */\nexport function makeCredentialsProvider(\n credentials?: CredentialsSettings\n): CredentialsProvider {\n if (!credentials) {\n return new EmptyCredentialsProvider();\n }\n\n switch (credentials.type) {\n case 'gapi':\n const client = credentials.client as Gapi;\n // Make sure this really is a Gapi client.\n hardAssert(\n !!(\n typeof client === 'object' &&\n client !== null &&\n client['auth'] &&\n client['auth']['getAuthHeaderValueForFirstParty']\n ),\n 'unexpected gapi interface'\n );\n return new FirstPartyCredentialsProvider(\n client,\n credentials.sessionIndex || '0'\n );\n\n case 'provider':\n return credentials.client;\n\n default:\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'makeCredentialsProvider failed due to invalid credential type'\n );\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CredentialsProvider, Token } from '../api/credentials';\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { TargetId } from '../core/types';\nimport { TargetData } from '../local/target_data';\nimport { Mutation, MutationResult } from '../model/mutation';\nimport * as api from '../protos/firestore_proto_api';\nimport { debugAssert, hardAssert } from '../util/assert';\nimport { AsyncQueue, DelayedOperation, TimerId } from '../util/async_queue';\nimport { Code, FirestoreError } from '../util/error';\nimport { logDebug, logError } from '../util/log';\n\nimport { isNullOrUndefined } from '../util/types';\nimport { ExponentialBackoff } from './backoff';\nimport { Connection, Stream } from './connection';\nimport {\n fromVersion,\n fromWatchChange,\n fromWriteResults,\n getEncodedDatabaseId,\n JsonProtoSerializer,\n toListenRequestLabels,\n toMutation,\n toTarget,\n versionFromListenResponse\n} from './serializer';\nimport { WatchChange } from './watch_change';\n\nconst LOG_TAG = 'PersistentStream';\n\n// The generated proto interfaces for these class are missing the database\n// field. So we add it here.\n// TODO(b/36015800): Remove this once the api generator is fixed.\ninterface ListenRequest extends api.ListenRequest {\n database?: string;\n}\nexport interface WriteRequest extends api.WriteRequest {\n database?: string;\n}\n/**\n * PersistentStream can be in one of 5 states (each described in detail below)\n * based on the following state transition diagram:\n *\n * start() called auth & connection succeeded\n * INITIAL ----------------> STARTING -----------------------------> OPEN\n * ^ | |\n * | | error occurred |\n * | \\-----------------------------v-----/\n * | |\n * backoff | |\n * elapsed | start() called |\n * \\--- BACKOFF <---------------- ERROR\n *\n * [any state] --------------------------> INITIAL\n * stop() called or\n * idle timer expired\n */\nconst enum PersistentStreamState {\n /**\n * The streaming RPC is not yet running and there's no error condition.\n * Calling start() will start the stream immediately without backoff.\n * While in this state isStarted() will return false.\n */\n Initial,\n\n /**\n * The stream is starting, either waiting for an auth token or for the stream\n * to successfully open. While in this state, isStarted() will return true but\n * isOpen() will return false.\n */\n Starting,\n\n /**\n * The streaming RPC is up and running. Requests and responses can flow\n * freely. Both isStarted() and isOpen() will return true.\n */\n Open,\n\n /**\n * The stream encountered an error. The next start attempt will back off.\n * While in this state isStarted() will return false.\n */\n Error,\n\n /**\n * An in-between state after an error where the stream is waiting before\n * re-starting. After waiting is complete, the stream will try to open.\n * While in this state isStarted() will return true but isOpen() will return\n * false.\n */\n Backoff\n}\n\n/**\n * Provides a common interface that is shared by the listeners for stream\n * events by the concrete implementation classes.\n */\nexport interface PersistentStreamListener {\n /**\n * Called after the stream was established and can accept outgoing\n * messages\n */\n onOpen: () => Promise;\n /**\n * Called after the stream has closed. If there was an error, the\n * FirestoreError will be set.\n */\n onClose: (err?: FirestoreError) => Promise;\n}\n\n/** The time a stream stays open after it is marked idle. */\nconst IDLE_TIMEOUT_MS = 60 * 1000;\n\n/**\n * A PersistentStream is an abstract base class that represents a streaming RPC\n * to the Firestore backend. It's built on top of the connections own support\n * for streaming RPCs, and adds several critical features for our clients:\n *\n * - Exponential backoff on failure\n * - Authentication via CredentialsProvider\n * - Dispatching all callbacks into the shared worker queue\n * - Closing idle streams after 60 seconds of inactivity\n *\n * Subclasses of PersistentStream implement serialization of models to and\n * from the JSON representation of the protocol buffers for a specific\n * streaming RPC.\n *\n * ## Starting and Stopping\n *\n * Streaming RPCs are stateful and need to be start()ed before messages can\n * be sent and received. The PersistentStream will call the onOpen() function\n * of the listener once the stream is ready to accept requests.\n *\n * Should a start() fail, PersistentStream will call the registered onClose()\n * listener with a FirestoreError indicating what went wrong.\n *\n * A PersistentStream can be started and stopped repeatedly.\n *\n * Generic types:\n * SendType: The type of the outgoing message of the underlying\n * connection stream\n * ReceiveType: The type of the incoming message of the underlying\n * connection stream\n * ListenerType: The type of the listener that will be used for callbacks\n */\nexport abstract class PersistentStream<\n SendType,\n ReceiveType,\n ListenerType extends PersistentStreamListener\n> {\n private state = PersistentStreamState.Initial;\n /**\n * A close count that's incremented every time the stream is closed; used by\n * getCloseGuardedDispatcher() to invalidate callbacks that happen after\n * close.\n */\n private closeCount = 0;\n\n private idleTimer: DelayedOperation | null = null;\n private stream: Stream | null = null;\n\n protected backoff: ExponentialBackoff;\n\n constructor(\n private queue: AsyncQueue,\n connectionTimerId: TimerId,\n private idleTimerId: TimerId,\n protected connection: Connection,\n private credentialsProvider: CredentialsProvider,\n protected listener: ListenerType\n ) {\n this.backoff = new ExponentialBackoff(queue, connectionTimerId);\n }\n\n /**\n * Returns true if start() has been called and no error has occurred. True\n * indicates the stream is open or in the process of opening (which\n * encompasses respecting backoff, getting auth tokens, and starting the\n * actual RPC). Use isOpen() to determine if the stream is open and ready for\n * outbound requests.\n */\n isStarted(): boolean {\n return (\n this.state === PersistentStreamState.Starting ||\n this.state === PersistentStreamState.Open ||\n this.state === PersistentStreamState.Backoff\n );\n }\n\n /**\n * Returns true if the underlying RPC is open (the onOpen() listener has been\n * called) and the stream is ready for outbound requests.\n */\n isOpen(): boolean {\n return this.state === PersistentStreamState.Open;\n }\n\n /**\n * Starts the RPC. Only allowed if isStarted() returns false. The stream is\n * not immediately ready for use: onOpen() will be invoked when the RPC is\n * ready for outbound requests, at which point isOpen() will return true.\n *\n * When start returns, isStarted() will return true.\n */\n start(): void {\n if (this.state === PersistentStreamState.Error) {\n this.performBackoff();\n return;\n }\n\n debugAssert(\n this.state === PersistentStreamState.Initial,\n 'Already started'\n );\n this.auth();\n }\n\n /**\n * Stops the RPC. This call is idempotent and allowed regardless of the\n * current isStarted() state.\n *\n * When stop returns, isStarted() and isOpen() will both return false.\n */\n async stop(): Promise {\n if (this.isStarted()) {\n await this.close(PersistentStreamState.Initial);\n }\n }\n\n /**\n * After an error the stream will usually back off on the next attempt to\n * start it. If the error warrants an immediate restart of the stream, the\n * sender can use this to indicate that the receiver should not back off.\n *\n * Each error will call the onClose() listener. That function can decide to\n * inhibit backoff if required.\n */\n inhibitBackoff(): void {\n debugAssert(\n !this.isStarted(),\n 'Can only inhibit backoff in a stopped state'\n );\n\n this.state = PersistentStreamState.Initial;\n this.backoff.reset();\n }\n\n /**\n * Marks this stream as idle. If no further actions are performed on the\n * stream for one minute, the stream will automatically close itself and\n * notify the stream's onClose() handler with Status.OK. The stream will then\n * be in a !isStarted() state, requiring the caller to start the stream again\n * before further use.\n *\n * Only streams that are in state 'Open' can be marked idle, as all other\n * states imply pending network operations.\n */\n markIdle(): void {\n // Starts the idle time if we are in state 'Open' and are not yet already\n // running a timer (in which case the previous idle timeout still applies).\n if (this.isOpen() && this.idleTimer === null) {\n this.idleTimer = this.queue.enqueueAfterDelay(\n this.idleTimerId,\n IDLE_TIMEOUT_MS,\n () => this.handleIdleCloseTimer()\n );\n }\n }\n\n /** Sends a message to the underlying stream. */\n protected sendRequest(msg: SendType): void {\n this.cancelIdleCheck();\n this.stream!.send(msg);\n }\n\n /** Called by the idle timer when the stream should close due to inactivity. */\n private async handleIdleCloseTimer(): Promise {\n if (this.isOpen()) {\n // When timing out an idle stream there's no reason to force the stream into backoff when\n // it restarts so set the stream state to Initial instead of Error.\n return this.close(PersistentStreamState.Initial);\n }\n }\n\n /** Marks the stream as active again. */\n private cancelIdleCheck(): void {\n if (this.idleTimer) {\n this.idleTimer.cancel();\n this.idleTimer = null;\n }\n }\n\n /**\n * Closes the stream and cleans up as necessary:\n *\n * * closes the underlying GRPC stream;\n * * calls the onClose handler with the given 'error';\n * * sets internal stream state to 'finalState';\n * * adjusts the backoff timer based on the error\n *\n * A new stream can be opened by calling start().\n *\n * @param finalState the intended state of the stream after closing.\n * @param error the error the connection was closed with.\n */\n private async close(\n finalState: PersistentStreamState,\n error?: FirestoreError\n ): Promise {\n debugAssert(this.isStarted(), 'Only started streams should be closed.');\n debugAssert(\n finalState === PersistentStreamState.Error || isNullOrUndefined(error),\n \"Can't provide an error when not in an error state.\"\n );\n\n // Cancel any outstanding timers (they're guaranteed not to execute).\n this.cancelIdleCheck();\n this.backoff.cancel();\n\n // Invalidates any stream-related callbacks (e.g. from auth or the\n // underlying stream), guaranteeing they won't execute.\n this.closeCount++;\n\n if (finalState !== PersistentStreamState.Error) {\n // If this is an intentional close ensure we don't delay our next connection attempt.\n this.backoff.reset();\n } else if (error && error.code === Code.RESOURCE_EXHAUSTED) {\n // Log the error. (Probably either 'quota exceeded' or 'max queue length reached'.)\n logError(error.toString());\n logError(\n 'Using maximum backoff delay to prevent overloading the backend.'\n );\n this.backoff.resetToMax();\n } else if (error && error.code === Code.UNAUTHENTICATED) {\n // \"unauthenticated\" error means the token was rejected. Try force refreshing it in case it\n // just expired.\n this.credentialsProvider.invalidateToken();\n }\n\n // Clean up the underlying stream because we are no longer interested in events.\n if (this.stream !== null) {\n this.tearDown();\n this.stream.close();\n this.stream = null;\n }\n\n // This state must be assigned before calling onClose() to allow the callback to\n // inhibit backoff or otherwise manipulate the state in its non-started state.\n this.state = finalState;\n\n // Notify the listener that the stream closed.\n await this.listener.onClose(error);\n }\n\n /**\n * Can be overridden to perform additional cleanup before the stream is closed.\n * Calling super.tearDown() is not required.\n */\n protected tearDown(): void {}\n\n /**\n * Used by subclasses to start the concrete RPC and return the underlying\n * connection stream.\n */\n protected abstract startRpc(\n token: Token | null\n ): Stream;\n\n /**\n * Called after the stream has received a message. The function will be\n * called on the right queue and must return a Promise.\n * @param message The message received from the stream.\n */\n protected abstract onMessage(message: ReceiveType): Promise;\n\n private auth(): void {\n debugAssert(\n this.state === PersistentStreamState.Initial,\n 'Must be in initial state to auth'\n );\n\n this.state = PersistentStreamState.Starting;\n\n const dispatchIfNotClosed = this.getCloseGuardedDispatcher(this.closeCount);\n\n // TODO(mikelehen): Just use dispatchIfNotClosed, but see TODO below.\n const closeCount = this.closeCount;\n\n this.credentialsProvider.getToken().then(\n token => {\n // Stream can be stopped while waiting for authentication.\n // TODO(mikelehen): We really should just use dispatchIfNotClosed\n // and let this dispatch onto the queue, but that opened a spec test can\n // of worms that I don't want to deal with in this PR.\n if (this.closeCount === closeCount) {\n // Normally we'd have to schedule the callback on the AsyncQueue.\n // However, the following calls are safe to be called outside the\n // AsyncQueue since they don't chain asynchronous calls\n this.startStream(token);\n }\n },\n (error: Error) => {\n dispatchIfNotClosed(() => {\n const rpcError = new FirestoreError(\n Code.UNKNOWN,\n 'Fetching auth token failed: ' + error.message\n );\n return this.handleStreamClose(rpcError);\n });\n }\n );\n }\n\n private startStream(token: Token | null): void {\n debugAssert(\n this.state === PersistentStreamState.Starting,\n 'Trying to start stream in a non-starting state'\n );\n\n const dispatchIfNotClosed = this.getCloseGuardedDispatcher(this.closeCount);\n\n this.stream = this.startRpc(token);\n this.stream.onOpen(() => {\n dispatchIfNotClosed(() => {\n debugAssert(\n this.state === PersistentStreamState.Starting,\n 'Expected stream to be in state Starting, but was ' + this.state\n );\n this.state = PersistentStreamState.Open;\n return this.listener!.onOpen();\n });\n });\n this.stream.onClose((error?: FirestoreError) => {\n dispatchIfNotClosed(() => {\n return this.handleStreamClose(error);\n });\n });\n this.stream.onMessage((msg: ReceiveType) => {\n dispatchIfNotClosed(() => {\n return this.onMessage(msg);\n });\n });\n }\n\n private performBackoff(): void {\n debugAssert(\n this.state === PersistentStreamState.Error,\n 'Should only perform backoff when in Error state'\n );\n this.state = PersistentStreamState.Backoff;\n\n this.backoff.backoffAndRun(async () => {\n debugAssert(\n this.state === PersistentStreamState.Backoff,\n 'Backoff elapsed but state is now: ' + this.state\n );\n\n this.state = PersistentStreamState.Initial;\n this.start();\n debugAssert(this.isStarted(), 'PersistentStream should have started');\n });\n }\n\n // Visible for tests\n handleStreamClose(error?: FirestoreError): Promise {\n debugAssert(\n this.isStarted(),\n \"Can't handle server close on non-started stream\"\n );\n logDebug(LOG_TAG, `close with error: ${error}`);\n\n this.stream = null;\n\n // In theory the stream could close cleanly, however, in our current model\n // we never expect this to happen because if we stop a stream ourselves,\n // this callback will never be called. To prevent cases where we retry\n // without a backoff accidentally, we set the stream to error in all cases.\n return this.close(PersistentStreamState.Error, error);\n }\n\n /**\n * Returns a \"dispatcher\" function that dispatches operations onto the\n * AsyncQueue but only runs them if closeCount remains unchanged. This allows\n * us to turn auth / stream callbacks into no-ops if the stream is closed /\n * re-opened, etc.\n */\n private getCloseGuardedDispatcher(\n startCloseCount: number\n ): (fn: () => Promise) => void {\n return (fn: () => Promise): void => {\n this.queue.enqueueAndForget(() => {\n if (this.closeCount === startCloseCount) {\n return fn();\n } else {\n logDebug(\n LOG_TAG,\n 'stream callback skipped by getCloseGuardedDispatcher.'\n );\n return Promise.resolve();\n }\n });\n };\n }\n}\n\n/** Listener for the PersistentWatchStream */\nexport interface WatchStreamListener extends PersistentStreamListener {\n /**\n * Called on a watchChange. The snapshot parameter will be MIN if the watch\n * change did not have a snapshot associated with it.\n */\n onWatchChange: (\n watchChange: WatchChange,\n snapshot: SnapshotVersion\n ) => Promise;\n}\n\n/**\n * A PersistentStream that implements the Listen RPC.\n *\n * Once the Listen stream has called the onOpen() listener, any number of\n * listen() and unlisten() calls can be made to control what changes will be\n * sent from the server for ListenResponses.\n */\nexport class PersistentListenStream extends PersistentStream<\n api.ListenRequest,\n api.ListenResponse,\n WatchStreamListener\n> {\n constructor(\n queue: AsyncQueue,\n connection: Connection,\n credentials: CredentialsProvider,\n private serializer: JsonProtoSerializer,\n listener: WatchStreamListener\n ) {\n super(\n queue,\n TimerId.ListenStreamConnectionBackoff,\n TimerId.ListenStreamIdle,\n connection,\n credentials,\n listener\n );\n }\n\n protected startRpc(\n token: Token | null\n ): Stream {\n return this.connection.openStream(\n 'Listen',\n token\n );\n }\n\n protected onMessage(watchChangeProto: api.ListenResponse): Promise {\n // A successful response means the stream is healthy\n this.backoff.reset();\n\n const watchChange = fromWatchChange(this.serializer, watchChangeProto);\n const snapshot = versionFromListenResponse(watchChangeProto);\n return this.listener!.onWatchChange(watchChange, snapshot);\n }\n\n /**\n * Registers interest in the results of the given target. If the target\n * includes a resumeToken it will be included in the request. Results that\n * affect the target will be streamed back as WatchChange messages that\n * reference the targetId.\n */\n watch(targetData: TargetData): void {\n const request: ListenRequest = {};\n request.database = getEncodedDatabaseId(this.serializer);\n request.addTarget = toTarget(this.serializer, targetData);\n\n const labels = toListenRequestLabels(this.serializer, targetData);\n if (labels) {\n request.labels = labels;\n }\n\n this.sendRequest(request);\n }\n\n /**\n * Unregisters interest in the results of the target associated with the\n * given targetId.\n */\n unwatch(targetId: TargetId): void {\n const request: ListenRequest = {};\n request.database = getEncodedDatabaseId(this.serializer);\n request.removeTarget = targetId;\n this.sendRequest(request);\n }\n}\n\n/** Listener for the PersistentWriteStream */\nexport interface WriteStreamListener extends PersistentStreamListener {\n /**\n * Called by the PersistentWriteStream upon a successful handshake response\n * from the server, which is the receiver's cue to send any pending writes.\n */\n onHandshakeComplete: () => Promise;\n\n /**\n * Called by the PersistentWriteStream upon receiving a StreamingWriteResponse\n * from the server that contains a mutation result.\n */\n onMutationResult: (\n commitVersion: SnapshotVersion,\n results: MutationResult[]\n ) => Promise;\n}\n\n/**\n * A Stream that implements the Write RPC.\n *\n * The Write RPC requires the caller to maintain special streamToken\n * state in between calls, to help the server understand which responses the\n * client has processed by the time the next request is made. Every response\n * will contain a streamToken; this value must be passed to the next\n * request.\n *\n * After calling start() on this stream, the next request must be a handshake,\n * containing whatever streamToken is on hand. Once a response to this\n * request is received, all pending mutations may be submitted. When\n * submitting multiple batches of mutations at the same time, it's\n * okay to use the same streamToken for the calls to writeMutations.\n *\n * TODO(b/33271235): Use proto types\n */\nexport class PersistentWriteStream extends PersistentStream<\n api.WriteRequest,\n api.WriteResponse,\n WriteStreamListener\n> {\n private handshakeComplete_ = false;\n\n constructor(\n queue: AsyncQueue,\n connection: Connection,\n credentials: CredentialsProvider,\n private serializer: JsonProtoSerializer,\n listener: WriteStreamListener\n ) {\n super(\n queue,\n TimerId.WriteStreamConnectionBackoff,\n TimerId.WriteStreamIdle,\n connection,\n credentials,\n listener\n );\n }\n\n /**\n * The last received stream token from the server, used to acknowledge which\n * responses the client has processed. Stream tokens are opaque checkpoint\n * markers whose only real value is their inclusion in the next request.\n *\n * PersistentWriteStream manages propagating this value from responses to the\n * next request.\n */\n private lastStreamToken: string | Uint8Array | undefined;\n\n /**\n * Tracks whether or not a handshake has been successfully exchanged and\n * the stream is ready to accept mutations.\n */\n get handshakeComplete(): boolean {\n return this.handshakeComplete_;\n }\n\n // Override of PersistentStream.start\n start(): void {\n this.handshakeComplete_ = false;\n this.lastStreamToken = undefined;\n super.start();\n }\n\n protected tearDown(): void {\n if (this.handshakeComplete_) {\n this.writeMutations([]);\n }\n }\n\n protected startRpc(\n token: Token | null\n ): Stream {\n return this.connection.openStream(\n 'Write',\n token\n );\n }\n\n protected onMessage(responseProto: api.WriteResponse): Promise {\n // Always capture the last stream token.\n hardAssert(\n !!responseProto.streamToken,\n 'Got a write response without a stream token'\n );\n this.lastStreamToken = responseProto.streamToken;\n\n if (!this.handshakeComplete_) {\n // The first response is always the handshake response\n hardAssert(\n !responseProto.writeResults || responseProto.writeResults.length === 0,\n 'Got mutation results for handshake'\n );\n this.handshakeComplete_ = true;\n return this.listener!.onHandshakeComplete();\n } else {\n // A successful first write response means the stream is healthy,\n // Note, that we could consider a successful handshake healthy, however,\n // the write itself might be causing an error we want to back off from.\n this.backoff.reset();\n\n const results = fromWriteResults(\n responseProto.writeResults,\n responseProto.commitTime\n );\n const commitVersion = fromVersion(responseProto.commitTime!);\n return this.listener!.onMutationResult(commitVersion, results);\n }\n }\n\n /**\n * Sends an initial streamToken to the server, performing the handshake\n * required to make the StreamingWrite RPC work. Subsequent\n * calls should wait until onHandshakeComplete was called.\n */\n writeHandshake(): void {\n debugAssert(this.isOpen(), 'Writing handshake requires an opened stream');\n debugAssert(!this.handshakeComplete_, 'Handshake already completed');\n debugAssert(\n !this.lastStreamToken,\n 'Stream token should be empty during handshake'\n );\n // TODO(dimond): Support stream resumption. We intentionally do not set the\n // stream token on the handshake, ignoring any stream token we might have.\n const request: WriteRequest = {};\n request.database = getEncodedDatabaseId(this.serializer);\n this.sendRequest(request);\n }\n\n /** Sends a group of mutations to the Firestore backend to apply. */\n writeMutations(mutations: Mutation[]): void {\n debugAssert(this.isOpen(), 'Writing mutations requires an opened stream');\n debugAssert(\n this.handshakeComplete_,\n 'Handshake must be complete before writing mutations'\n );\n debugAssert(\n !!this.lastStreamToken,\n 'Trying to write mutation without a token'\n );\n\n const request: WriteRequest = {\n streamToken: this.lastStreamToken,\n writes: mutations.map(mutation => toMutation(this.serializer, mutation))\n };\n\n this.sendRequest(request);\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CredentialsProvider } from '../api/credentials';\nimport { Document, MaybeDocument } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport { Mutation } from '../model/mutation';\nimport * as api from '../protos/firestore_proto_api';\nimport { debugCast, hardAssert } from '../util/assert';\nimport { Code, FirestoreError } from '../util/error';\nimport { Connection } from './connection';\nimport {\n fromDocument,\n fromMaybeDocument,\n getEncodedDatabaseId,\n JsonProtoSerializer,\n toMutation,\n toName,\n toQueryTarget\n} from './serializer';\nimport {\n PersistentListenStream,\n PersistentWriteStream,\n WatchStreamListener,\n WriteStreamListener\n} from './persistent_stream';\nimport { AsyncQueue } from '../util/async_queue';\nimport { Query } from '../core/query';\n\n/**\n * Datastore and its related methods are a wrapper around the external Google\n * Cloud Datastore grpc API, which provides an interface that is more convenient\n * for the rest of the client SDK architecture to consume.\n */\nexport class Datastore {\n // Make sure that the structural type of `Datastore` is unique.\n // See https://github.com/microsoft/TypeScript/issues/5451\n private _ = undefined;\n}\n\n/**\n * An implementation of Datastore that exposes additional state for internal\n * consumption.\n */\nclass DatastoreImpl extends Datastore {\n terminated = false;\n\n constructor(\n readonly connection: Connection,\n readonly credentials: CredentialsProvider,\n readonly serializer: JsonProtoSerializer\n ) {\n super();\n }\n\n private verifyNotTerminated(): void {\n if (this.terminated) {\n throw new FirestoreError(\n Code.FAILED_PRECONDITION,\n 'The client has already been terminated.'\n );\n }\n }\n\n /** Gets an auth token and invokes the provided RPC. */\n invokeRPC(rpcName: string, request: Req): Promise {\n this.verifyNotTerminated();\n return this.credentials\n .getToken()\n .then(token => {\n return this.connection.invokeRPC(rpcName, request, token);\n })\n .catch((error: FirestoreError) => {\n if (error.code === Code.UNAUTHENTICATED) {\n this.credentials.invalidateToken();\n }\n throw error;\n });\n }\n\n /** Gets an auth token and invokes the provided RPC with streamed results. */\n invokeStreamingRPC(\n rpcName: string,\n request: Req\n ): Promise {\n this.verifyNotTerminated();\n return this.credentials\n .getToken()\n .then(token => {\n return this.connection.invokeStreamingRPC(\n rpcName,\n request,\n token\n );\n })\n .catch((error: FirestoreError) => {\n if (error.code === Code.UNAUTHENTICATED) {\n this.credentials.invalidateToken();\n }\n throw error;\n });\n }\n}\n\nexport function newDatastore(\n connection: Connection,\n credentials: CredentialsProvider,\n serializer: JsonProtoSerializer\n): Datastore {\n return new DatastoreImpl(connection, credentials, serializer);\n}\n\nexport async function invokeCommitRpc(\n datastore: Datastore,\n mutations: Mutation[]\n): Promise {\n const datastoreImpl = debugCast(datastore, DatastoreImpl);\n const params = {\n database: getEncodedDatabaseId(datastoreImpl.serializer),\n writes: mutations.map(m => toMutation(datastoreImpl.serializer, m))\n };\n await datastoreImpl.invokeRPC('Commit', params);\n}\n\nexport async function invokeBatchGetDocumentsRpc(\n datastore: Datastore,\n keys: DocumentKey[]\n): Promise {\n const datastoreImpl = debugCast(datastore, DatastoreImpl);\n const params = {\n database: getEncodedDatabaseId(datastoreImpl.serializer),\n documents: keys.map(k => toName(datastoreImpl.serializer, k))\n };\n const response = await datastoreImpl.invokeStreamingRPC<\n api.BatchGetDocumentsRequest,\n api.BatchGetDocumentsResponse\n >('BatchGetDocuments', params);\n\n const docs = new Map();\n response.forEach(proto => {\n const doc = fromMaybeDocument(datastoreImpl.serializer, proto);\n docs.set(doc.key.toString(), doc);\n });\n const result: MaybeDocument[] = [];\n keys.forEach(key => {\n const doc = docs.get(key.toString());\n hardAssert(!!doc, 'Missing entity in write response for ' + key);\n result.push(doc);\n });\n return result;\n}\n\nexport async function invokeRunQueryRpc(\n datastore: Datastore,\n query: Query\n): Promise {\n const datastoreImpl = debugCast(datastore, DatastoreImpl);\n const { structuredQuery, parent } = toQueryTarget(\n datastoreImpl.serializer,\n query.toTarget()\n );\n const params = {\n database: getEncodedDatabaseId(datastoreImpl.serializer),\n parent,\n structuredQuery\n };\n\n const response = await datastoreImpl.invokeStreamingRPC<\n api.RunQueryRequest,\n api.RunQueryResponse\n >('RunQuery', params);\n\n return (\n response\n // Omit RunQueryResponses that only contain readTimes.\n .filter(proto => !!proto.document)\n .map(proto =>\n fromDocument(datastoreImpl.serializer, proto.document!, undefined)\n )\n );\n}\n\nexport function newPersistentWriteStream(\n datastore: Datastore,\n queue: AsyncQueue,\n listener: WriteStreamListener\n): PersistentWriteStream {\n const datastoreImpl = debugCast(datastore, DatastoreImpl);\n return new PersistentWriteStream(\n queue,\n datastoreImpl.connection,\n datastoreImpl.credentials,\n datastoreImpl.serializer,\n listener\n );\n}\n\nexport function newPersistentWatchStream(\n datastore: Datastore,\n queue: AsyncQueue,\n listener: WatchStreamListener\n): PersistentListenStream {\n const datastoreImpl = debugCast(datastore, DatastoreImpl);\n return new PersistentListenStream(\n queue,\n datastoreImpl.connection,\n datastoreImpl.credentials,\n datastoreImpl.serializer,\n listener\n );\n}\n\nexport function terminateDatastore(datastore: Datastore): void {\n const datastoreImpl = debugCast(datastore, DatastoreImpl);\n datastoreImpl.terminated = true;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ParsedSetData, ParsedUpdateData } from '../api/user_data_reader';\nimport { Document, MaybeDocument, NoDocument } from '../model/document';\n\nimport { DocumentKey } from '../model/document_key';\nimport {\n DeleteMutation,\n Mutation,\n Precondition,\n VerifyMutation\n} from '../model/mutation';\nimport {\n Datastore,\n invokeBatchGetDocumentsRpc,\n invokeCommitRpc\n} from '../remote/datastore';\nimport { fail, debugAssert } from '../util/assert';\nimport { Code, FirestoreError } from '../util/error';\nimport { SnapshotVersion } from './snapshot_version';\nimport { ResourcePath } from '../model/path';\n\n/**\n * Internal transaction object responsible for accumulating the mutations to\n * perform and the base versions for any documents read.\n */\nexport class Transaction {\n // The version of each document that was read during this transaction.\n private readVersions = new Map();\n private mutations: Mutation[] = [];\n private committed = false;\n\n /**\n * A deferred usage error that occurred previously in this transaction that\n * will cause the transaction to fail once it actually commits.\n */\n private lastWriteError: FirestoreError | null = null;\n\n /**\n * Set of documents that have been written in the transaction.\n *\n * When there's more than one write to the same key in a transaction, any\n * writes after the first are handled differently.\n */\n private writtenDocs: Set = new Set();\n\n constructor(private datastore: Datastore) {}\n\n async lookup(keys: DocumentKey[]): Promise {\n this.ensureCommitNotCalled();\n\n if (this.mutations.length > 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Firestore transactions require all reads to be executed before all writes.'\n );\n }\n const docs = await invokeBatchGetDocumentsRpc(this.datastore, keys);\n docs.forEach(doc => {\n if (doc instanceof NoDocument || doc instanceof Document) {\n this.recordVersion(doc);\n } else {\n fail('Document in a transaction was a ' + doc.constructor.name);\n }\n });\n return docs;\n }\n\n set(key: DocumentKey, data: ParsedSetData): void {\n this.write(data.toMutations(key, this.precondition(key)));\n this.writtenDocs.add(key);\n }\n\n update(key: DocumentKey, data: ParsedUpdateData): void {\n try {\n this.write(data.toMutations(key, this.preconditionForUpdate(key)));\n } catch (e) {\n this.lastWriteError = e;\n }\n this.writtenDocs.add(key);\n }\n\n delete(key: DocumentKey): void {\n this.write([new DeleteMutation(key, this.precondition(key))]);\n this.writtenDocs.add(key);\n }\n\n async commit(): Promise {\n this.ensureCommitNotCalled();\n\n if (this.lastWriteError) {\n throw this.lastWriteError;\n }\n const unwritten = this.readVersions;\n // For each mutation, note that the doc was written.\n this.mutations.forEach(mutation => {\n unwritten.delete(mutation.key.toString());\n });\n // For each document that was read but not written to, we want to perform\n // a `verify` operation.\n unwritten.forEach((_, path) => {\n const key = new DocumentKey(ResourcePath.fromString(path));\n this.mutations.push(new VerifyMutation(key, this.precondition(key)));\n });\n await invokeCommitRpc(this.datastore, this.mutations);\n this.committed = true;\n }\n\n private recordVersion(doc: MaybeDocument): void {\n let docVersion: SnapshotVersion;\n\n if (doc instanceof Document) {\n docVersion = doc.version;\n } else if (doc instanceof NoDocument) {\n // For deleted docs, we must use baseVersion 0 when we overwrite them.\n docVersion = SnapshotVersion.min();\n } else {\n throw fail('Document in a transaction was a ' + doc.constructor.name);\n }\n\n const existingVersion = this.readVersions.get(doc.key.toString());\n if (existingVersion) {\n if (!docVersion.isEqual(existingVersion)) {\n // This transaction will fail no matter what.\n throw new FirestoreError(\n Code.ABORTED,\n 'Document version changed between two reads.'\n );\n }\n } else {\n this.readVersions.set(doc.key.toString(), docVersion);\n }\n }\n\n /**\n * Returns the version of this document when it was read in this transaction,\n * as a precondition, or no precondition if it was not read.\n */\n private precondition(key: DocumentKey): Precondition {\n const version = this.readVersions.get(key.toString());\n if (!this.writtenDocs.has(key) && version) {\n return Precondition.updateTime(version);\n } else {\n return Precondition.none();\n }\n }\n\n /**\n * Returns the precondition for a document if the operation is an update.\n */\n private preconditionForUpdate(key: DocumentKey): Precondition {\n const version = this.readVersions.get(key.toString());\n // The first time a document is written, we want to take into account the\n // read time and existence\n if (!this.writtenDocs.has(key) && version) {\n if (version.isEqual(SnapshotVersion.min())) {\n // The document doesn't exist, so fail the transaction.\n\n // This has to be validated locally because you can't send a\n // precondition that a document does not exist without changing the\n // semantics of the backend write to be an insert. This is the reverse\n // of what we want, since we want to assert that the document doesn't\n // exist but then send the update and have it fail. Since we can't\n // express that to the backend, we have to validate locally.\n\n // Note: this can change once we can send separate verify writes in the\n // transaction.\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n \"Can't update a document that doesn't exist.\"\n );\n }\n // Document exists, base precondition on document update time.\n return Precondition.updateTime(version);\n } else {\n // Document was not read, so we just use the preconditions for a blind\n // update.\n return Precondition.exists(true);\n }\n }\n\n private write(mutations: Mutation[]): void {\n this.ensureCommitNotCalled();\n this.mutations = this.mutations.concat(mutations);\n }\n\n private ensureCommitNotCalled(): void {\n debugAssert(\n !this.committed,\n 'A transaction object cannot be used after its update callback has been invoked.'\n );\n }\n}\n","/**\n * @license\n * Copyright 2018 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { OnlineState } from '../core/types';\nimport { debugAssert } from '../util/assert';\nimport { AsyncQueue, DelayedOperation, TimerId } from '../util/async_queue';\nimport { FirestoreError } from '../util/error';\nimport { logError, logDebug } from '../util/log';\n\nconst LOG_TAG = 'OnlineStateTracker';\n\n// To deal with transient failures, we allow multiple stream attempts before\n// giving up and transitioning from OnlineState.Unknown to Offline.\n// TODO(mikelehen): This used to be set to 2 as a mitigation for b/66228394.\n// @jdimond thinks that bug is sufficiently fixed so that we can set this back\n// to 1. If that works okay, we could potentially remove this logic entirely.\nconst MAX_WATCH_STREAM_FAILURES = 1;\n\n// To deal with stream attempts that don't succeed or fail in a timely manner,\n// we have a timeout for OnlineState to reach Online or Offline.\n// If the timeout is reached, we transition to Offline rather than waiting\n// indefinitely.\nconst ONLINE_STATE_TIMEOUT_MS = 10 * 1000;\n\n/**\n * A component used by the RemoteStore to track the OnlineState (that is,\n * whether or not the client as a whole should be considered to be online or\n * offline), implementing the appropriate heuristics.\n *\n * In particular, when the client is trying to connect to the backend, we\n * allow up to MAX_WATCH_STREAM_FAILURES within ONLINE_STATE_TIMEOUT_MS for\n * a connection to succeed. If we have too many failures or the timeout elapses,\n * then we set the OnlineState to Offline, and the client will behave as if\n * it is offline (get()s will return cached data, etc.).\n */\nexport class OnlineStateTracker {\n /** The current OnlineState. */\n private state = OnlineState.Unknown;\n\n /**\n * A count of consecutive failures to open the stream. If it reaches the\n * maximum defined by MAX_WATCH_STREAM_FAILURES, we'll set the OnlineState to\n * Offline.\n */\n private watchStreamFailures = 0;\n\n /**\n * A timer that elapses after ONLINE_STATE_TIMEOUT_MS, at which point we\n * transition from OnlineState.Unknown to OnlineState.Offline without waiting\n * for the stream to actually fail (MAX_WATCH_STREAM_FAILURES times).\n */\n private onlineStateTimer: DelayedOperation | null = null;\n\n /**\n * Whether the client should log a warning message if it fails to connect to\n * the backend (initially true, cleared after a successful stream, or if we've\n * logged the message already).\n */\n private shouldWarnClientIsOffline = true;\n\n constructor(\n private asyncQueue: AsyncQueue,\n private onlineStateHandler: (onlineState: OnlineState) => void\n ) {}\n\n /**\n * Called by RemoteStore when a watch stream is started (including on each\n * backoff attempt).\n *\n * If this is the first attempt, it sets the OnlineState to Unknown and starts\n * the onlineStateTimer.\n */\n handleWatchStreamStart(): void {\n if (this.watchStreamFailures === 0) {\n this.setAndBroadcast(OnlineState.Unknown);\n\n debugAssert(\n this.onlineStateTimer === null,\n `onlineStateTimer shouldn't be started yet`\n );\n this.onlineStateTimer = this.asyncQueue.enqueueAfterDelay(\n TimerId.OnlineStateTimeout,\n ONLINE_STATE_TIMEOUT_MS,\n () => {\n this.onlineStateTimer = null;\n debugAssert(\n this.state === OnlineState.Unknown,\n 'Timer should be canceled if we transitioned to a different state.'\n );\n this.logClientOfflineWarningIfNecessary(\n `Backend didn't respond within ${ONLINE_STATE_TIMEOUT_MS / 1000} ` +\n `seconds.`\n );\n this.setAndBroadcast(OnlineState.Offline);\n\n // NOTE: handleWatchStreamFailure() will continue to increment\n // watchStreamFailures even though we are already marked Offline,\n // but this is non-harmful.\n\n return Promise.resolve();\n }\n );\n }\n }\n\n /**\n * Updates our OnlineState as appropriate after the watch stream reports a\n * failure. The first failure moves us to the 'Unknown' state. We then may\n * allow multiple failures (based on MAX_WATCH_STREAM_FAILURES) before we\n * actually transition to the 'Offline' state.\n */\n handleWatchStreamFailure(error: FirestoreError): void {\n if (this.state === OnlineState.Online) {\n this.setAndBroadcast(OnlineState.Unknown);\n\n // To get to OnlineState.Online, set() must have been called which would\n // have reset our heuristics.\n debugAssert(\n this.watchStreamFailures === 0,\n 'watchStreamFailures must be 0'\n );\n debugAssert(\n this.onlineStateTimer === null,\n 'onlineStateTimer must be null'\n );\n } else {\n this.watchStreamFailures++;\n if (this.watchStreamFailures >= MAX_WATCH_STREAM_FAILURES) {\n this.clearOnlineStateTimer();\n\n this.logClientOfflineWarningIfNecessary(\n `Connection failed ${MAX_WATCH_STREAM_FAILURES} ` +\n `times. Most recent error: ${error.toString()}`\n );\n\n this.setAndBroadcast(OnlineState.Offline);\n }\n }\n }\n\n /**\n * Explicitly sets the OnlineState to the specified state.\n *\n * Note that this resets our timers / failure counters, etc. used by our\n * Offline heuristics, so must not be used in place of\n * handleWatchStreamStart() and handleWatchStreamFailure().\n */\n set(newState: OnlineState): void {\n this.clearOnlineStateTimer();\n this.watchStreamFailures = 0;\n\n if (newState === OnlineState.Online) {\n // We've connected to watch at least once. Don't warn the developer\n // about being offline going forward.\n this.shouldWarnClientIsOffline = false;\n }\n\n this.setAndBroadcast(newState);\n }\n\n private setAndBroadcast(newState: OnlineState): void {\n if (newState !== this.state) {\n this.state = newState;\n this.onlineStateHandler(newState);\n }\n }\n\n private logClientOfflineWarningIfNecessary(details: string): void {\n const message =\n `Could not reach Cloud Firestore backend. ${details}\\n` +\n `This typically indicates that your device does not have a healthy ` +\n `Internet connection at the moment. The client will operate in offline ` +\n `mode until it is able to successfully connect to the backend.`;\n if (this.shouldWarnClientIsOffline) {\n logError(message);\n this.shouldWarnClientIsOffline = false;\n } else {\n logDebug(LOG_TAG, message);\n }\n }\n\n private clearOnlineStateTimer(): void {\n if (this.onlineStateTimer !== null) {\n this.onlineStateTimer.cancel();\n this.onlineStateTimer = null;\n }\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { Transaction } from '../core/transaction';\nimport { OnlineState, TargetId } from '../core/types';\nimport { LocalStore } from '../local/local_store';\nimport { TargetData, TargetPurpose } from '../local/target_data';\nimport { MutationResult } from '../model/mutation';\nimport {\n BATCHID_UNKNOWN,\n MutationBatch,\n MutationBatchResult\n} from '../model/mutation_batch';\nimport { debugAssert } from '../util/assert';\nimport { FirestoreError } from '../util/error';\nimport { logDebug } from '../util/log';\nimport { DocumentKeySet } from '../model/collections';\nimport { AsyncQueue } from '../util/async_queue';\nimport { ConnectivityMonitor, NetworkStatus } from './connectivity_monitor';\nimport {\n Datastore,\n newPersistentWatchStream,\n newPersistentWriteStream\n} from './datastore';\nimport { OnlineStateTracker } from './online_state_tracker';\nimport {\n PersistentListenStream,\n PersistentWriteStream\n} from './persistent_stream';\nimport { RemoteSyncer } from './remote_syncer';\nimport { isPermanentWriteError } from './rpc_error';\nimport {\n DocumentWatchChange,\n ExistenceFilterChange,\n TargetMetadataProvider,\n WatchChange,\n WatchChangeAggregator,\n WatchTargetChange,\n WatchTargetChangeState\n} from './watch_change';\nimport { ByteString } from '../util/byte_string';\nimport { isIndexedDbTransactionError } from '../local/simple_db';\nimport { User } from '../auth/user';\n\nconst LOG_TAG = 'RemoteStore';\n\n// TODO(b/35853402): Negotiate this with the stream.\nconst MAX_PENDING_WRITES = 10;\n\n/** Reasons for why the RemoteStore may be offline. */\nconst enum OfflineCause {\n /** The user has explicitly disabled the network (via `disableNetwork()`). */\n UserDisabled,\n /** An IndexedDb failure occurred while persisting a stream update. */\n IndexedDbFailed,\n /** The tab is not the primary tab (only relevant with multi-tab). */\n IsSecondary,\n /** We are restarting the streams due to an Auth credential change. */\n CredentialChange,\n /** The connectivity state of the environment has changed. */\n ConnectivityChange,\n /** The RemoteStore has been shut down. */\n Shutdown\n}\n\n/**\n * RemoteStore - An interface to remotely stored data, basically providing a\n * wrapper around the Datastore that is more reliable for the rest of the\n * system.\n *\n * RemoteStore is responsible for maintaining the connection to the server.\n * - maintaining a list of active listens.\n * - reconnecting when the connection is dropped.\n * - resuming all the active listens on reconnect.\n *\n * RemoteStore handles all incoming events from the Datastore.\n * - listening to the watch stream and repackaging the events as RemoteEvents\n * - notifying SyncEngine of any changes to the active listens.\n *\n * RemoteStore takes writes from other components and handles them reliably.\n * - pulling pending mutations from LocalStore and sending them to Datastore.\n * - retrying mutations that failed because of network problems.\n * - acking mutations to the SyncEngine once they are accepted or rejected.\n */\nexport class RemoteStore implements TargetMetadataProvider {\n /**\n * A list of up to MAX_PENDING_WRITES writes that we have fetched from the\n * LocalStore via fillWritePipeline() and have or will send to the write\n * stream.\n *\n * Whenever writePipeline.length > 0 the RemoteStore will attempt to start or\n * restart the write stream. When the stream is established the writes in the\n * pipeline will be sent in order.\n *\n * Writes remain in writePipeline until they are acknowledged by the backend\n * and thus will automatically be re-sent if the stream is interrupted /\n * restarted before they're acknowledged.\n *\n * Write responses from the backend are linked to their originating request\n * purely based on order, and so we can just shift() writes from the front of\n * the writePipeline as we receive responses.\n */\n private writePipeline: MutationBatch[] = [];\n\n /**\n * A mapping of watched targets that the client cares about tracking and the\n * user has explicitly called a 'listen' for this target.\n *\n * These targets may or may not have been sent to or acknowledged by the\n * server. On re-establishing the listen stream, these targets should be sent\n * to the server. The targets removed with unlistens are removed eagerly\n * without waiting for confirmation from the listen stream.\n */\n private listenTargets = new Map();\n\n private connectivityMonitor: ConnectivityMonitor;\n private watchStream: PersistentListenStream;\n private writeStream: PersistentWriteStream;\n private watchChangeAggregator: WatchChangeAggregator | null = null;\n\n /**\n * A set of reasons for why the RemoteStore may be offline. If empty, the\n * RemoteStore may start its network connections.\n */\n private offlineCauses = new Set();\n\n private onlineStateTracker: OnlineStateTracker;\n\n constructor(\n /**\n * The local store, used to fill the write pipeline with outbound mutations.\n */\n private localStore: LocalStore,\n /** The client-side proxy for interacting with the backend. */\n private datastore: Datastore,\n private asyncQueue: AsyncQueue,\n onlineStateHandler: (onlineState: OnlineState) => void,\n connectivityMonitor: ConnectivityMonitor\n ) {\n this.connectivityMonitor = connectivityMonitor;\n this.connectivityMonitor.addCallback((_: NetworkStatus) => {\n asyncQueue.enqueueAndForget(async () => {\n // Porting Note: Unlike iOS, `restartNetwork()` is called even when the\n // network becomes unreachable as we don't have any other way to tear\n // down our streams.\n if (this.canUseNetwork()) {\n logDebug(\n LOG_TAG,\n 'Restarting streams for network reachability change.'\n );\n await this.restartNetwork();\n }\n });\n });\n\n this.onlineStateTracker = new OnlineStateTracker(\n asyncQueue,\n onlineStateHandler\n );\n\n // Create streams (but note they're not started yet).\n this.watchStream = newPersistentWatchStream(this.datastore, asyncQueue, {\n onOpen: this.onWatchStreamOpen.bind(this),\n onClose: this.onWatchStreamClose.bind(this),\n onWatchChange: this.onWatchStreamChange.bind(this)\n });\n\n this.writeStream = newPersistentWriteStream(this.datastore, asyncQueue, {\n onOpen: this.onWriteStreamOpen.bind(this),\n onClose: this.onWriteStreamClose.bind(this),\n onHandshakeComplete: this.onWriteHandshakeComplete.bind(this),\n onMutationResult: this.onMutationResult.bind(this)\n });\n }\n\n /**\n * SyncEngine to notify of watch and write events. This must be set\n * immediately after construction.\n */\n syncEngine!: RemoteSyncer;\n\n /**\n * Starts up the remote store, creating streams, restoring state from\n * LocalStore, etc.\n */\n start(): Promise {\n return this.enableNetwork();\n }\n\n /** Re-enables the network. Idempotent. */\n enableNetwork(): Promise {\n this.offlineCauses.delete(OfflineCause.UserDisabled);\n return this.enableNetworkInternal();\n }\n\n private async enableNetworkInternal(): Promise {\n if (this.canUseNetwork()) {\n if (this.shouldStartWatchStream()) {\n this.startWatchStream();\n } else {\n this.onlineStateTracker.set(OnlineState.Unknown);\n }\n\n // This will start the write stream if necessary.\n await this.fillWritePipeline();\n }\n }\n\n /**\n * Temporarily disables the network. The network can be re-enabled using\n * enableNetwork().\n */\n async disableNetwork(): Promise {\n this.offlineCauses.add(OfflineCause.UserDisabled);\n await this.disableNetworkInternal();\n\n // Set the OnlineState to Offline so get()s return from cache, etc.\n this.onlineStateTracker.set(OnlineState.Offline);\n }\n\n private async disableNetworkInternal(): Promise {\n await this.writeStream.stop();\n await this.watchStream.stop();\n\n if (this.writePipeline.length > 0) {\n logDebug(\n LOG_TAG,\n `Stopping write stream with ${this.writePipeline.length} pending writes`\n );\n this.writePipeline = [];\n }\n\n this.cleanUpWatchStreamState();\n }\n\n async shutdown(): Promise {\n logDebug(LOG_TAG, 'RemoteStore shutting down.');\n this.offlineCauses.add(OfflineCause.Shutdown);\n await this.disableNetworkInternal();\n this.connectivityMonitor.shutdown();\n\n // Set the OnlineState to Unknown (rather than Offline) to avoid potentially\n // triggering spurious listener events with cached data, etc.\n this.onlineStateTracker.set(OnlineState.Unknown);\n }\n\n /**\n * Starts new listen for the given target. Uses resume token if provided. It\n * is a no-op if the target of given `TargetData` is already being listened to.\n */\n listen(targetData: TargetData): void {\n if (this.listenTargets.has(targetData.targetId)) {\n return;\n }\n\n // Mark this as something the client is currently listening for.\n this.listenTargets.set(targetData.targetId, targetData);\n\n if (this.shouldStartWatchStream()) {\n // The listen will be sent in onWatchStreamOpen\n this.startWatchStream();\n } else if (this.watchStream.isOpen()) {\n this.sendWatchRequest(targetData);\n }\n }\n\n /**\n * Removes the listen from server. It is a no-op if the given target id is\n * not being listened to.\n */\n unlisten(targetId: TargetId): void {\n debugAssert(\n this.listenTargets.has(targetId),\n `unlisten called on target no currently watched: ${targetId}`\n );\n\n this.listenTargets.delete(targetId);\n if (this.watchStream.isOpen()) {\n this.sendUnwatchRequest(targetId);\n }\n\n if (this.listenTargets.size === 0) {\n if (this.watchStream.isOpen()) {\n this.watchStream.markIdle();\n } else if (this.canUseNetwork()) {\n // Revert to OnlineState.Unknown if the watch stream is not open and we\n // have no listeners, since without any listens to send we cannot\n // confirm if the stream is healthy and upgrade to OnlineState.Online.\n this.onlineStateTracker.set(OnlineState.Unknown);\n }\n }\n }\n\n /** {@link TargetMetadataProvider.getTargetDataForTarget} */\n getTargetDataForTarget(targetId: TargetId): TargetData | null {\n return this.listenTargets.get(targetId) || null;\n }\n\n /** {@link TargetMetadataProvider.getRemoteKeysForTarget} */\n getRemoteKeysForTarget(targetId: TargetId): DocumentKeySet {\n return this.syncEngine.getRemoteKeysForTarget(targetId);\n }\n\n /**\n * We need to increment the the expected number of pending responses we're due\n * from watch so we wait for the ack to process any messages from this target.\n */\n private sendWatchRequest(targetData: TargetData): void {\n this.watchChangeAggregator!.recordPendingTargetRequest(targetData.targetId);\n this.watchStream.watch(targetData);\n }\n\n /**\n * We need to increment the expected number of pending responses we're due\n * from watch so we wait for the removal on the server before we process any\n * messages from this target.\n */\n private sendUnwatchRequest(targetId: TargetId): void {\n this.watchChangeAggregator!.recordPendingTargetRequest(targetId);\n this.watchStream.unwatch(targetId);\n }\n\n private startWatchStream(): void {\n debugAssert(\n this.shouldStartWatchStream(),\n 'startWatchStream() called when shouldStartWatchStream() is false.'\n );\n\n this.watchChangeAggregator = new WatchChangeAggregator(this);\n this.watchStream.start();\n this.onlineStateTracker.handleWatchStreamStart();\n }\n\n /**\n * Returns whether the watch stream should be started because it's necessary\n * and has not yet been started.\n */\n private shouldStartWatchStream(): boolean {\n return (\n this.canUseNetwork() &&\n !this.watchStream.isStarted() &&\n this.listenTargets.size > 0\n );\n }\n\n canUseNetwork(): boolean {\n return this.offlineCauses.size === 0;\n }\n\n private cleanUpWatchStreamState(): void {\n this.watchChangeAggregator = null;\n }\n\n private async onWatchStreamOpen(): Promise {\n this.listenTargets.forEach((targetData, targetId) => {\n this.sendWatchRequest(targetData);\n });\n }\n\n private async onWatchStreamClose(error?: FirestoreError): Promise {\n if (error === undefined) {\n // Graceful stop (due to stop() or idle timeout). Make sure that's\n // desirable.\n debugAssert(\n !this.shouldStartWatchStream(),\n 'Watch stream was stopped gracefully while still needed.'\n );\n }\n\n this.cleanUpWatchStreamState();\n\n // If we still need the watch stream, retry the connection.\n if (this.shouldStartWatchStream()) {\n this.onlineStateTracker.handleWatchStreamFailure(error!);\n\n this.startWatchStream();\n } else {\n // No need to restart watch stream because there are no active targets.\n // The online state is set to unknown because there is no active attempt\n // at establishing a connection\n this.onlineStateTracker.set(OnlineState.Unknown);\n }\n }\n\n private async onWatchStreamChange(\n watchChange: WatchChange,\n snapshotVersion: SnapshotVersion\n ): Promise {\n // Mark the client as online since we got a message from the server\n this.onlineStateTracker.set(OnlineState.Online);\n\n if (\n watchChange instanceof WatchTargetChange &&\n watchChange.state === WatchTargetChangeState.Removed &&\n watchChange.cause\n ) {\n // There was an error on a target, don't wait for a consistent snapshot\n // to raise events\n try {\n await this.handleTargetError(watchChange);\n } catch (e) {\n logDebug(\n LOG_TAG,\n 'Failed to remove targets %s: %s ',\n watchChange.targetIds.join(','),\n e\n );\n await this.disableNetworkUntilRecovery(e);\n }\n return;\n }\n\n if (watchChange instanceof DocumentWatchChange) {\n this.watchChangeAggregator!.handleDocumentChange(watchChange);\n } else if (watchChange instanceof ExistenceFilterChange) {\n this.watchChangeAggregator!.handleExistenceFilter(watchChange);\n } else {\n debugAssert(\n watchChange instanceof WatchTargetChange,\n 'Expected watchChange to be an instance of WatchTargetChange'\n );\n this.watchChangeAggregator!.handleTargetChange(watchChange);\n }\n\n if (!snapshotVersion.isEqual(SnapshotVersion.min())) {\n try {\n const lastRemoteSnapshotVersion = await this.localStore.getLastRemoteSnapshotVersion();\n if (snapshotVersion.compareTo(lastRemoteSnapshotVersion) >= 0) {\n // We have received a target change with a global snapshot if the snapshot\n // version is not equal to SnapshotVersion.min().\n await this.raiseWatchSnapshot(snapshotVersion);\n }\n } catch (e) {\n logDebug(LOG_TAG, 'Failed to raise snapshot:', e);\n await this.disableNetworkUntilRecovery(e);\n }\n }\n }\n\n /**\n * Recovery logic for IndexedDB errors that takes the network offline until\n * `op` succeeds. Retries are scheduled with backoff using\n * `enqueueRetryable()`. If `op()` is not provided, IndexedDB access is\n * validated via a generic operation.\n *\n * The returned Promise is resolved once the network is disabled and before\n * any retry attempt.\n */\n private async disableNetworkUntilRecovery(\n e: FirestoreError,\n op?: () => Promise\n ): Promise {\n if (isIndexedDbTransactionError(e)) {\n debugAssert(\n !this.offlineCauses.has(OfflineCause.IndexedDbFailed),\n 'Unexpected network event when IndexedDB was marked failed.'\n );\n this.offlineCauses.add(OfflineCause.IndexedDbFailed);\n\n // Disable network and raise offline snapshots\n await this.disableNetworkInternal();\n this.onlineStateTracker.set(OnlineState.Offline);\n\n if (!op) {\n // Use a simple read operation to determine if IndexedDB recovered.\n // Ideally, we would expose a health check directly on SimpleDb, but\n // RemoteStore only has access to persistence through LocalStore.\n op = () => this.localStore.getLastRemoteSnapshotVersion();\n }\n\n // Probe IndexedDB periodically and re-enable network\n this.asyncQueue.enqueueRetryable(async () => {\n logDebug(LOG_TAG, 'Retrying IndexedDB access');\n await op!();\n this.offlineCauses.delete(OfflineCause.IndexedDbFailed);\n await this.enableNetworkInternal();\n });\n } else {\n throw e;\n }\n }\n\n /**\n * Executes `op`. If `op` fails, takes the network offline until `op`\n * succeeds. Returns after the first attempt.\n */\n private executeWithRecovery(op: () => Promise): Promise {\n return op().catch(e => this.disableNetworkUntilRecovery(e, op));\n }\n\n /**\n * Takes a batch of changes from the Datastore, repackages them as a\n * RemoteEvent, and passes that on to the listener, which is typically the\n * SyncEngine.\n */\n private raiseWatchSnapshot(snapshotVersion: SnapshotVersion): Promise {\n debugAssert(\n !snapshotVersion.isEqual(SnapshotVersion.min()),\n \"Can't raise event for unknown SnapshotVersion\"\n );\n const remoteEvent = this.watchChangeAggregator!.createRemoteEvent(\n snapshotVersion\n );\n\n // Update in-memory resume tokens. LocalStore will update the\n // persistent view of these when applying the completed RemoteEvent.\n remoteEvent.targetChanges.forEach((change, targetId) => {\n if (change.resumeToken.approximateByteSize() > 0) {\n const targetData = this.listenTargets.get(targetId);\n // A watched target might have been removed already.\n if (targetData) {\n this.listenTargets.set(\n targetId,\n targetData.withResumeToken(change.resumeToken, snapshotVersion)\n );\n }\n }\n });\n\n // Re-establish listens for the targets that have been invalidated by\n // existence filter mismatches.\n remoteEvent.targetMismatches.forEach(targetId => {\n const targetData = this.listenTargets.get(targetId);\n if (!targetData) {\n // A watched target might have been removed already.\n return;\n }\n\n // Clear the resume token for the target, since we're in a known mismatch\n // state.\n this.listenTargets.set(\n targetId,\n targetData.withResumeToken(\n ByteString.EMPTY_BYTE_STRING,\n targetData.snapshotVersion\n )\n );\n\n // Cause a hard reset by unwatching and rewatching immediately, but\n // deliberately don't send a resume token so that we get a full update.\n this.sendUnwatchRequest(targetId);\n\n // Mark the target we send as being on behalf of an existence filter\n // mismatch, but don't actually retain that in listenTargets. This ensures\n // that we flag the first re-listen this way without impacting future\n // listens of this target (that might happen e.g. on reconnect).\n const requestTargetData = new TargetData(\n targetData.target,\n targetId,\n TargetPurpose.ExistenceFilterMismatch,\n targetData.sequenceNumber\n );\n this.sendWatchRequest(requestTargetData);\n });\n\n // Finally raise remote event\n return this.syncEngine.applyRemoteEvent(remoteEvent);\n }\n\n /** Handles an error on a target */\n private async handleTargetError(\n watchChange: WatchTargetChange\n ): Promise {\n debugAssert(!!watchChange.cause, 'Handling target error without a cause');\n const error = watchChange.cause!;\n for (const targetId of watchChange.targetIds) {\n // A watched target might have been removed already.\n if (this.listenTargets.has(targetId)) {\n await this.syncEngine.rejectListen(targetId, error);\n this.listenTargets.delete(targetId);\n this.watchChangeAggregator!.removeTarget(targetId);\n }\n }\n }\n\n /**\n * Attempts to fill our write pipeline with writes from the LocalStore.\n *\n * Called internally to bootstrap or refill the write pipeline and by\n * SyncEngine whenever there are new mutations to process.\n *\n * Starts the write stream if necessary.\n */\n async fillWritePipeline(): Promise {\n let lastBatchIdRetrieved =\n this.writePipeline.length > 0\n ? this.writePipeline[this.writePipeline.length - 1].batchId\n : BATCHID_UNKNOWN;\n\n while (this.canAddToWritePipeline()) {\n try {\n const batch = await this.localStore.nextMutationBatch(\n lastBatchIdRetrieved\n );\n\n if (batch === null) {\n if (this.writePipeline.length === 0) {\n this.writeStream.markIdle();\n }\n break;\n } else {\n lastBatchIdRetrieved = batch.batchId;\n this.addToWritePipeline(batch);\n }\n } catch (e) {\n await this.disableNetworkUntilRecovery(e);\n }\n }\n\n if (this.shouldStartWriteStream()) {\n this.startWriteStream();\n }\n }\n\n /**\n * Returns true if we can add to the write pipeline (i.e. the network is\n * enabled and the write pipeline is not full).\n */\n private canAddToWritePipeline(): boolean {\n return (\n this.canUseNetwork() && this.writePipeline.length < MAX_PENDING_WRITES\n );\n }\n\n // For testing\n outstandingWrites(): number {\n return this.writePipeline.length;\n }\n\n /**\n * Queues additional writes to be sent to the write stream, sending them\n * immediately if the write stream is established.\n */\n private addToWritePipeline(batch: MutationBatch): void {\n debugAssert(\n this.canAddToWritePipeline(),\n 'addToWritePipeline called when pipeline is full'\n );\n this.writePipeline.push(batch);\n\n if (this.writeStream.isOpen() && this.writeStream.handshakeComplete) {\n this.writeStream.writeMutations(batch.mutations);\n }\n }\n\n private shouldStartWriteStream(): boolean {\n return (\n this.canUseNetwork() &&\n !this.writeStream.isStarted() &&\n this.writePipeline.length > 0\n );\n }\n\n private startWriteStream(): void {\n debugAssert(\n this.shouldStartWriteStream(),\n 'startWriteStream() called when shouldStartWriteStream() is false.'\n );\n this.writeStream.start();\n }\n\n private async onWriteStreamOpen(): Promise {\n this.writeStream.writeHandshake();\n }\n\n private async onWriteHandshakeComplete(): Promise {\n // Send the write pipeline now that the stream is established.\n for (const batch of this.writePipeline) {\n this.writeStream.writeMutations(batch.mutations);\n }\n }\n\n private async onMutationResult(\n commitVersion: SnapshotVersion,\n results: MutationResult[]\n ): Promise {\n // This is a response to a write containing mutations and should be\n // correlated to the first write in our write pipeline.\n debugAssert(\n this.writePipeline.length > 0,\n 'Got result for empty write pipeline'\n );\n const batch = this.writePipeline.shift()!;\n const success = MutationBatchResult.from(batch, commitVersion, results);\n\n await this.executeWithRecovery(() =>\n this.syncEngine.applySuccessfulWrite(success)\n );\n\n // It's possible that with the completion of this mutation another\n // slot has freed up.\n await this.fillWritePipeline();\n }\n\n private async onWriteStreamClose(error?: FirestoreError): Promise {\n if (error === undefined) {\n // Graceful stop (due to stop() or idle timeout). Make sure that's\n // desirable.\n debugAssert(\n !this.shouldStartWriteStream(),\n 'Write stream was stopped gracefully while still needed.'\n );\n }\n\n // If the write stream closed after the write handshake completes, a write\n // operation failed and we fail the pending operation.\n if (error && this.writeStream.handshakeComplete) {\n // This error affects the actual write.\n await this.handleWriteError(error!);\n }\n\n // The write stream might have been started by refilling the write\n // pipeline for failed writes\n if (this.shouldStartWriteStream()) {\n this.startWriteStream();\n }\n }\n\n private async handleWriteError(error: FirestoreError): Promise {\n // Only handle permanent errors here. If it's transient, just let the retry\n // logic kick in.\n if (isPermanentWriteError(error.code)) {\n // This was a permanent error, the request itself was the problem\n // so it's not going to succeed if we resend it.\n const batch = this.writePipeline.shift()!;\n\n // In this case it's also unlikely that the server itself is melting\n // down -- this was just a bad request so inhibit backoff on the next\n // restart.\n this.writeStream.inhibitBackoff();\n\n await this.executeWithRecovery(() =>\n this.syncEngine.rejectFailedWrite(batch.batchId, error)\n );\n\n // It's possible that with the completion of this mutation\n // another slot has freed up.\n await this.fillWritePipeline();\n } else {\n // Transient error, just let the retry logic kick in.\n }\n }\n\n createTransaction(): Transaction {\n return new Transaction(this.datastore);\n }\n\n private async restartNetwork(): Promise {\n this.offlineCauses.add(OfflineCause.ConnectivityChange);\n await this.disableNetworkInternal();\n this.onlineStateTracker.set(OnlineState.Unknown);\n this.writeStream.inhibitBackoff();\n this.watchStream.inhibitBackoff();\n this.offlineCauses.delete(OfflineCause.ConnectivityChange);\n await this.enableNetworkInternal();\n }\n\n async handleCredentialChange(user: User): Promise {\n this.asyncQueue.verifyOperationInProgress();\n\n // Tear down and re-create our network streams. This will ensure we get a\n // fresh auth token for the new user and re-fill the write pipeline with\n // new mutations from the LocalStore (since mutations are per-user).\n logDebug(LOG_TAG, 'RemoteStore received new credentials');\n this.offlineCauses.add(OfflineCause.CredentialChange);\n\n await this.disableNetworkInternal();\n this.onlineStateTracker.set(OnlineState.Unknown);\n await this.syncEngine.handleCredentialChange(user);\n\n this.offlineCauses.delete(OfflineCause.CredentialChange);\n await this.enableNetworkInternal();\n }\n\n /**\n * Toggles the network state when the client gains or loses its primary lease.\n */\n async applyPrimaryState(isPrimary: boolean): Promise {\n if (isPrimary) {\n this.offlineCauses.delete(OfflineCause.IsSecondary);\n await this.enableNetworkInternal();\n } else if (!isPrimary) {\n this.offlineCauses.add(OfflineCause.IsSecondary);\n await this.disableNetworkInternal();\n this.onlineStateTracker.set(OnlineState.Unknown);\n }\n }\n}\n","/**\n * @license\n * Copyright 2018 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { User } from '../auth/user';\nimport { ListenSequence } from '../core/listen_sequence';\nimport {\n BatchId,\n ListenSequenceNumber,\n MutationBatchState,\n OnlineState,\n TargetId\n} from '../core/types';\nimport { TargetIdSet, targetIdSet } from '../model/collections';\nimport { hardAssert, debugAssert } from '../util/assert';\nimport { AsyncQueue } from '../util/async_queue';\nimport { Code, FirestoreError } from '../util/error';\nimport { logError, logDebug } from '../util/log';\nimport { SortedSet } from '../util/sorted_set';\nimport { SortedMap } from '../util/sorted_map';\nimport { primitiveComparator } from '../util/misc';\nimport { isSafeInteger, WindowLike } from '../util/types';\nimport {\n QueryTargetState,\n SharedClientStateSyncer\n} from './shared_client_state_syncer';\nimport {\n CLIENT_STATE_KEY_PREFIX,\n ClientStateSchema,\n createWebStorageClientStateKey,\n createWebStorageMutationBatchKey,\n createWebStorageOnlineStateKey,\n createWebStorageQueryTargetMetadataKey,\n createWebStorageSequenceNumberKey,\n MUTATION_BATCH_KEY_PREFIX,\n MutationMetadataSchema,\n QUERY_TARGET_KEY_PREFIX,\n QueryTargetStateSchema,\n SharedOnlineStateSchema\n} from './shared_client_state_schema';\n\nconst LOG_TAG = 'SharedClientState';\n\n/**\n * A randomly-generated key assigned to each Firestore instance at startup.\n */\nexport type ClientId = string;\n\n/**\n * A `SharedClientState` keeps track of the global state of the mutations\n * and query targets for all active clients with the same persistence key (i.e.\n * project ID and FirebaseApp name). It relays local changes to other clients\n * and updates its local state as new state is observed.\n *\n * `SharedClientState` is primarily used for synchronization in Multi-Tab\n * environments. Each tab is responsible for registering its active query\n * targets and mutations. `SharedClientState` will then notify the listener\n * assigned to `.syncEngine` for updates to mutations and queries that\n * originated in other clients.\n *\n * To receive notifications, `.syncEngine` and `.onlineStateHandler` has to be\n * assigned before calling `start()`.\n */\nexport interface SharedClientState {\n syncEngine: SharedClientStateSyncer | null;\n onlineStateHandler: ((onlineState: OnlineState) => void) | null;\n sequenceNumberHandler:\n | ((sequenceNumber: ListenSequenceNumber) => void)\n | null;\n\n /** Registers the Mutation Batch ID of a newly pending mutation. */\n addPendingMutation(batchId: BatchId): void;\n\n /**\n * Records that a pending mutation has been acknowledged or rejected.\n * Called by the primary client to notify secondary clients of mutation\n * results as they come back from the backend.\n */\n updateMutationState(\n batchId: BatchId,\n state: 'acknowledged' | 'rejected',\n error?: FirestoreError\n ): void;\n\n /**\n * Associates a new Query Target ID with the local Firestore client. Returns\n * the new query state for the query (which can be 'current' if the query is\n * already associated with another tab).\n *\n * If the target id is already associated with local client, the method simply\n * returns its `QueryTargetState`.\n */\n addLocalQueryTarget(targetId: TargetId): QueryTargetState;\n\n /** Removes the Query Target ID association from the local client. */\n removeLocalQueryTarget(targetId: TargetId): void;\n\n /** Checks whether the target is associated with the local client. */\n isLocalQueryTarget(targetId: TargetId): boolean;\n\n /**\n * Processes an update to a query target.\n *\n * Called by the primary client to notify secondary clients of document\n * changes or state transitions that affect the provided query target.\n */\n updateQueryState(\n targetId: TargetId,\n state: QueryTargetState,\n error?: FirestoreError\n ): void;\n\n /**\n * Removes the target's metadata entry.\n *\n * Called by the primary client when all clients stopped listening to a query\n * target.\n */\n clearQueryState(targetId: TargetId): void;\n\n /**\n * Gets the active Query Targets IDs for all active clients.\n *\n * The implementation for this may require O(n) runtime, where 'n' is the size\n * of the result set.\n */\n // Visible for testing\n getAllActiveQueryTargets(): SortedSet;\n\n /**\n * Checks whether the provided target ID is currently being listened to by\n * any of the active clients.\n *\n * The implementation may require O(n*log m) runtime, where 'n' is the number\n * of clients and 'm' the number of targets.\n */\n isActiveQueryTarget(targetId: TargetId): boolean;\n\n /**\n * Starts the SharedClientState, reads existing client data and registers\n * listeners for updates to new and existing clients.\n */\n start(): Promise;\n\n /** Shuts down the `SharedClientState` and its listeners. */\n shutdown(): void;\n\n /**\n * Changes the active user and removes all existing user-specific data. The\n * user change does not call back into SyncEngine (for example, no mutations\n * will be marked as removed).\n */\n handleUserChange(\n user: User,\n removedBatchIds: BatchId[],\n addedBatchIds: BatchId[]\n ): void;\n\n /** Changes the shared online state of all clients. */\n setOnlineState(onlineState: OnlineState): void;\n\n writeSequenceNumber(sequenceNumber: ListenSequenceNumber): void;\n}\n\n/**\n * Holds the state of a mutation batch, including its user ID, batch ID and\n * whether the batch is 'pending', 'acknowledged' or 'rejected'.\n */\n// Visible for testing\nexport class MutationMetadata {\n constructor(\n readonly user: User,\n readonly batchId: BatchId,\n readonly state: MutationBatchState,\n readonly error?: FirestoreError\n ) {\n debugAssert(\n (error !== undefined) === (state === 'rejected'),\n `MutationMetadata must contain an error iff state is 'rejected'`\n );\n }\n\n /**\n * Parses a MutationMetadata from its JSON representation in WebStorage.\n * Logs a warning and returns null if the format of the data is not valid.\n */\n static fromWebStorageEntry(\n user: User,\n batchId: BatchId,\n value: string\n ): MutationMetadata | null {\n const mutationBatch = JSON.parse(value) as MutationMetadataSchema;\n\n let validData =\n typeof mutationBatch === 'object' &&\n ['pending', 'acknowledged', 'rejected'].indexOf(mutationBatch.state) !==\n -1 &&\n (mutationBatch.error === undefined ||\n typeof mutationBatch.error === 'object');\n\n let firestoreError: FirestoreError | undefined = undefined;\n\n if (validData && mutationBatch.error) {\n validData =\n typeof mutationBatch.error.message === 'string' &&\n typeof mutationBatch.error.code === 'string';\n if (validData) {\n firestoreError = new FirestoreError(\n mutationBatch.error.code as Code,\n mutationBatch.error.message\n );\n }\n }\n\n if (validData) {\n return new MutationMetadata(\n user,\n batchId,\n mutationBatch.state,\n firestoreError\n );\n } else {\n logError(\n LOG_TAG,\n `Failed to parse mutation state for ID '${batchId}': ${value}`\n );\n return null;\n }\n }\n\n toWebStorageJSON(): string {\n const batchMetadata: MutationMetadataSchema = {\n state: this.state,\n updateTimeMs: Date.now() // Modify the existing value to trigger update.\n };\n\n if (this.error) {\n batchMetadata.error = {\n code: this.error.code,\n message: this.error.message\n };\n }\n\n return JSON.stringify(batchMetadata);\n }\n}\n\n/**\n * Holds the state of a query target, including its target ID and whether the\n * target is 'not-current', 'current' or 'rejected'.\n */\n// Visible for testing\nexport class QueryTargetMetadata {\n constructor(\n readonly targetId: TargetId,\n readonly state: QueryTargetState,\n readonly error?: FirestoreError\n ) {\n debugAssert(\n (error !== undefined) === (state === 'rejected'),\n `QueryTargetMetadata must contain an error iff state is 'rejected'`\n );\n }\n\n /**\n * Parses a QueryTargetMetadata from its JSON representation in WebStorage.\n * Logs a warning and returns null if the format of the data is not valid.\n */\n static fromWebStorageEntry(\n targetId: TargetId,\n value: string\n ): QueryTargetMetadata | null {\n const targetState = JSON.parse(value) as QueryTargetStateSchema;\n\n let validData =\n typeof targetState === 'object' &&\n ['not-current', 'current', 'rejected'].indexOf(targetState.state) !==\n -1 &&\n (targetState.error === undefined ||\n typeof targetState.error === 'object');\n\n let firestoreError: FirestoreError | undefined = undefined;\n\n if (validData && targetState.error) {\n validData =\n typeof targetState.error.message === 'string' &&\n typeof targetState.error.code === 'string';\n if (validData) {\n firestoreError = new FirestoreError(\n targetState.error.code as Code,\n targetState.error.message\n );\n }\n }\n\n if (validData) {\n return new QueryTargetMetadata(\n targetId,\n targetState.state,\n firestoreError\n );\n } else {\n logError(\n LOG_TAG,\n `Failed to parse target state for ID '${targetId}': ${value}`\n );\n return null;\n }\n }\n\n toWebStorageJSON(): string {\n const targetState: QueryTargetStateSchema = {\n state: this.state,\n updateTimeMs: Date.now() // Modify the existing value to trigger update.\n };\n\n if (this.error) {\n targetState.error = {\n code: this.error.code,\n message: this.error.message\n };\n }\n\n return JSON.stringify(targetState);\n }\n}\n\n/**\n * Metadata state of a single client denoting the query targets it is actively\n * listening to.\n */\n// Visible for testing.\nexport interface ClientState {\n readonly activeTargetIds: TargetIdSet;\n}\n\n/**\n * This class represents the immutable ClientState for a client read from\n * WebStorage, containing the list of active query targets.\n */\nclass RemoteClientState implements ClientState {\n private constructor(\n readonly clientId: ClientId,\n readonly activeTargetIds: TargetIdSet\n ) {}\n\n /**\n * Parses a RemoteClientState from the JSON representation in WebStorage.\n * Logs a warning and returns null if the format of the data is not valid.\n */\n static fromWebStorageEntry(\n clientId: ClientId,\n value: string\n ): RemoteClientState | null {\n const clientState = JSON.parse(value) as ClientStateSchema;\n\n let validData =\n typeof clientState === 'object' &&\n clientState.activeTargetIds instanceof Array;\n\n let activeTargetIdsSet = targetIdSet();\n\n for (let i = 0; validData && i < clientState.activeTargetIds.length; ++i) {\n validData = isSafeInteger(clientState.activeTargetIds[i]);\n activeTargetIdsSet = activeTargetIdsSet.add(\n clientState.activeTargetIds[i]\n );\n }\n\n if (validData) {\n return new RemoteClientState(clientId, activeTargetIdsSet);\n } else {\n logError(\n LOG_TAG,\n `Failed to parse client data for instance '${clientId}': ${value}`\n );\n return null;\n }\n }\n}\n\n/**\n * This class represents the online state for all clients participating in\n * multi-tab. The online state is only written to by the primary client, and\n * used in secondary clients to update their query views.\n */\nexport class SharedOnlineState {\n constructor(readonly clientId: string, readonly onlineState: OnlineState) {}\n\n /**\n * Parses a SharedOnlineState from its JSON representation in WebStorage.\n * Logs a warning and returns null if the format of the data is not valid.\n */\n static fromWebStorageEntry(value: string): SharedOnlineState | null {\n const onlineState = JSON.parse(value) as SharedOnlineStateSchema;\n\n const validData =\n typeof onlineState === 'object' &&\n ['Unknown', 'Online', 'Offline'].indexOf(onlineState.onlineState) !==\n -1 &&\n typeof onlineState.clientId === 'string';\n\n if (validData) {\n return new SharedOnlineState(\n onlineState.clientId,\n onlineState.onlineState as OnlineState\n );\n } else {\n logError(LOG_TAG, `Failed to parse online state: ${value}`);\n return null;\n }\n }\n}\n\n/**\n * Metadata state of the local client. Unlike `RemoteClientState`, this class is\n * mutable and keeps track of all pending mutations, which allows us to\n * update the range of pending mutation batch IDs as new mutations are added or\n * removed.\n *\n * The data in `LocalClientState` is not read from WebStorage and instead\n * updated via its instance methods. The updated state can be serialized via\n * `toWebStorageJSON()`.\n */\n// Visible for testing.\nexport class LocalClientState implements ClientState {\n activeTargetIds = targetIdSet();\n\n addQueryTarget(targetId: TargetId): void {\n this.activeTargetIds = this.activeTargetIds.add(targetId);\n }\n\n removeQueryTarget(targetId: TargetId): void {\n this.activeTargetIds = this.activeTargetIds.delete(targetId);\n }\n\n /**\n * Converts this entry into a JSON-encoded format we can use for WebStorage.\n * Does not encode `clientId` as it is part of the key in WebStorage.\n */\n toWebStorageJSON(): string {\n const data: ClientStateSchema = {\n activeTargetIds: this.activeTargetIds.toArray(),\n updateTimeMs: Date.now() // Modify the existing value to trigger update.\n };\n return JSON.stringify(data);\n }\n}\n\n/**\n * `WebStorageSharedClientState` uses WebStorage (window.localStorage) as the\n * backing store for the SharedClientState. It keeps track of all active\n * clients and supports modifications of the local client's data.\n */\nexport class WebStorageSharedClientState implements SharedClientState {\n syncEngine: SharedClientStateSyncer | null = null;\n onlineStateHandler: ((onlineState: OnlineState) => void) | null = null;\n sequenceNumberHandler:\n | ((sequenceNumber: ListenSequenceNumber) => void)\n | null = null;\n\n private readonly storage: Storage;\n private readonly localClientStorageKey: string;\n private readonly sequenceNumberKey: string;\n private readonly storageListener = this.handleWebStorageEvent.bind(this);\n private readonly onlineStateKey: string;\n private readonly clientStateKeyRe: RegExp;\n private readonly mutationBatchKeyRe: RegExp;\n private readonly queryTargetKeyRe: RegExp;\n private activeClients = new SortedMap(\n primitiveComparator\n );\n private started = false;\n private currentUser: User;\n\n /**\n * Captures WebStorage events that occur before `start()` is called. These\n * events are replayed once `WebStorageSharedClientState` is started.\n */\n private earlyEvents: StorageEvent[] = [];\n\n constructor(\n private readonly window: WindowLike,\n private readonly queue: AsyncQueue,\n private readonly persistenceKey: string,\n private readonly localClientId: ClientId,\n initialUser: User\n ) {\n // Escape the special characters mentioned here:\n // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions\n const escapedPersistenceKey = persistenceKey.replace(\n /[.*+?^${}()|[\\]\\\\]/g,\n '\\\\$&'\n );\n\n this.storage = this.window.localStorage;\n this.currentUser = initialUser;\n this.localClientStorageKey = createWebStorageClientStateKey(\n this.persistenceKey,\n this.localClientId\n );\n this.sequenceNumberKey = createWebStorageSequenceNumberKey(\n this.persistenceKey\n );\n this.activeClients = this.activeClients.insert(\n this.localClientId,\n new LocalClientState()\n );\n\n this.clientStateKeyRe = new RegExp(\n `^${CLIENT_STATE_KEY_PREFIX}_${escapedPersistenceKey}_([^_]*)$`\n );\n this.mutationBatchKeyRe = new RegExp(\n `^${MUTATION_BATCH_KEY_PREFIX}_${escapedPersistenceKey}_(\\\\d+)(?:_(.*))?$`\n );\n this.queryTargetKeyRe = new RegExp(\n `^${QUERY_TARGET_KEY_PREFIX}_${escapedPersistenceKey}_(\\\\d+)$`\n );\n\n this.onlineStateKey = createWebStorageOnlineStateKey(this.persistenceKey);\n\n // Rather than adding the storage observer during start(), we add the\n // storage observer during initialization. This ensures that we collect\n // events before other components populate their initial state (during their\n // respective start() calls). Otherwise, we might for example miss a\n // mutation that is added after LocalStore's start() processed the existing\n // mutations but before we observe WebStorage events.\n this.window.addEventListener('storage', this.storageListener);\n }\n\n /** Returns 'true' if WebStorage is available in the current environment. */\n static isAvailable(window: WindowLike | null): window is WindowLike {\n return !!(window && window.localStorage);\n }\n\n async start(): Promise {\n debugAssert(!this.started, 'WebStorageSharedClientState already started');\n debugAssert(\n this.syncEngine !== null,\n 'syncEngine property must be set before calling start()'\n );\n debugAssert(\n this.onlineStateHandler !== null,\n 'onlineStateHandler property must be set before calling start()'\n );\n\n // Retrieve the list of existing clients to backfill the data in\n // SharedClientState.\n const existingClients = await this.syncEngine!.getActiveClients();\n\n for (const clientId of existingClients) {\n if (clientId === this.localClientId) {\n continue;\n }\n\n const storageItem = this.getItem(\n createWebStorageClientStateKey(this.persistenceKey, clientId)\n );\n if (storageItem) {\n const clientState = RemoteClientState.fromWebStorageEntry(\n clientId,\n storageItem\n );\n if (clientState) {\n this.activeClients = this.activeClients.insert(\n clientState.clientId,\n clientState\n );\n }\n }\n }\n\n this.persistClientState();\n\n // Check if there is an existing online state and call the callback handler\n // if applicable.\n const onlineStateJSON = this.storage.getItem(this.onlineStateKey);\n if (onlineStateJSON) {\n const onlineState = this.fromWebStorageOnlineState(onlineStateJSON);\n if (onlineState) {\n this.handleOnlineStateEvent(onlineState);\n }\n }\n\n for (const event of this.earlyEvents) {\n this.handleWebStorageEvent(event);\n }\n\n this.earlyEvents = [];\n\n // Register a window unload hook to remove the client metadata entry from\n // WebStorage even if `shutdown()` was not called.\n this.window.addEventListener('unload', () => this.shutdown());\n\n this.started = true;\n }\n\n writeSequenceNumber(sequenceNumber: ListenSequenceNumber): void {\n this.setItem(this.sequenceNumberKey, JSON.stringify(sequenceNumber));\n }\n\n getAllActiveQueryTargets(): TargetIdSet {\n return this.extractActiveQueryTargets(this.activeClients);\n }\n\n isActiveQueryTarget(targetId: TargetId): boolean {\n let found = false;\n this.activeClients.forEach((key, value) => {\n if (value.activeTargetIds.has(targetId)) {\n found = true;\n }\n });\n return found;\n }\n\n addPendingMutation(batchId: BatchId): void {\n this.persistMutationState(batchId, 'pending');\n }\n\n updateMutationState(\n batchId: BatchId,\n state: 'acknowledged' | 'rejected',\n error?: FirestoreError\n ): void {\n this.persistMutationState(batchId, state, error);\n\n // Once a final mutation result is observed by other clients, they no longer\n // access the mutation's metadata entry. Since WebStorage replays events\n // in order, it is safe to delete the entry right after updating it.\n this.removeMutationState(batchId);\n }\n\n addLocalQueryTarget(targetId: TargetId): QueryTargetState {\n let queryState: QueryTargetState = 'not-current';\n\n // Lookup an existing query state if the target ID was already registered\n // by another tab\n if (this.isActiveQueryTarget(targetId)) {\n const storageItem = this.storage.getItem(\n createWebStorageQueryTargetMetadataKey(this.persistenceKey, targetId)\n );\n\n if (storageItem) {\n const metadata = QueryTargetMetadata.fromWebStorageEntry(\n targetId,\n storageItem\n );\n if (metadata) {\n queryState = metadata.state;\n }\n }\n }\n\n this.localClientState.addQueryTarget(targetId);\n this.persistClientState();\n\n return queryState;\n }\n\n removeLocalQueryTarget(targetId: TargetId): void {\n this.localClientState.removeQueryTarget(targetId);\n this.persistClientState();\n }\n\n isLocalQueryTarget(targetId: TargetId): boolean {\n return this.localClientState.activeTargetIds.has(targetId);\n }\n\n clearQueryState(targetId: TargetId): void {\n this.removeItem(\n createWebStorageQueryTargetMetadataKey(this.persistenceKey, targetId)\n );\n }\n\n updateQueryState(\n targetId: TargetId,\n state: QueryTargetState,\n error?: FirestoreError\n ): void {\n this.persistQueryTargetState(targetId, state, error);\n }\n\n handleUserChange(\n user: User,\n removedBatchIds: BatchId[],\n addedBatchIds: BatchId[]\n ): void {\n removedBatchIds.forEach(batchId => {\n this.removeMutationState(batchId);\n });\n this.currentUser = user;\n addedBatchIds.forEach(batchId => {\n this.addPendingMutation(batchId);\n });\n }\n\n setOnlineState(onlineState: OnlineState): void {\n this.persistOnlineState(onlineState);\n }\n\n shutdown(): void {\n if (this.started) {\n this.window.removeEventListener('storage', this.storageListener);\n this.removeItem(this.localClientStorageKey);\n this.started = false;\n }\n }\n\n private getItem(key: string): string | null {\n const value = this.storage.getItem(key);\n logDebug(LOG_TAG, 'READ', key, value);\n return value;\n }\n\n private setItem(key: string, value: string): void {\n logDebug(LOG_TAG, 'SET', key, value);\n this.storage.setItem(key, value);\n }\n\n private removeItem(key: string): void {\n logDebug(LOG_TAG, 'REMOVE', key);\n this.storage.removeItem(key);\n }\n\n private handleWebStorageEvent(event: Event): void {\n // Note: The function is typed to take Event to be interface-compatible with\n // `Window.addEventListener`.\n const storageEvent = event as StorageEvent;\n if (storageEvent.storageArea === this.storage) {\n logDebug(LOG_TAG, 'EVENT', storageEvent.key, storageEvent.newValue);\n\n if (storageEvent.key === this.localClientStorageKey) {\n logError(\n 'Received WebStorage notification for local change. Another client might have ' +\n 'garbage-collected our state'\n );\n return;\n }\n\n this.queue.enqueueRetryable(async () => {\n if (!this.started) {\n this.earlyEvents.push(storageEvent);\n return;\n }\n\n if (storageEvent.key === null) {\n return;\n }\n\n if (this.clientStateKeyRe.test(storageEvent.key)) {\n if (storageEvent.newValue != null) {\n const clientState = this.fromWebStorageClientState(\n storageEvent.key,\n storageEvent.newValue\n );\n if (clientState) {\n return this.handleClientStateEvent(\n clientState.clientId,\n clientState\n );\n }\n } else {\n const clientId = this.fromWebStorageClientStateKey(\n storageEvent.key\n )!;\n return this.handleClientStateEvent(clientId, null);\n }\n } else if (this.mutationBatchKeyRe.test(storageEvent.key)) {\n if (storageEvent.newValue !== null) {\n const mutationMetadata = this.fromWebStorageMutationMetadata(\n storageEvent.key,\n storageEvent.newValue\n );\n if (mutationMetadata) {\n return this.handleMutationBatchEvent(mutationMetadata);\n }\n }\n } else if (this.queryTargetKeyRe.test(storageEvent.key)) {\n if (storageEvent.newValue !== null) {\n const queryTargetMetadata = this.fromWebStorageQueryTargetMetadata(\n storageEvent.key,\n storageEvent.newValue\n );\n if (queryTargetMetadata) {\n return this.handleQueryTargetEvent(queryTargetMetadata);\n }\n }\n } else if (storageEvent.key === this.onlineStateKey) {\n if (storageEvent.newValue !== null) {\n const onlineState = this.fromWebStorageOnlineState(\n storageEvent.newValue\n );\n if (onlineState) {\n return this.handleOnlineStateEvent(onlineState);\n }\n }\n } else if (storageEvent.key === this.sequenceNumberKey) {\n debugAssert(\n !!this.sequenceNumberHandler,\n 'Missing sequenceNumberHandler'\n );\n const sequenceNumber = fromWebStorageSequenceNumber(\n storageEvent.newValue\n );\n if (sequenceNumber !== ListenSequence.INVALID) {\n this.sequenceNumberHandler!(sequenceNumber);\n }\n }\n });\n }\n }\n\n private get localClientState(): LocalClientState {\n return this.activeClients.get(this.localClientId) as LocalClientState;\n }\n\n private persistClientState(): void {\n this.setItem(\n this.localClientStorageKey,\n this.localClientState.toWebStorageJSON()\n );\n }\n\n private persistMutationState(\n batchId: BatchId,\n state: MutationBatchState,\n error?: FirestoreError\n ): void {\n const mutationState = new MutationMetadata(\n this.currentUser,\n batchId,\n state,\n error\n );\n const mutationKey = createWebStorageMutationBatchKey(\n this.persistenceKey,\n this.currentUser,\n batchId\n );\n this.setItem(mutationKey, mutationState.toWebStorageJSON());\n }\n\n private removeMutationState(batchId: BatchId): void {\n const mutationKey = createWebStorageMutationBatchKey(\n this.persistenceKey,\n this.currentUser,\n batchId\n );\n this.removeItem(mutationKey);\n }\n\n private persistOnlineState(onlineState: OnlineState): void {\n const entry: SharedOnlineStateSchema = {\n clientId: this.localClientId,\n onlineState\n };\n this.storage.setItem(this.onlineStateKey, JSON.stringify(entry));\n }\n\n private persistQueryTargetState(\n targetId: TargetId,\n state: QueryTargetState,\n error?: FirestoreError\n ): void {\n const targetKey = createWebStorageQueryTargetMetadataKey(\n this.persistenceKey,\n targetId\n );\n const targetMetadata = new QueryTargetMetadata(targetId, state, error);\n this.setItem(targetKey, targetMetadata.toWebStorageJSON());\n }\n\n /**\n * Parses a client state key in WebStorage. Returns null if the key does not\n * match the expected key format.\n */\n private fromWebStorageClientStateKey(key: string): ClientId | null {\n const match = this.clientStateKeyRe.exec(key);\n return match ? match[1] : null;\n }\n\n /**\n * Parses a client state in WebStorage. Returns 'null' if the value could not\n * be parsed.\n */\n private fromWebStorageClientState(\n key: string,\n value: string\n ): RemoteClientState | null {\n const clientId = this.fromWebStorageClientStateKey(key);\n debugAssert(clientId !== null, `Cannot parse client state key '${key}'`);\n return RemoteClientState.fromWebStorageEntry(clientId, value);\n }\n\n /**\n * Parses a mutation batch state in WebStorage. Returns 'null' if the value\n * could not be parsed.\n */\n private fromWebStorageMutationMetadata(\n key: string,\n value: string\n ): MutationMetadata | null {\n const match = this.mutationBatchKeyRe.exec(key);\n debugAssert(match !== null, `Cannot parse mutation batch key '${key}'`);\n\n const batchId = Number(match[1]);\n const userId = match[2] !== undefined ? match[2] : null;\n return MutationMetadata.fromWebStorageEntry(\n new User(userId),\n batchId,\n value\n );\n }\n\n /**\n * Parses a query target state from WebStorage. Returns 'null' if the value\n * could not be parsed.\n */\n private fromWebStorageQueryTargetMetadata(\n key: string,\n value: string\n ): QueryTargetMetadata | null {\n const match = this.queryTargetKeyRe.exec(key);\n debugAssert(match !== null, `Cannot parse query target key '${key}'`);\n\n const targetId = Number(match[1]);\n return QueryTargetMetadata.fromWebStorageEntry(targetId, value);\n }\n\n /**\n * Parses an online state from WebStorage. Returns 'null' if the value\n * could not be parsed.\n */\n private fromWebStorageOnlineState(value: string): SharedOnlineState | null {\n return SharedOnlineState.fromWebStorageEntry(value);\n }\n\n private async handleMutationBatchEvent(\n mutationBatch: MutationMetadata\n ): Promise {\n if (mutationBatch.user.uid !== this.currentUser.uid) {\n logDebug(\n LOG_TAG,\n `Ignoring mutation for non-active user ${mutationBatch.user.uid}`\n );\n return;\n }\n\n return this.syncEngine!.applyBatchState(\n mutationBatch.batchId,\n mutationBatch.state,\n mutationBatch.error\n );\n }\n\n private handleQueryTargetEvent(\n targetMetadata: QueryTargetMetadata\n ): Promise {\n return this.syncEngine!.applyTargetState(\n targetMetadata.targetId,\n targetMetadata.state,\n targetMetadata.error\n );\n }\n\n private handleClientStateEvent(\n clientId: ClientId,\n clientState: RemoteClientState | null\n ): Promise {\n const updatedClients = clientState\n ? this.activeClients.insert(clientId, clientState)\n : this.activeClients.remove(clientId);\n\n const existingTargets = this.extractActiveQueryTargets(this.activeClients);\n const newTargets = this.extractActiveQueryTargets(updatedClients);\n\n const addedTargets: TargetId[] = [];\n const removedTargets: TargetId[] = [];\n\n newTargets.forEach(targetId => {\n if (!existingTargets.has(targetId)) {\n addedTargets.push(targetId);\n }\n });\n\n existingTargets.forEach(targetId => {\n if (!newTargets.has(targetId)) {\n removedTargets.push(targetId);\n }\n });\n\n return this.syncEngine!.applyActiveTargetsChange(\n addedTargets,\n removedTargets\n ).then(() => {\n this.activeClients = updatedClients;\n });\n }\n\n private handleOnlineStateEvent(onlineState: SharedOnlineState): void {\n // We check whether the client that wrote this online state is still active\n // by comparing its client ID to the list of clients kept active in\n // IndexedDb. If a client does not update their IndexedDb client state\n // within 5 seconds, it is considered inactive and we don't emit an online\n // state event.\n if (this.activeClients.get(onlineState.clientId)) {\n this.onlineStateHandler!(onlineState.onlineState);\n }\n }\n\n private extractActiveQueryTargets(\n clients: SortedMap\n ): SortedSet {\n let activeTargets = targetIdSet();\n clients.forEach((kev, value) => {\n activeTargets = activeTargets.unionWith(value.activeTargetIds);\n });\n return activeTargets;\n }\n}\n\nfunction fromWebStorageSequenceNumber(\n seqString: string | null\n): ListenSequenceNumber {\n let sequenceNumber = ListenSequence.INVALID;\n if (seqString != null) {\n try {\n const parsed = JSON.parse(seqString);\n hardAssert(\n typeof parsed === 'number',\n 'Found non-numeric sequence number'\n );\n sequenceNumber = parsed;\n } catch (e) {\n logError(LOG_TAG, 'Failed to read sequence number from WebStorage', e);\n }\n }\n return sequenceNumber;\n}\n\n/**\n * `MemorySharedClientState` is a simple implementation of SharedClientState for\n * clients using memory persistence. The state in this class remains fully\n * isolated and no synchronization is performed.\n */\nexport class MemorySharedClientState implements SharedClientState {\n private localState = new LocalClientState();\n private queryState: { [targetId: number]: QueryTargetState } = {};\n\n syncEngine: SharedClientStateSyncer | null = null;\n onlineStateHandler: ((onlineState: OnlineState) => void) | null = null;\n sequenceNumberHandler:\n | ((sequenceNumber: ListenSequenceNumber) => void)\n | null = null;\n\n addPendingMutation(batchId: BatchId): void {\n // No op.\n }\n\n updateMutationState(\n batchId: BatchId,\n state: 'acknowledged' | 'rejected',\n error?: FirestoreError\n ): void {\n // No op.\n }\n\n addLocalQueryTarget(targetId: TargetId): QueryTargetState {\n this.localState.addQueryTarget(targetId);\n return this.queryState[targetId] || 'not-current';\n }\n\n updateQueryState(\n targetId: TargetId,\n state: QueryTargetState,\n error?: FirestoreError\n ): void {\n this.queryState[targetId] = state;\n }\n\n removeLocalQueryTarget(targetId: TargetId): void {\n this.localState.removeQueryTarget(targetId);\n }\n\n isLocalQueryTarget(targetId: TargetId): boolean {\n return this.localState.activeTargetIds.has(targetId);\n }\n\n clearQueryState(targetId: TargetId): void {\n delete this.queryState[targetId];\n }\n\n getAllActiveQueryTargets(): TargetIdSet {\n return this.localState.activeTargetIds;\n }\n\n isActiveQueryTarget(targetId: TargetId): boolean {\n return this.localState.activeTargetIds.has(targetId);\n }\n\n start(): Promise {\n this.localState = new LocalClientState();\n return Promise.resolve();\n }\n\n handleUserChange(\n user: User,\n removedBatchIds: BatchId[],\n addedBatchIds: BatchId[]\n ): void {\n // No op.\n }\n\n setOnlineState(onlineState: OnlineState): void {\n // No op.\n }\n\n shutdown(): void {}\n\n writeSequenceNumber(sequenceNumber: ListenSequenceNumber): void {}\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { QueryResult } from '../local/local_store';\nimport {\n documentKeySet,\n DocumentKeySet,\n MaybeDocumentMap\n} from '../model/collections';\nimport { Document, MaybeDocument } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport { DocumentSet } from '../model/document_set';\nimport { TargetChange } from '../remote/remote_event';\nimport { debugAssert, fail } from '../util/assert';\n\nimport { newQueryComparator, Query, queryMatches } from './query';\nimport { OnlineState } from './types';\nimport {\n ChangeType,\n DocumentChangeSet,\n SyncState,\n ViewSnapshot\n} from './view_snapshot';\n\nexport type LimboDocumentChange = AddedLimboDocument | RemovedLimboDocument;\nexport class AddedLimboDocument {\n constructor(public key: DocumentKey) {}\n}\nexport class RemovedLimboDocument {\n constructor(public key: DocumentKey) {}\n}\n\n/** The result of applying a set of doc changes to a view. */\nexport interface ViewDocumentChanges {\n /** The new set of docs that should be in the view. */\n documentSet: DocumentSet;\n /** The diff of these docs with the previous set of docs. */\n changeSet: DocumentChangeSet;\n /**\n * Whether the set of documents passed in was not sufficient to calculate the\n * new state of the view and there needs to be another pass based on the\n * local cache.\n */\n needsRefill: boolean;\n\n mutatedKeys: DocumentKeySet;\n}\n\nexport interface ViewChange {\n snapshot?: ViewSnapshot;\n limboChanges: LimboDocumentChange[];\n}\n\n/**\n * View is responsible for computing the final merged truth of what docs are in\n * a query. It gets notified of local and remote changes to docs, and applies\n * the query filters and limits to determine the most correct possible results.\n */\nexport class View {\n private syncState: SyncState | null = null;\n /**\n * A flag whether the view is current with the backend. A view is considered\n * current after it has seen the current flag from the backend and did not\n * lose consistency within the watch stream (e.g. because of an existence\n * filter mismatch).\n */\n private current = false;\n private documentSet: DocumentSet;\n /** Documents in the view but not in the remote target */\n private limboDocuments = documentKeySet();\n /** Document Keys that have local changes */\n private mutatedKeys = documentKeySet();\n /** Query comparator that defines the document order in this view. */\n private docComparator: (d1: Document, d2: Document) => number;\n\n constructor(\n private query: Query,\n /** Documents included in the remote target */\n private _syncedDocuments: DocumentKeySet\n ) {\n this.docComparator = newQueryComparator(query);\n this.documentSet = new DocumentSet(this.docComparator);\n }\n\n /**\n * The set of remote documents that the server has told us belongs to the target associated with\n * this view.\n */\n get syncedDocuments(): DocumentKeySet {\n return this._syncedDocuments;\n }\n\n /**\n * Iterates over a set of doc changes, applies the query limit, and computes\n * what the new results should be, what the changes were, and whether we may\n * need to go back to the local cache for more results. Does not make any\n * changes to the view.\n * @param docChanges The doc changes to apply to this view.\n * @param previousChanges If this is being called with a refill, then start\n * with this set of docs and changes instead of the current view.\n * @return a new set of docs, changes, and refill flag.\n */\n computeDocChanges(\n docChanges: MaybeDocumentMap,\n previousChanges?: ViewDocumentChanges\n ): ViewDocumentChanges {\n const changeSet = previousChanges\n ? previousChanges.changeSet\n : new DocumentChangeSet();\n const oldDocumentSet = previousChanges\n ? previousChanges.documentSet\n : this.documentSet;\n let newMutatedKeys = previousChanges\n ? previousChanges.mutatedKeys\n : this.mutatedKeys;\n let newDocumentSet = oldDocumentSet;\n let needsRefill = false;\n\n // Track the last doc in a (full) limit. This is necessary, because some\n // update (a delete, or an update moving a doc past the old limit) might\n // mean there is some other document in the local cache that either should\n // come (1) between the old last limit doc and the new last document, in the\n // case of updates, or (2) after the new last document, in the case of\n // deletes. So we keep this doc at the old limit to compare the updates to.\n //\n // Note that this should never get used in a refill (when previousChanges is\n // set), because there will only be adds -- no deletes or updates.\n const lastDocInLimit =\n this.query.hasLimitToFirst() && oldDocumentSet.size === this.query.limit\n ? oldDocumentSet.last()\n : null;\n const firstDocInLimit =\n this.query.hasLimitToLast() && oldDocumentSet.size === this.query.limit\n ? oldDocumentSet.first()\n : null;\n\n docChanges.inorderTraversal(\n (key: DocumentKey, newMaybeDoc: MaybeDocument) => {\n const oldDoc = oldDocumentSet.get(key);\n let newDoc = newMaybeDoc instanceof Document ? newMaybeDoc : null;\n if (newDoc) {\n debugAssert(\n key.isEqual(newDoc.key),\n 'Mismatching keys found in document changes: ' +\n key +\n ' != ' +\n newDoc.key\n );\n newDoc = queryMatches(this.query, newDoc) ? newDoc : null;\n }\n\n const oldDocHadPendingMutations = oldDoc\n ? this.mutatedKeys.has(oldDoc.key)\n : false;\n const newDocHasPendingMutations = newDoc\n ? newDoc.hasLocalMutations ||\n // We only consider committed mutations for documents that were\n // mutated during the lifetime of the view.\n (this.mutatedKeys.has(newDoc.key) && newDoc.hasCommittedMutations)\n : false;\n\n let changeApplied = false;\n\n // Calculate change\n if (oldDoc && newDoc) {\n const docsEqual = oldDoc.data().isEqual(newDoc.data());\n if (!docsEqual) {\n if (!this.shouldWaitForSyncedDocument(oldDoc, newDoc)) {\n changeSet.track({\n type: ChangeType.Modified,\n doc: newDoc\n });\n changeApplied = true;\n\n if (\n (lastDocInLimit &&\n this.docComparator(newDoc, lastDocInLimit) > 0) ||\n (firstDocInLimit &&\n this.docComparator(newDoc, firstDocInLimit) < 0)\n ) {\n // This doc moved from inside the limit to outside the limit.\n // That means there may be some other doc in the local cache\n // that should be included instead.\n needsRefill = true;\n }\n }\n } else if (oldDocHadPendingMutations !== newDocHasPendingMutations) {\n changeSet.track({ type: ChangeType.Metadata, doc: newDoc });\n changeApplied = true;\n }\n } else if (!oldDoc && newDoc) {\n changeSet.track({ type: ChangeType.Added, doc: newDoc });\n changeApplied = true;\n } else if (oldDoc && !newDoc) {\n changeSet.track({ type: ChangeType.Removed, doc: oldDoc });\n changeApplied = true;\n\n if (lastDocInLimit || firstDocInLimit) {\n // A doc was removed from a full limit query. We'll need to\n // requery from the local cache to see if we know about some other\n // doc that should be in the results.\n needsRefill = true;\n }\n }\n\n if (changeApplied) {\n if (newDoc) {\n newDocumentSet = newDocumentSet.add(newDoc);\n if (newDocHasPendingMutations) {\n newMutatedKeys = newMutatedKeys.add(key);\n } else {\n newMutatedKeys = newMutatedKeys.delete(key);\n }\n } else {\n newDocumentSet = newDocumentSet.delete(key);\n newMutatedKeys = newMutatedKeys.delete(key);\n }\n }\n }\n );\n\n // Drop documents out to meet limit/limitToLast requirement.\n if (this.query.hasLimitToFirst() || this.query.hasLimitToLast()) {\n while (newDocumentSet.size > this.query.limit!) {\n const oldDoc = this.query.hasLimitToFirst()\n ? newDocumentSet.last()\n : newDocumentSet.first();\n newDocumentSet = newDocumentSet.delete(oldDoc!.key);\n newMutatedKeys = newMutatedKeys.delete(oldDoc!.key);\n changeSet.track({ type: ChangeType.Removed, doc: oldDoc! });\n }\n }\n\n debugAssert(\n !needsRefill || !previousChanges,\n 'View was refilled using docs that themselves needed refilling.'\n );\n return {\n documentSet: newDocumentSet,\n changeSet,\n needsRefill,\n mutatedKeys: newMutatedKeys\n };\n }\n\n private shouldWaitForSyncedDocument(\n oldDoc: Document,\n newDoc: Document\n ): boolean {\n // We suppress the initial change event for documents that were modified as\n // part of a write acknowledgment (e.g. when the value of a server transform\n // is applied) as Watch will send us the same document again.\n // By suppressing the event, we only raise two user visible events (one with\n // `hasPendingWrites` and the final state of the document) instead of three\n // (one with `hasPendingWrites`, the modified document with\n // `hasPendingWrites` and the final state of the document).\n return (\n oldDoc.hasLocalMutations &&\n newDoc.hasCommittedMutations &&\n !newDoc.hasLocalMutations\n );\n }\n\n /**\n * Updates the view with the given ViewDocumentChanges and optionally updates\n * limbo docs and sync state from the provided target change.\n * @param docChanges The set of changes to make to the view's docs.\n * @param updateLimboDocuments Whether to update limbo documents based on this\n * change.\n * @param targetChange A target change to apply for computing limbo docs and\n * sync state.\n * @return A new ViewChange with the given docs, changes, and sync state.\n */\n // PORTING NOTE: The iOS/Android clients always compute limbo document changes.\n applyChanges(\n docChanges: ViewDocumentChanges,\n updateLimboDocuments: boolean,\n targetChange?: TargetChange\n ): ViewChange {\n debugAssert(\n !docChanges.needsRefill,\n 'Cannot apply changes that need a refill'\n );\n const oldDocs = this.documentSet;\n this.documentSet = docChanges.documentSet;\n this.mutatedKeys = docChanges.mutatedKeys;\n // Sort changes based on type and query comparator\n const changes = docChanges.changeSet.getChanges();\n changes.sort((c1, c2) => {\n return (\n compareChangeType(c1.type, c2.type) ||\n this.docComparator(c1.doc, c2.doc)\n );\n });\n\n this.applyTargetChange(targetChange);\n const limboChanges = updateLimboDocuments\n ? this.updateLimboDocuments()\n : [];\n const synced = this.limboDocuments.size === 0 && this.current;\n const newSyncState = synced ? SyncState.Synced : SyncState.Local;\n const syncStateChanged = newSyncState !== this.syncState;\n this.syncState = newSyncState;\n\n if (changes.length === 0 && !syncStateChanged) {\n // no changes\n return { limboChanges };\n } else {\n const snap: ViewSnapshot = new ViewSnapshot(\n this.query,\n docChanges.documentSet,\n oldDocs,\n changes,\n docChanges.mutatedKeys,\n newSyncState === SyncState.Local,\n syncStateChanged,\n /* excludesMetadataChanges= */ false\n );\n return {\n snapshot: snap,\n limboChanges\n };\n }\n }\n\n /**\n * Applies an OnlineState change to the view, potentially generating a\n * ViewChange if the view's syncState changes as a result.\n */\n applyOnlineStateChange(onlineState: OnlineState): ViewChange {\n if (this.current && onlineState === OnlineState.Offline) {\n // If we're offline, set `current` to false and then call applyChanges()\n // to refresh our syncState and generate a ViewChange as appropriate. We\n // are guaranteed to get a new TargetChange that sets `current` back to\n // true once the client is back online.\n this.current = false;\n return this.applyChanges(\n {\n documentSet: this.documentSet,\n changeSet: new DocumentChangeSet(),\n mutatedKeys: this.mutatedKeys,\n needsRefill: false\n },\n /* updateLimboDocuments= */ false\n );\n } else {\n // No effect, just return a no-op ViewChange.\n return { limboChanges: [] };\n }\n }\n\n /**\n * Returns whether the doc for the given key should be in limbo.\n */\n private shouldBeInLimbo(key: DocumentKey): boolean {\n // If the remote end says it's part of this query, it's not in limbo.\n if (this._syncedDocuments.has(key)) {\n return false;\n }\n // The local store doesn't think it's a result, so it shouldn't be in limbo.\n if (!this.documentSet.has(key)) {\n return false;\n }\n // If there are local changes to the doc, they might explain why the server\n // doesn't know that it's part of the query. So don't put it in limbo.\n // TODO(klimt): Ideally, we would only consider changes that might actually\n // affect this specific query.\n if (this.documentSet.get(key)!.hasLocalMutations) {\n return false;\n }\n // Everything else is in limbo.\n return true;\n }\n\n /**\n * Updates syncedDocuments, current, and limbo docs based on the given change.\n * Returns the list of changes to which docs are in limbo.\n */\n private applyTargetChange(targetChange?: TargetChange): void {\n if (targetChange) {\n targetChange.addedDocuments.forEach(\n key => (this._syncedDocuments = this._syncedDocuments.add(key))\n );\n targetChange.modifiedDocuments.forEach(key => {\n debugAssert(\n this._syncedDocuments.has(key),\n `Modified document ${key} not found in view.`\n );\n });\n targetChange.removedDocuments.forEach(\n key => (this._syncedDocuments = this._syncedDocuments.delete(key))\n );\n this.current = targetChange.current;\n }\n }\n\n private updateLimboDocuments(): LimboDocumentChange[] {\n // We can only determine limbo documents when we're in-sync with the server.\n if (!this.current) {\n return [];\n }\n\n // TODO(klimt): Do this incrementally so that it's not quadratic when\n // updating many documents.\n const oldLimboDocuments = this.limboDocuments;\n this.limboDocuments = documentKeySet();\n this.documentSet.forEach(doc => {\n if (this.shouldBeInLimbo(doc.key)) {\n this.limboDocuments = this.limboDocuments.add(doc.key);\n }\n });\n\n // Diff the new limbo docs with the old limbo docs.\n const changes: LimboDocumentChange[] = [];\n oldLimboDocuments.forEach(key => {\n if (!this.limboDocuments.has(key)) {\n changes.push(new RemovedLimboDocument(key));\n }\n });\n this.limboDocuments.forEach(key => {\n if (!oldLimboDocuments.has(key)) {\n changes.push(new AddedLimboDocument(key));\n }\n });\n return changes;\n }\n\n /**\n * Update the in-memory state of the current view with the state read from\n * persistence.\n *\n * We update the query view whenever a client's primary status changes:\n * - When a client transitions from primary to secondary, it can miss\n * LocalStorage updates and its query views may temporarily not be\n * synchronized with the state on disk.\n * - For secondary to primary transitions, the client needs to update the list\n * of `syncedDocuments` since secondary clients update their query views\n * based purely on synthesized RemoteEvents.\n *\n * @param queryResult.documents - The documents that match the query according\n * to the LocalStore.\n * @param queryResult.remoteKeys - The keys of the documents that match the\n * query according to the backend.\n *\n * @return The ViewChange that resulted from this synchronization.\n */\n // PORTING NOTE: Multi-tab only.\n synchronizeWithPersistedState(queryResult: QueryResult): ViewChange {\n this._syncedDocuments = queryResult.remoteKeys;\n this.limboDocuments = documentKeySet();\n const docChanges = this.computeDocChanges(queryResult.documents);\n return this.applyChanges(docChanges, /*updateLimboDocuments=*/ true);\n }\n\n /**\n * Returns a view snapshot as if this query was just listened to. Contains\n * a document add for every existing document and the `fromCache` and\n * `hasPendingWrites` status of the already established view.\n */\n // PORTING NOTE: Multi-tab only.\n computeInitialSnapshot(): ViewSnapshot {\n return ViewSnapshot.fromInitialDocuments(\n this.query,\n this.documentSet,\n this.mutatedKeys,\n this.syncState === SyncState.Local\n );\n }\n}\n\nfunction compareChangeType(c1: ChangeType, c2: ChangeType): number {\n const order = (change: ChangeType): 0 | 1 | 2 => {\n switch (change) {\n case ChangeType.Added:\n return 1;\n case ChangeType.Modified:\n return 2;\n case ChangeType.Metadata:\n // A metadata change is converted to a modified change at the public\n // api layer. Since we sort by document key and then change type,\n // metadata and modified changes must be sorted equivalently.\n return 2;\n case ChangeType.Removed:\n return 0;\n default:\n return fail('Unknown ChangeType: ' + change);\n }\n };\n\n return order(c1) - order(c2);\n}\n","/**\n * @license\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Deferred } from '../util/promise';\nimport { TimerId, AsyncQueue } from '../util/async_queue';\nimport { ExponentialBackoff } from '../remote/backoff';\nimport { Transaction } from './transaction';\nimport { Datastore } from '../remote/datastore';\nimport { isNullOrUndefined } from '../util/types';\nimport { isPermanentError } from '../remote/rpc_error';\nimport { FirestoreError } from '../util/error';\n\nconst RETRY_COUNT = 5;\n\n/**\n * TransactionRunner encapsulates the logic needed to run and retry transactions\n * with backoff.\n */\nexport class TransactionRunner {\n private retries = RETRY_COUNT;\n private backoff: ExponentialBackoff;\n\n constructor(\n private readonly asyncQueue: AsyncQueue,\n private readonly datastore: Datastore,\n private readonly updateFunction: (transaction: Transaction) => Promise,\n private readonly deferred: Deferred\n ) {\n this.backoff = new ExponentialBackoff(\n this.asyncQueue,\n TimerId.TransactionRetry\n );\n }\n\n /** Runs the transaction and sets the result on deferred. */\n run(): void {\n this.runWithBackOff();\n }\n\n private runWithBackOff(): void {\n this.backoff.backoffAndRun(async () => {\n const transaction = new Transaction(this.datastore);\n const userPromise = this.tryRunUpdateFunction(transaction);\n if (userPromise) {\n userPromise\n .then(result => {\n this.asyncQueue.enqueueAndForget(() => {\n return transaction\n .commit()\n .then(() => {\n this.deferred.resolve(result);\n })\n .catch(commitError => {\n this.handleTransactionError(commitError);\n });\n });\n })\n .catch(userPromiseError => {\n this.handleTransactionError(userPromiseError);\n });\n }\n });\n }\n\n private tryRunUpdateFunction(transaction: Transaction): Promise | null {\n try {\n const userPromise = this.updateFunction(transaction);\n if (\n isNullOrUndefined(userPromise) ||\n !userPromise.catch ||\n !userPromise.then\n ) {\n this.deferred.reject(\n Error('Transaction callback must return a Promise')\n );\n return null;\n }\n return userPromise;\n } catch (error) {\n // Do not retry errors thrown by user provided updateFunction.\n this.deferred.reject(error);\n return null;\n }\n }\n\n private handleTransactionError(error: Error): void {\n if (this.retries > 0 && this.isRetryableTransactionError(error)) {\n this.retries -= 1;\n this.asyncQueue.enqueueAndForget(() => {\n this.runWithBackOff();\n return Promise.resolve();\n });\n } else {\n this.deferred.reject(error);\n }\n }\n\n private isRetryableTransactionError(error: Error): boolean {\n if (error.name === 'FirebaseError') {\n // In transactions, the backend will fail outdated reads with FAILED_PRECONDITION and\n // non-matching document versions with ABORTED. These errors should be retried.\n const code = (error as FirestoreError).code;\n return (\n code === 'aborted' ||\n code === 'failed-precondition' ||\n !isPermanentError(code)\n );\n }\n return false;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { User } from '../auth/user';\nimport {\n ignoreIfPrimaryLeaseLoss,\n LocalStore,\n MultiTabLocalStore\n} from '../local/local_store';\nimport { LocalViewChanges } from '../local/local_view_changes';\nimport { ReferenceSet } from '../local/reference_set';\nimport { TargetData, TargetPurpose } from '../local/target_data';\nimport {\n documentKeySet,\n DocumentKeySet,\n MaybeDocumentMap\n} from '../model/collections';\nimport { MaybeDocument, NoDocument } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport { Mutation } from '../model/mutation';\nimport { BATCHID_UNKNOWN, MutationBatchResult } from '../model/mutation_batch';\nimport { RemoteEvent, TargetChange } from '../remote/remote_event';\nimport { RemoteStore } from '../remote/remote_store';\nimport { RemoteSyncer } from '../remote/remote_syncer';\nimport { debugAssert, fail, hardAssert } from '../util/assert';\nimport { Code, FirestoreError } from '../util/error';\nimport { logDebug } from '../util/log';\nimport { primitiveComparator } from '../util/misc';\nimport { ObjectMap } from '../util/obj_map';\nimport { Deferred } from '../util/promise';\nimport { SortedMap } from '../util/sorted_map';\n\nimport { ClientId, SharedClientState } from '../local/shared_client_state';\nimport {\n QueryTargetState,\n SharedClientStateSyncer\n} from '../local/shared_client_state_syncer';\nimport { SortedSet } from '../util/sorted_set';\nimport { ListenSequence } from './listen_sequence';\nimport {\n canonifyQuery,\n LimitType,\n Query,\n queryEquals,\n stringifyQuery\n} from './query';\nimport { SnapshotVersion } from './snapshot_version';\nimport { Target } from './target';\nimport { TargetIdGenerator } from './target_id_generator';\nimport { Transaction } from './transaction';\nimport {\n BatchId,\n MutationBatchState,\n OnlineState,\n OnlineStateSource,\n TargetId\n} from './types';\nimport {\n AddedLimboDocument,\n LimboDocumentChange,\n RemovedLimboDocument,\n View,\n ViewChange,\n ViewDocumentChanges\n} from './view';\nimport { ViewSnapshot } from './view_snapshot';\nimport { AsyncQueue, wrapInUserErrorIfRecoverable } from '../util/async_queue';\nimport { TransactionRunner } from './transaction_runner';\nimport { Datastore } from '../remote/datastore';\n\nconst LOG_TAG = 'SyncEngine';\n\n/**\n * QueryView contains all of the data that SyncEngine needs to keep track of for\n * a particular query.\n */\nclass QueryView {\n constructor(\n /**\n * The query itself.\n */\n public query: Query,\n /**\n * The target number created by the client that is used in the watch\n * stream to identify this query.\n */\n public targetId: TargetId,\n /**\n * The view is responsible for computing the final merged truth of what\n * docs are in the query. It gets notified of local and remote changes,\n * and applies the query filters and limits to determine the most correct\n * possible results.\n */\n public view: View\n ) {}\n}\n\n/** Tracks a limbo resolution. */\nclass LimboResolution {\n constructor(public key: DocumentKey) {}\n\n /**\n * Set to true once we've received a document. This is used in\n * getRemoteKeysForTarget() and ultimately used by WatchChangeAggregator to\n * decide whether it needs to manufacture a delete event for the target once\n * the target is CURRENT.\n */\n receivedDocument: boolean = false;\n}\n\n/**\n * Interface implemented by EventManager to handle notifications from\n * SyncEngine.\n */\nexport interface SyncEngineListener {\n /** Handles new view snapshots. */\n onWatchChange(snapshots: ViewSnapshot[]): void;\n\n /** Handles the failure of a query. */\n onWatchError(query: Query, error: Error): void;\n\n /** Handles a change in online state. */\n onOnlineStateChange(onlineState: OnlineState): void;\n}\n\n/**\n * SyncEngine is the central controller in the client SDK architecture. It is\n * the glue code between the EventManager, LocalStore, and RemoteStore. Some of\n * SyncEngine's responsibilities include:\n * 1. Coordinating client requests and remote events between the EventManager\n * and the local and remote data stores.\n * 2. Managing a View object for each query, providing the unified view between\n * the local and remote data stores.\n * 3. Notifying the RemoteStore when the LocalStore has new mutations in its\n * queue that need sending to the backend.\n *\n * The SyncEngine’s methods should only ever be called by methods running in the\n * global async queue.\n */\nexport interface SyncEngine extends RemoteSyncer {\n isPrimaryClient: boolean;\n\n /** Subscribes to SyncEngine notifications. Has to be called exactly once. */\n subscribe(syncEngineListener: SyncEngineListener): void;\n\n /**\n * Initiates the new listen, resolves promise when listen enqueued to the\n * server. All the subsequent view snapshots or errors are sent to the\n * subscribed handlers. Returns the initial snapshot.\n */\n listen(query: Query): Promise;\n\n /** Stops listening to the query. */\n unlisten(query: Query): Promise;\n\n /**\n * Initiates the write of local mutation batch which involves adding the\n * writes to the mutation queue, notifying the remote store about new\n * mutations and raising events for any changes this write caused.\n *\n * The promise returned by this call is resolved when the above steps\n * have completed, *not* when the write was acked by the backend. The\n * userCallback is resolved once the write was acked/rejected by the\n * backend (or failed locally for any other reason).\n */\n write(batch: Mutation[], userCallback: Deferred): Promise;\n\n /**\n * Takes an updateFunction in which a set of reads and writes can be performed\n * atomically. In the updateFunction, the client can read and write values\n * using the supplied transaction object. After the updateFunction, all\n * changes will be committed. If a retryable error occurs (ex: some other\n * client has changed any of the data referenced), then the updateFunction\n * will be called again after a backoff. If the updateFunction still fails\n * after all retries, then the transaction will be rejected.\n *\n * The transaction object passed to the updateFunction contains methods for\n * accessing documents and collections. Unlike other datastore access, data\n * accessed with the transaction will not reflect local changes that have not\n * been committed. For this reason, it is required that all reads are\n * performed before any writes. Transactions must be performed while online.\n *\n * The Deferred input is resolved when the transaction is fully committed.\n */\n runTransaction(\n asyncQueue: AsyncQueue,\n updateFunction: (transaction: Transaction) => Promise,\n deferred: Deferred\n ): void;\n\n /**\n * Applies an OnlineState change to the sync engine and notifies any views of\n * the change.\n */\n applyOnlineStateChange(\n onlineState: OnlineState,\n source: OnlineStateSource\n ): void;\n\n /**\n * Registers a user callback that resolves when all pending mutations at the moment of calling\n * are acknowledged .\n */\n registerPendingWritesCallback(callback: Deferred): Promise;\n\n // Visible for testing\n activeLimboDocumentResolutions(): SortedMap;\n\n // Visible for testing\n enqueuedLimboDocumentResolutions(): DocumentKey[];\n\n handleCredentialChange(user: User): Promise;\n\n enableNetwork(): Promise;\n\n disableNetwork(): Promise;\n\n getRemoteKeysForTarget(targetId: TargetId): DocumentKeySet;\n}\n\n/**\n * An implementation of `SyncEngine` coordinating with other parts of SDK.\n *\n * Note: some field defined in this class might have public access level, but\n * the class is not exported so they are only accessible from this module.\n * This is useful to implement optional features (like bundles) in free\n * functions, such that they are tree-shakeable.\n */\nclass SyncEngineImpl implements SyncEngine {\n protected syncEngineListener: SyncEngineListener | null = null;\n\n protected queryViewsByQuery = new ObjectMap(\n q => canonifyQuery(q),\n queryEquals\n );\n protected queriesByTarget = new Map();\n /**\n * The keys of documents that are in limbo for which we haven't yet started a\n * limbo resolution query.\n */\n private enqueuedLimboResolutions: DocumentKey[] = [];\n /**\n * Keeps track of the target ID for each document that is in limbo with an\n * active target.\n */\n protected activeLimboTargetsByKey = new SortedMap(\n DocumentKey.comparator\n );\n /**\n * Keeps track of the information about an active limbo resolution for each\n * active target ID that was started for the purpose of limbo resolution.\n */\n protected activeLimboResolutionsByTarget = new Map<\n TargetId,\n LimboResolution\n >();\n protected limboDocumentRefs = new ReferenceSet();\n /** Stores user completion handlers, indexed by User and BatchId. */\n private mutationUserCallbacks = {} as {\n [uidKey: string]: SortedMap>;\n };\n /** Stores user callbacks waiting for all pending writes to be acknowledged. */\n private pendingWritesCallbacks = new Map>>();\n private limboTargetIdGenerator = TargetIdGenerator.forSyncEngine();\n\n private onlineState = OnlineState.Unknown;\n\n constructor(\n protected localStore: LocalStore,\n protected remoteStore: RemoteStore,\n protected datastore: Datastore,\n // PORTING NOTE: Manages state synchronization in multi-tab environments.\n protected sharedClientState: SharedClientState,\n private currentUser: User,\n private maxConcurrentLimboResolutions: number\n ) {}\n\n get isPrimaryClient(): boolean {\n return true;\n }\n\n subscribe(syncEngineListener: SyncEngineListener): void {\n debugAssert(\n syncEngineListener !== null,\n 'SyncEngine listener cannot be null'\n );\n debugAssert(\n this.syncEngineListener === null,\n 'SyncEngine already has a subscriber.'\n );\n\n this.syncEngineListener = syncEngineListener;\n }\n\n async listen(query: Query): Promise {\n this.assertSubscribed('listen()');\n\n let targetId;\n let viewSnapshot;\n\n const queryView = this.queryViewsByQuery.get(query);\n if (queryView) {\n // PORTING NOTE: With Multi-Tab Web, it is possible that a query view\n // already exists when EventManager calls us for the first time. This\n // happens when the primary tab is already listening to this query on\n // behalf of another tab and the user of the primary also starts listening\n // to the query. EventManager will not have an assigned target ID in this\n // case and calls `listen` to obtain this ID.\n targetId = queryView.targetId;\n this.sharedClientState.addLocalQueryTarget(targetId);\n viewSnapshot = queryView.view.computeInitialSnapshot();\n } else {\n const targetData = await this.localStore.allocateTarget(query.toTarget());\n\n const status = this.sharedClientState.addLocalQueryTarget(\n targetData.targetId\n );\n targetId = targetData.targetId;\n viewSnapshot = await this.initializeViewAndComputeSnapshot(\n query,\n targetId,\n status === 'current'\n );\n if (this.isPrimaryClient) {\n this.remoteStore.listen(targetData);\n }\n }\n\n return viewSnapshot;\n }\n\n /**\n * Registers a view for a previously unknown query and computes its initial\n * snapshot.\n */\n protected async initializeViewAndComputeSnapshot(\n query: Query,\n targetId: TargetId,\n current: boolean\n ): Promise {\n const queryResult = await this.localStore.executeQuery(\n query,\n /* usePreviousResults= */ true\n );\n const view = new View(query, queryResult.remoteKeys);\n const viewDocChanges = view.computeDocChanges(queryResult.documents);\n const synthesizedTargetChange = TargetChange.createSynthesizedTargetChangeForCurrentChange(\n targetId,\n current && this.onlineState !== OnlineState.Offline\n );\n const viewChange = view.applyChanges(\n viewDocChanges,\n /* updateLimboDocuments= */ this.isPrimaryClient,\n synthesizedTargetChange\n );\n this.updateTrackedLimbos(targetId, viewChange.limboChanges);\n\n debugAssert(\n !!viewChange.snapshot,\n 'applyChanges for new view should always return a snapshot'\n );\n\n const data = new QueryView(query, targetId, view);\n this.queryViewsByQuery.set(query, data);\n if (this.queriesByTarget.has(targetId)) {\n this.queriesByTarget.get(targetId)!.push(query);\n } else {\n this.queriesByTarget.set(targetId, [query]);\n }\n return viewChange.snapshot!;\n }\n\n async unlisten(query: Query): Promise {\n this.assertSubscribed('unlisten()');\n\n const queryView = this.queryViewsByQuery.get(query)!;\n debugAssert(\n !!queryView,\n 'Trying to unlisten on query not found:' + stringifyQuery(query)\n );\n\n // Only clean up the query view and target if this is the only query mapped\n // to the target.\n const queries = this.queriesByTarget.get(queryView.targetId)!;\n if (queries.length > 1) {\n this.queriesByTarget.set(\n queryView.targetId,\n queries.filter(q => !queryEquals(q, query))\n );\n this.queryViewsByQuery.delete(query);\n return;\n }\n\n // No other queries are mapped to the target, clean up the query and the target.\n if (this.isPrimaryClient) {\n // We need to remove the local query target first to allow us to verify\n // whether any other client is still interested in this target.\n this.sharedClientState.removeLocalQueryTarget(queryView.targetId);\n const targetRemainsActive = this.sharedClientState.isActiveQueryTarget(\n queryView.targetId\n );\n\n if (!targetRemainsActive) {\n await this.localStore\n .releaseTarget(queryView.targetId, /*keepPersistedTargetData=*/ false)\n .then(() => {\n this.sharedClientState.clearQueryState(queryView.targetId);\n this.remoteStore.unlisten(queryView.targetId);\n this.removeAndCleanupTarget(queryView.targetId);\n })\n .catch(ignoreIfPrimaryLeaseLoss);\n }\n } else {\n this.removeAndCleanupTarget(queryView.targetId);\n await this.localStore.releaseTarget(\n queryView.targetId,\n /*keepPersistedTargetData=*/ true\n );\n }\n }\n\n async write(batch: Mutation[], userCallback: Deferred): Promise {\n this.assertSubscribed('write()');\n\n try {\n const result = await this.localStore.localWrite(batch);\n this.sharedClientState.addPendingMutation(result.batchId);\n this.addMutationCallback(result.batchId, userCallback);\n await this.emitNewSnapsAndNotifyLocalStore(result.changes);\n await this.remoteStore.fillWritePipeline();\n } catch (e) {\n // If we can't persist the mutation, we reject the user callback and\n // don't send the mutation. The user can then retry the write.\n const error = wrapInUserErrorIfRecoverable(e, `Failed to persist write`);\n userCallback.reject(error);\n }\n }\n\n runTransaction(\n asyncQueue: AsyncQueue,\n updateFunction: (transaction: Transaction) => Promise,\n deferred: Deferred\n ): void {\n new TransactionRunner(\n asyncQueue,\n this.datastore,\n updateFunction,\n deferred\n ).run();\n }\n\n async applyRemoteEvent(remoteEvent: RemoteEvent): Promise {\n this.assertSubscribed('applyRemoteEvent()');\n try {\n const changes = await this.localStore.applyRemoteEvent(remoteEvent);\n // Update `receivedDocument` as appropriate for any limbo targets.\n remoteEvent.targetChanges.forEach((targetChange, targetId) => {\n const limboResolution = this.activeLimboResolutionsByTarget.get(\n targetId\n );\n if (limboResolution) {\n // Since this is a limbo resolution lookup, it's for a single document\n // and it could be added, modified, or removed, but not a combination.\n hardAssert(\n targetChange.addedDocuments.size +\n targetChange.modifiedDocuments.size +\n targetChange.removedDocuments.size <=\n 1,\n 'Limbo resolution for single document contains multiple changes.'\n );\n if (targetChange.addedDocuments.size > 0) {\n limboResolution.receivedDocument = true;\n } else if (targetChange.modifiedDocuments.size > 0) {\n hardAssert(\n limboResolution.receivedDocument,\n 'Received change for limbo target document without add.'\n );\n } else if (targetChange.removedDocuments.size > 0) {\n hardAssert(\n limboResolution.receivedDocument,\n 'Received remove for limbo target document without add.'\n );\n limboResolution.receivedDocument = false;\n } else {\n // This was probably just a CURRENT targetChange or similar.\n }\n }\n });\n await this.emitNewSnapsAndNotifyLocalStore(changes, remoteEvent);\n } catch (error) {\n await ignoreIfPrimaryLeaseLoss(error);\n }\n }\n\n applyOnlineStateChange(\n onlineState: OnlineState,\n source: OnlineStateSource\n ): void {\n this.assertSubscribed('applyOnlineStateChange()');\n const newViewSnapshots = [] as ViewSnapshot[];\n this.queryViewsByQuery.forEach((query, queryView) => {\n const viewChange = queryView.view.applyOnlineStateChange(onlineState);\n debugAssert(\n viewChange.limboChanges.length === 0,\n 'OnlineState should not affect limbo documents.'\n );\n if (viewChange.snapshot) {\n newViewSnapshots.push(viewChange.snapshot);\n }\n });\n this.syncEngineListener!.onOnlineStateChange(onlineState);\n this.syncEngineListener!.onWatchChange(newViewSnapshots);\n this.onlineState = onlineState;\n }\n\n async rejectListen(targetId: TargetId, err: FirestoreError): Promise {\n this.assertSubscribed('rejectListens()');\n\n // PORTING NOTE: Multi-tab only.\n this.sharedClientState.updateQueryState(targetId, 'rejected', err);\n\n const limboResolution = this.activeLimboResolutionsByTarget.get(targetId);\n const limboKey = limboResolution && limboResolution.key;\n if (limboKey) {\n // TODO(klimt): We really only should do the following on permission\n // denied errors, but we don't have the cause code here.\n\n // It's a limbo doc. Create a synthetic event saying it was deleted.\n // This is kind of a hack. Ideally, we would have a method in the local\n // store to purge a document. However, it would be tricky to keep all of\n // the local store's invariants with another method.\n let documentUpdates = new SortedMap(\n DocumentKey.comparator\n );\n documentUpdates = documentUpdates.insert(\n limboKey,\n new NoDocument(limboKey, SnapshotVersion.min())\n );\n const resolvedLimboDocuments = documentKeySet().add(limboKey);\n const event = new RemoteEvent(\n SnapshotVersion.min(),\n /* targetChanges= */ new Map(),\n /* targetMismatches= */ new SortedSet(primitiveComparator),\n documentUpdates,\n resolvedLimboDocuments\n );\n\n await this.applyRemoteEvent(event);\n\n // Since this query failed, we won't want to manually unlisten to it.\n // We only remove it from bookkeeping after we successfully applied the\n // RemoteEvent. If `applyRemoteEvent()` throws, we want to re-listen to\n // this query when the RemoteStore restarts the Watch stream, which should\n // re-trigger the target failure.\n this.activeLimboTargetsByKey = this.activeLimboTargetsByKey.remove(\n limboKey\n );\n this.activeLimboResolutionsByTarget.delete(targetId);\n this.pumpEnqueuedLimboResolutions();\n } else {\n await this.localStore\n .releaseTarget(targetId, /* keepPersistedTargetData */ false)\n .then(() => this.removeAndCleanupTarget(targetId, err))\n .catch(ignoreIfPrimaryLeaseLoss);\n }\n }\n\n async applySuccessfulWrite(\n mutationBatchResult: MutationBatchResult\n ): Promise {\n this.assertSubscribed('applySuccessfulWrite()');\n\n const batchId = mutationBatchResult.batch.batchId;\n\n try {\n const changes = await this.localStore.acknowledgeBatch(\n mutationBatchResult\n );\n\n // The local store may or may not be able to apply the write result and\n // raise events immediately (depending on whether the watcher is caught\n // up), so we raise user callbacks first so that they consistently happen\n // before listen events.\n this.processUserCallback(batchId, /*error=*/ null);\n this.triggerPendingWritesCallbacks(batchId);\n\n this.sharedClientState.updateMutationState(batchId, 'acknowledged');\n await this.emitNewSnapsAndNotifyLocalStore(changes);\n } catch (error) {\n await ignoreIfPrimaryLeaseLoss(error);\n }\n }\n\n async rejectFailedWrite(\n batchId: BatchId,\n error: FirestoreError\n ): Promise {\n this.assertSubscribed('rejectFailedWrite()');\n\n try {\n const changes = await this.localStore.rejectBatch(batchId);\n\n // The local store may or may not be able to apply the write result and\n // raise events immediately (depending on whether the watcher is caught up),\n // so we raise user callbacks first so that they consistently happen before\n // listen events.\n this.processUserCallback(batchId, error);\n this.triggerPendingWritesCallbacks(batchId);\n\n this.sharedClientState.updateMutationState(batchId, 'rejected', error);\n await this.emitNewSnapsAndNotifyLocalStore(changes);\n } catch (error) {\n await ignoreIfPrimaryLeaseLoss(error);\n }\n }\n\n async registerPendingWritesCallback(callback: Deferred): Promise {\n if (!this.remoteStore.canUseNetwork()) {\n logDebug(\n LOG_TAG,\n 'The network is disabled. The task returned by ' +\n \"'awaitPendingWrites()' will not complete until the network is enabled.\"\n );\n }\n\n try {\n const highestBatchId = await this.localStore.getHighestUnacknowledgedBatchId();\n if (highestBatchId === BATCHID_UNKNOWN) {\n // Trigger the callback right away if there is no pending writes at the moment.\n callback.resolve();\n return;\n }\n\n const callbacks = this.pendingWritesCallbacks.get(highestBatchId) || [];\n callbacks.push(callback);\n this.pendingWritesCallbacks.set(highestBatchId, callbacks);\n } catch (e) {\n const firestoreError = wrapInUserErrorIfRecoverable(\n e,\n 'Initialization of waitForPendingWrites() operation failed'\n );\n callback.reject(firestoreError);\n }\n }\n\n /**\n * Triggers the callbacks that are waiting for this batch id to get acknowledged by server,\n * if there are any.\n */\n private triggerPendingWritesCallbacks(batchId: BatchId): void {\n (this.pendingWritesCallbacks.get(batchId) || []).forEach(callback => {\n callback.resolve();\n });\n\n this.pendingWritesCallbacks.delete(batchId);\n }\n\n /** Reject all outstanding callbacks waiting for pending writes to complete. */\n private rejectOutstandingPendingWritesCallbacks(errorMessage: string): void {\n this.pendingWritesCallbacks.forEach(callbacks => {\n callbacks.forEach(callback => {\n callback.reject(new FirestoreError(Code.CANCELLED, errorMessage));\n });\n });\n\n this.pendingWritesCallbacks.clear();\n }\n\n private addMutationCallback(\n batchId: BatchId,\n callback: Deferred\n ): void {\n let newCallbacks = this.mutationUserCallbacks[this.currentUser.toKey()];\n if (!newCallbacks) {\n newCallbacks = new SortedMap>(\n primitiveComparator\n );\n }\n newCallbacks = newCallbacks.insert(batchId, callback);\n this.mutationUserCallbacks[this.currentUser.toKey()] = newCallbacks;\n }\n\n /**\n * Resolves or rejects the user callback for the given batch and then discards\n * it.\n */\n protected processUserCallback(batchId: BatchId, error: Error | null): void {\n let newCallbacks = this.mutationUserCallbacks[this.currentUser.toKey()];\n\n // NOTE: Mutations restored from persistence won't have callbacks, so it's\n // okay for there to be no callback for this ID.\n if (newCallbacks) {\n const callback = newCallbacks.get(batchId);\n if (callback) {\n debugAssert(\n batchId === newCallbacks.minKey(),\n 'Mutation callbacks processed out-of-order?'\n );\n if (error) {\n callback.reject(error);\n } else {\n callback.resolve();\n }\n newCallbacks = newCallbacks.remove(batchId);\n }\n this.mutationUserCallbacks[this.currentUser.toKey()] = newCallbacks;\n }\n }\n\n protected removeAndCleanupTarget(\n targetId: number,\n error: Error | null = null\n ): void {\n this.sharedClientState.removeLocalQueryTarget(targetId);\n\n debugAssert(\n this.queriesByTarget.has(targetId) &&\n this.queriesByTarget.get(targetId)!.length !== 0,\n `There are no queries mapped to target id ${targetId}`\n );\n\n for (const query of this.queriesByTarget.get(targetId)!) {\n this.queryViewsByQuery.delete(query);\n if (error) {\n this.syncEngineListener!.onWatchError(query, error);\n }\n }\n\n this.queriesByTarget.delete(targetId);\n\n if (this.isPrimaryClient) {\n const limboKeys = this.limboDocumentRefs.removeReferencesForId(targetId);\n limboKeys.forEach(limboKey => {\n const isReferenced = this.limboDocumentRefs.containsKey(limboKey);\n if (!isReferenced) {\n // We removed the last reference for this key\n this.removeLimboTarget(limboKey);\n }\n });\n }\n }\n\n private removeLimboTarget(key: DocumentKey): void {\n // It's possible that the target already got removed because the query failed. In that case,\n // the key won't exist in `limboTargetsByKey`. Only do the cleanup if we still have the target.\n const limboTargetId = this.activeLimboTargetsByKey.get(key);\n if (limboTargetId === null) {\n // This target already got removed, because the query failed.\n return;\n }\n\n this.remoteStore.unlisten(limboTargetId);\n this.activeLimboTargetsByKey = this.activeLimboTargetsByKey.remove(key);\n this.activeLimboResolutionsByTarget.delete(limboTargetId);\n this.pumpEnqueuedLimboResolutions();\n }\n\n protected updateTrackedLimbos(\n targetId: TargetId,\n limboChanges: LimboDocumentChange[]\n ): void {\n for (const limboChange of limboChanges) {\n if (limboChange instanceof AddedLimboDocument) {\n this.limboDocumentRefs.addReference(limboChange.key, targetId);\n this.trackLimboChange(limboChange);\n } else if (limboChange instanceof RemovedLimboDocument) {\n logDebug(LOG_TAG, 'Document no longer in limbo: ' + limboChange.key);\n this.limboDocumentRefs.removeReference(limboChange.key, targetId);\n const isReferenced = this.limboDocumentRefs.containsKey(\n limboChange.key\n );\n if (!isReferenced) {\n // We removed the last reference for this key\n this.removeLimboTarget(limboChange.key);\n }\n } else {\n fail('Unknown limbo change: ' + JSON.stringify(limboChange));\n }\n }\n }\n\n private trackLimboChange(limboChange: AddedLimboDocument): void {\n const key = limboChange.key;\n if (!this.activeLimboTargetsByKey.get(key)) {\n logDebug(LOG_TAG, 'New document in limbo: ' + key);\n this.enqueuedLimboResolutions.push(key);\n this.pumpEnqueuedLimboResolutions();\n }\n }\n\n /**\n * Starts listens for documents in limbo that are enqueued for resolution,\n * subject to a maximum number of concurrent resolutions.\n *\n * Without bounding the number of concurrent resolutions, the server can fail\n * with \"resource exhausted\" errors which can lead to pathological client\n * behavior as seen in https://github.com/firebase/firebase-js-sdk/issues/2683.\n */\n private pumpEnqueuedLimboResolutions(): void {\n while (\n this.enqueuedLimboResolutions.length > 0 &&\n this.activeLimboTargetsByKey.size < this.maxConcurrentLimboResolutions\n ) {\n const key = this.enqueuedLimboResolutions.shift()!;\n const limboTargetId = this.limboTargetIdGenerator.next();\n this.activeLimboResolutionsByTarget.set(\n limboTargetId,\n new LimboResolution(key)\n );\n this.activeLimboTargetsByKey = this.activeLimboTargetsByKey.insert(\n key,\n limboTargetId\n );\n this.remoteStore.listen(\n new TargetData(\n Query.atPath(key.path).toTarget(),\n limboTargetId,\n TargetPurpose.LimboResolution,\n ListenSequence.INVALID\n )\n );\n }\n }\n\n // Visible for testing\n activeLimboDocumentResolutions(): SortedMap {\n return this.activeLimboTargetsByKey;\n }\n\n // Visible for testing\n enqueuedLimboDocumentResolutions(): DocumentKey[] {\n return this.enqueuedLimboResolutions;\n }\n\n protected async emitNewSnapsAndNotifyLocalStore(\n changes: MaybeDocumentMap,\n remoteEvent?: RemoteEvent\n ): Promise {\n const newSnaps: ViewSnapshot[] = [];\n const docChangesInAllViews: LocalViewChanges[] = [];\n const queriesProcessed: Array> = [];\n\n this.queryViewsByQuery.forEach((_, queryView) => {\n queriesProcessed.push(\n Promise.resolve()\n .then(() => {\n const viewDocChanges = queryView.view.computeDocChanges(changes);\n if (!viewDocChanges.needsRefill) {\n return viewDocChanges;\n }\n // The query has a limit and some docs were removed, so we need\n // to re-run the query against the local store to make sure we\n // didn't lose any good docs that had been past the limit.\n return this.localStore\n .executeQuery(queryView.query, /* usePreviousResults= */ false)\n .then(({ documents }) => {\n return queryView.view.computeDocChanges(\n documents,\n viewDocChanges\n );\n });\n })\n .then((viewDocChanges: ViewDocumentChanges) => {\n const targetChange =\n remoteEvent && remoteEvent.targetChanges.get(queryView.targetId);\n const viewChange = queryView.view.applyChanges(\n viewDocChanges,\n /* updateLimboDocuments= */ this.isPrimaryClient,\n targetChange\n );\n this.updateTrackedLimbos(\n queryView.targetId,\n viewChange.limboChanges\n );\n if (viewChange.snapshot) {\n if (this.isPrimaryClient) {\n this.sharedClientState.updateQueryState(\n queryView.targetId,\n viewChange.snapshot.fromCache ? 'not-current' : 'current'\n );\n }\n\n newSnaps.push(viewChange.snapshot);\n const docChanges = LocalViewChanges.fromSnapshot(\n queryView.targetId,\n viewChange.snapshot\n );\n docChangesInAllViews.push(docChanges);\n }\n })\n );\n });\n\n await Promise.all(queriesProcessed);\n this.syncEngineListener!.onWatchChange(newSnaps);\n await this.localStore.notifyLocalViewChanges(docChangesInAllViews);\n }\n\n protected assertSubscribed(fnName: string): void {\n debugAssert(\n this.syncEngineListener !== null,\n 'Trying to call ' + fnName + ' before calling subscribe().'\n );\n }\n\n async handleCredentialChange(user: User): Promise {\n const userChanged = !this.currentUser.isEqual(user);\n\n if (userChanged) {\n logDebug(LOG_TAG, 'User change. New user:', user.toKey());\n\n const result = await this.localStore.handleUserChange(user);\n this.currentUser = user;\n\n // Fails tasks waiting for pending writes requested by previous user.\n this.rejectOutstandingPendingWritesCallbacks(\n \"'waitForPendingWrites' promise is rejected due to a user change.\"\n );\n // TODO(b/114226417): Consider calling this only in the primary tab.\n this.sharedClientState.handleUserChange(\n user,\n result.removedBatchIds,\n result.addedBatchIds\n );\n await this.emitNewSnapsAndNotifyLocalStore(result.affectedDocuments);\n }\n }\n\n enableNetwork(): Promise {\n return this.remoteStore.enableNetwork();\n }\n\n disableNetwork(): Promise {\n return this.remoteStore.disableNetwork();\n }\n\n getRemoteKeysForTarget(targetId: TargetId): DocumentKeySet {\n const limboResolution = this.activeLimboResolutionsByTarget.get(targetId);\n if (limboResolution && limboResolution.receivedDocument) {\n return documentKeySet().add(limboResolution.key);\n } else {\n let keySet = documentKeySet();\n const queries = this.queriesByTarget.get(targetId);\n if (!queries) {\n return keySet;\n }\n for (const query of queries) {\n const queryView = this.queryViewsByQuery.get(query);\n debugAssert(\n !!queryView,\n `No query view found for ${stringifyQuery(query)}`\n );\n keySet = keySet.unionWith(queryView.view.syncedDocuments);\n }\n return keySet;\n }\n }\n}\n\nexport function newSyncEngine(\n localStore: LocalStore,\n remoteStore: RemoteStore,\n datastore: Datastore,\n // PORTING NOTE: Manages state synchronization in multi-tab environments.\n sharedClientState: SharedClientState,\n currentUser: User,\n maxConcurrentLimboResolutions: number\n): SyncEngine {\n return new SyncEngineImpl(\n localStore,\n remoteStore,\n datastore,\n sharedClientState,\n currentUser,\n maxConcurrentLimboResolutions\n );\n}\n\n/**\n * An extension of SyncEngine that also includes SharedClientStateSyncer for\n * Multi-Tab synchronization.\n */\n// PORTING NOTE: Web only\nexport interface MultiTabSyncEngine\n extends SharedClientStateSyncer,\n SyncEngine {\n applyPrimaryState(isPrimary: boolean): Promise;\n}\n\n/**\n * An implementation of `SyncEngineImpl` providing multi-tab synchronization on\n * top of `SyncEngineImpl`.\n *\n * Note: some field defined in this class might have public access level, but\n * the class is not exported so they are only accessible from this module.\n * This is useful to implement optional features (like bundles) in free\n * functions, such that they are tree-shakeable.\n */\nclass MultiTabSyncEngineImpl extends SyncEngineImpl {\n // The primary state is set to `true` or `false` immediately after Firestore\n // startup. In the interim, a client should only be considered primary if\n // `isPrimary` is true.\n private _isPrimaryClient: undefined | boolean = undefined;\n\n constructor(\n protected localStore: MultiTabLocalStore,\n remoteStore: RemoteStore,\n datastore: Datastore,\n sharedClientState: SharedClientState,\n currentUser: User,\n maxConcurrentLimboResolutions: number\n ) {\n super(\n localStore,\n remoteStore,\n datastore,\n sharedClientState,\n currentUser,\n maxConcurrentLimboResolutions\n );\n }\n\n get isPrimaryClient(): boolean {\n return this._isPrimaryClient === true;\n }\n\n enableNetwork(): Promise {\n this.localStore.setNetworkEnabled(true);\n return super.enableNetwork();\n }\n\n disableNetwork(): Promise {\n this.localStore.setNetworkEnabled(false);\n return super.disableNetwork();\n }\n\n /**\n * Reconcile the list of synced documents in an existing view with those\n * from persistence.\n */\n private async synchronizeViewAndComputeSnapshot(\n queryView: QueryView\n ): Promise {\n const queryResult = await this.localStore.executeQuery(\n queryView.query,\n /* usePreviousResults= */ true\n );\n const viewSnapshot = queryView.view.synchronizeWithPersistedState(\n queryResult\n );\n if (this._isPrimaryClient) {\n this.updateTrackedLimbos(queryView.targetId, viewSnapshot.limboChanges);\n }\n return viewSnapshot;\n }\n\n applyOnlineStateChange(\n onlineState: OnlineState,\n source: OnlineStateSource\n ): void {\n // If we are the primary client, the online state of all clients only\n // depends on the online state of the local RemoteStore.\n if (this.isPrimaryClient && source === OnlineStateSource.RemoteStore) {\n super.applyOnlineStateChange(onlineState, source);\n this.sharedClientState.setOnlineState(onlineState);\n }\n\n // If we are the secondary client, we explicitly ignore the remote store's\n // online state (the local client may go offline, even though the primary\n // tab remains online) and only apply the primary tab's online state from\n // SharedClientState.\n if (\n !this.isPrimaryClient &&\n source === OnlineStateSource.SharedClientState\n ) {\n super.applyOnlineStateChange(onlineState, source);\n }\n }\n\n async applyBatchState(\n batchId: BatchId,\n batchState: MutationBatchState,\n error?: FirestoreError\n ): Promise {\n this.assertSubscribed('applyBatchState()');\n const documents = await this.localStore.lookupMutationDocuments(batchId);\n\n if (documents === null) {\n // A throttled tab may not have seen the mutation before it was completed\n // and removed from the mutation queue, in which case we won't have cached\n // the affected documents. In this case we can safely ignore the update\n // since that means we didn't apply the mutation locally at all (if we\n // had, we would have cached the affected documents), and so we will just\n // see any resulting document changes via normal remote document updates\n // as applicable.\n logDebug(LOG_TAG, 'Cannot apply mutation batch with id: ' + batchId);\n return;\n }\n\n if (batchState === 'pending') {\n // If we are the primary client, we need to send this write to the\n // backend. Secondary clients will ignore these writes since their remote\n // connection is disabled.\n await this.remoteStore.fillWritePipeline();\n } else if (batchState === 'acknowledged' || batchState === 'rejected') {\n // NOTE: Both these methods are no-ops for batches that originated from\n // other clients.\n this.processUserCallback(batchId, error ? error : null);\n this.localStore.removeCachedMutationBatchMetadata(batchId);\n } else {\n fail(`Unknown batchState: ${batchState}`);\n }\n\n await this.emitNewSnapsAndNotifyLocalStore(documents);\n }\n\n async applyPrimaryState(isPrimary: boolean): Promise {\n if (isPrimary === true && this._isPrimaryClient !== true) {\n // Secondary tabs only maintain Views for their local listeners and the\n // Views internal state may not be 100% populated (in particular\n // secondary tabs don't track syncedDocuments, the set of documents the\n // server considers to be in the target). So when a secondary becomes\n // primary, we need to need to make sure that all views for all targets\n // match the state on disk.\n const activeTargets = this.sharedClientState.getAllActiveQueryTargets();\n const activeQueries = await this.synchronizeQueryViewsAndRaiseSnapshots(\n activeTargets.toArray(),\n /*transitionToPrimary=*/ true\n );\n this._isPrimaryClient = true;\n await this.remoteStore.applyPrimaryState(true);\n for (const targetData of activeQueries) {\n this.remoteStore.listen(targetData);\n }\n } else if (isPrimary === false && this._isPrimaryClient !== false) {\n const activeTargets: TargetId[] = [];\n\n let p = Promise.resolve();\n this.queriesByTarget.forEach((_, targetId) => {\n if (this.sharedClientState.isLocalQueryTarget(targetId)) {\n activeTargets.push(targetId);\n } else {\n p = p.then(() => {\n this.removeAndCleanupTarget(targetId);\n return this.localStore.releaseTarget(\n targetId,\n /*keepPersistedTargetData=*/ true\n );\n });\n }\n this.remoteStore.unlisten(targetId);\n });\n await p;\n\n await this.synchronizeQueryViewsAndRaiseSnapshots(\n activeTargets,\n /*transitionToPrimary=*/ false\n );\n this.resetLimboDocuments();\n this._isPrimaryClient = false;\n await this.remoteStore.applyPrimaryState(false);\n }\n }\n\n private resetLimboDocuments(): void {\n this.activeLimboResolutionsByTarget.forEach((_, targetId) => {\n this.remoteStore.unlisten(targetId);\n });\n this.limboDocumentRefs.removeAllReferences();\n this.activeLimboResolutionsByTarget = new Map();\n this.activeLimboTargetsByKey = new SortedMap(\n DocumentKey.comparator\n );\n }\n\n /**\n * Reconcile the query views of the provided query targets with the state from\n * persistence. Raises snapshots for any changes that affect the local\n * client and returns the updated state of all target's query data.\n *\n * @param targets the list of targets with views that need to be recomputed\n * @param transitionToPrimary `true` iff the tab transitions from a secondary\n * tab to a primary tab\n */\n private async synchronizeQueryViewsAndRaiseSnapshots(\n targets: TargetId[],\n transitionToPrimary: boolean\n ): Promise {\n const activeQueries: TargetData[] = [];\n const newViewSnapshots: ViewSnapshot[] = [];\n for (const targetId of targets) {\n let targetData: TargetData;\n const queries = this.queriesByTarget.get(targetId);\n\n if (queries && queries.length !== 0) {\n // For queries that have a local View, we fetch their current state\n // from LocalStore (as the resume token and the snapshot version\n // might have changed) and reconcile their views with the persisted\n // state (the list of syncedDocuments may have gotten out of sync).\n targetData = await this.localStore.allocateTarget(\n queries[0].toTarget()\n );\n\n for (const query of queries) {\n const queryView = this.queryViewsByQuery.get(query);\n debugAssert(\n !!queryView,\n `No query view found for ${stringifyQuery(query)}`\n );\n\n const viewChange = await this.synchronizeViewAndComputeSnapshot(\n queryView\n );\n if (viewChange.snapshot) {\n newViewSnapshots.push(viewChange.snapshot);\n }\n }\n } else {\n debugAssert(\n transitionToPrimary,\n 'A secondary tab should never have an active view without an active target.'\n );\n // For queries that never executed on this client, we need to\n // allocate the target in LocalStore and initialize a new View.\n const target = await this.localStore.getTarget(targetId);\n debugAssert(!!target, `Target for id ${targetId} not found`);\n targetData = await this.localStore.allocateTarget(target);\n await this.initializeViewAndComputeSnapshot(\n this.synthesizeTargetToQuery(target!),\n targetId,\n /*current=*/ false\n );\n }\n\n activeQueries.push(targetData!);\n }\n\n this.syncEngineListener!.onWatchChange(newViewSnapshots);\n return activeQueries;\n }\n\n /**\n * Creates a `Query` object from the specified `Target`. There is no way to\n * obtain the original `Query`, so we synthesize a `Query` from the `Target`\n * object.\n *\n * The synthesized result might be different from the original `Query`, but\n * since the synthesized `Query` should return the same results as the\n * original one (only the presentation of results might differ), the potential\n * difference will not cause issues.\n */\n private synthesizeTargetToQuery(target: Target): Query {\n return new Query(\n target.path,\n target.collectionGroup,\n target.orderBy,\n target.filters,\n target.limit,\n LimitType.First,\n target.startAt,\n target.endAt\n );\n }\n\n getActiveClients(): Promise {\n return this.localStore.getActiveClients();\n }\n\n async applyTargetState(\n targetId: TargetId,\n state: QueryTargetState,\n error?: FirestoreError\n ): Promise {\n if (this._isPrimaryClient) {\n // If we receive a target state notification via WebStorage, we are\n // either already secondary or another tab has taken the primary lease.\n logDebug(LOG_TAG, 'Ignoring unexpected query state notification.');\n return;\n }\n\n if (this.queriesByTarget.has(targetId)) {\n switch (state) {\n case 'current':\n case 'not-current': {\n const changes = await this.localStore.getNewDocumentChanges();\n const synthesizedRemoteEvent = RemoteEvent.createSynthesizedRemoteEventForCurrentChange(\n targetId,\n state === 'current'\n );\n await this.emitNewSnapsAndNotifyLocalStore(\n changes,\n synthesizedRemoteEvent\n );\n break;\n }\n case 'rejected': {\n await this.localStore.releaseTarget(\n targetId,\n /* keepPersistedTargetData */ true\n );\n this.removeAndCleanupTarget(targetId, error);\n break;\n }\n default:\n fail('Unexpected target state: ' + state);\n }\n }\n }\n\n async applyActiveTargetsChange(\n added: TargetId[],\n removed: TargetId[]\n ): Promise {\n if (!this._isPrimaryClient) {\n return;\n }\n\n for (const targetId of added) {\n if (this.queriesByTarget.has(targetId)) {\n // A target might have been added in a previous attempt\n logDebug(LOG_TAG, 'Adding an already active target ' + targetId);\n continue;\n }\n\n const target = await this.localStore.getTarget(targetId);\n debugAssert(\n !!target,\n `Query data for active target ${targetId} not found`\n );\n const targetData = await this.localStore.allocateTarget(target);\n await this.initializeViewAndComputeSnapshot(\n this.synthesizeTargetToQuery(target),\n targetData.targetId,\n /*current=*/ false\n );\n this.remoteStore.listen(targetData);\n }\n\n for (const targetId of removed) {\n // Check that the target is still active since the target might have been\n // removed if it has been rejected by the backend.\n if (!this.queriesByTarget.has(targetId)) {\n continue;\n }\n\n // Release queries that are still active.\n await this.localStore\n .releaseTarget(targetId, /* keepPersistedTargetData */ false)\n .then(() => {\n this.remoteStore.unlisten(targetId);\n this.removeAndCleanupTarget(targetId);\n })\n .catch(ignoreIfPrimaryLeaseLoss);\n }\n }\n}\n\nexport function newMultiTabSyncEngine(\n localStore: MultiTabLocalStore,\n remoteStore: RemoteStore,\n datastore: Datastore,\n sharedClientState: SharedClientState,\n currentUser: User,\n maxConcurrentLimboResolutions: number\n): MultiTabSyncEngine {\n return new MultiTabSyncEngineImpl(\n localStore,\n remoteStore,\n datastore,\n sharedClientState,\n currentUser,\n maxConcurrentLimboResolutions\n );\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { debugAssert } from '../util/assert';\nimport { EventHandler } from '../util/misc';\nimport { ObjectMap } from '../util/obj_map';\nimport { canonifyQuery, Query, queryEquals, stringifyQuery } from './query';\nimport { SyncEngine, SyncEngineListener } from './sync_engine';\nimport { OnlineState } from './types';\nimport { ChangeType, DocumentViewChange, ViewSnapshot } from './view_snapshot';\nimport { wrapInUserErrorIfRecoverable } from '../util/async_queue';\n\n/**\n * Holds the listeners and the last received ViewSnapshot for a query being\n * tracked by EventManager.\n */\nclass QueryListenersInfo {\n viewSnap: ViewSnapshot | undefined = undefined;\n listeners: QueryListener[] = [];\n}\n\n/**\n * Interface for handling events from the EventManager.\n */\nexport interface Observer {\n next: EventHandler;\n error: EventHandler;\n}\n\n/**\n * EventManager is responsible for mapping queries to query event emitters.\n * It handles \"fan-out\". -- Identical queries will re-use the same watch on the\n * backend.\n */\nexport class EventManager implements SyncEngineListener {\n private queries = new ObjectMap(\n q => canonifyQuery(q),\n queryEquals\n );\n\n private onlineState = OnlineState.Unknown;\n\n private snapshotsInSyncListeners: Set> = new Set();\n\n constructor(private syncEngine: SyncEngine) {\n this.syncEngine.subscribe(this);\n }\n\n async listen(listener: QueryListener): Promise {\n const query = listener.query;\n let firstListen = false;\n\n let queryInfo = this.queries.get(query);\n if (!queryInfo) {\n firstListen = true;\n queryInfo = new QueryListenersInfo();\n }\n\n if (firstListen) {\n try {\n queryInfo.viewSnap = await this.syncEngine.listen(query);\n } catch (e) {\n const firestoreError = wrapInUserErrorIfRecoverable(\n e,\n `Initialization of query '${stringifyQuery(listener.query)}' failed`\n );\n listener.onError(firestoreError);\n return;\n }\n }\n\n this.queries.set(query, queryInfo);\n queryInfo.listeners.push(listener);\n\n // Run global snapshot listeners if a consistent snapshot has been emitted.\n const raisedEvent = listener.applyOnlineStateChange(this.onlineState);\n debugAssert(\n !raisedEvent,\n \"applyOnlineStateChange() shouldn't raise an event for brand-new listeners.\"\n );\n\n if (queryInfo.viewSnap) {\n const raisedEvent = listener.onViewSnapshot(queryInfo.viewSnap);\n if (raisedEvent) {\n this.raiseSnapshotsInSyncEvent();\n }\n }\n }\n\n async unlisten(listener: QueryListener): Promise {\n const query = listener.query;\n let lastListen = false;\n\n const queryInfo = this.queries.get(query);\n if (queryInfo) {\n const i = queryInfo.listeners.indexOf(listener);\n if (i >= 0) {\n queryInfo.listeners.splice(i, 1);\n lastListen = queryInfo.listeners.length === 0;\n }\n }\n\n if (lastListen) {\n this.queries.delete(query);\n return this.syncEngine.unlisten(query);\n }\n }\n\n onWatchChange(viewSnaps: ViewSnapshot[]): void {\n let raisedEvent = false;\n for (const viewSnap of viewSnaps) {\n const query = viewSnap.query;\n const queryInfo = this.queries.get(query);\n if (queryInfo) {\n for (const listener of queryInfo.listeners) {\n if (listener.onViewSnapshot(viewSnap)) {\n raisedEvent = true;\n }\n }\n queryInfo.viewSnap = viewSnap;\n }\n }\n if (raisedEvent) {\n this.raiseSnapshotsInSyncEvent();\n }\n }\n\n onWatchError(query: Query, error: Error): void {\n const queryInfo = this.queries.get(query);\n if (queryInfo) {\n for (const listener of queryInfo.listeners) {\n listener.onError(error);\n }\n }\n\n // Remove all listeners. NOTE: We don't need to call syncEngine.unlisten()\n // after an error.\n this.queries.delete(query);\n }\n\n onOnlineStateChange(onlineState: OnlineState): void {\n this.onlineState = onlineState;\n let raisedEvent = false;\n this.queries.forEach((_, queryInfo) => {\n for (const listener of queryInfo.listeners) {\n // Run global snapshot listeners if a consistent snapshot has been emitted.\n if (listener.applyOnlineStateChange(onlineState)) {\n raisedEvent = true;\n }\n }\n });\n if (raisedEvent) {\n this.raiseSnapshotsInSyncEvent();\n }\n }\n\n addSnapshotsInSyncListener(observer: Observer): void {\n this.snapshotsInSyncListeners.add(observer);\n // Immediately fire an initial event, indicating all existing listeners\n // are in-sync.\n observer.next();\n }\n\n removeSnapshotsInSyncListener(observer: Observer): void {\n this.snapshotsInSyncListeners.delete(observer);\n }\n\n // Call all global snapshot listeners that have been set.\n private raiseSnapshotsInSyncEvent(): void {\n this.snapshotsInSyncListeners.forEach(observer => {\n observer.next();\n });\n }\n}\n\nexport interface ListenOptions {\n /** Raise events even when only the metadata changes */\n readonly includeMetadataChanges?: boolean;\n\n /**\n * Wait for a sync with the server when online, but still raise events while\n * offline.\n */\n readonly waitForSyncWhenOnline?: boolean;\n}\n\n/**\n * QueryListener takes a series of internal view snapshots and determines\n * when to raise the event.\n *\n * It uses an Observer to dispatch events.\n */\nexport class QueryListener {\n /**\n * Initial snapshots (e.g. from cache) may not be propagated to the wrapped\n * observer. This flag is set to true once we've actually raised an event.\n */\n private raisedInitialEvent = false;\n\n private options: ListenOptions;\n\n private snap: ViewSnapshot | null = null;\n\n private onlineState = OnlineState.Unknown;\n\n constructor(\n readonly query: Query,\n private queryObserver: Observer,\n options?: ListenOptions\n ) {\n this.options = options || {};\n }\n\n /**\n * Applies the new ViewSnapshot to this listener, raising a user-facing event\n * if applicable (depending on what changed, whether the user has opted into\n * metadata-only changes, etc.). Returns true if a user-facing event was\n * indeed raised.\n */\n onViewSnapshot(snap: ViewSnapshot): boolean {\n debugAssert(\n snap.docChanges.length > 0 || snap.syncStateChanged,\n 'We got a new snapshot with no changes?'\n );\n\n if (!this.options.includeMetadataChanges) {\n // Remove the metadata only changes.\n const docChanges: DocumentViewChange[] = [];\n for (const docChange of snap.docChanges) {\n if (docChange.type !== ChangeType.Metadata) {\n docChanges.push(docChange);\n }\n }\n snap = new ViewSnapshot(\n snap.query,\n snap.docs,\n snap.oldDocs,\n docChanges,\n snap.mutatedKeys,\n snap.fromCache,\n snap.syncStateChanged,\n /* excludesMetadataChanges= */ true\n );\n }\n let raisedEvent = false;\n if (!this.raisedInitialEvent) {\n if (this.shouldRaiseInitialEvent(snap, this.onlineState)) {\n this.raiseInitialEvent(snap);\n raisedEvent = true;\n }\n } else if (this.shouldRaiseEvent(snap)) {\n this.queryObserver.next(snap);\n raisedEvent = true;\n }\n\n this.snap = snap;\n return raisedEvent;\n }\n\n onError(error: Error): void {\n this.queryObserver.error(error);\n }\n\n /** Returns whether a snapshot was raised. */\n applyOnlineStateChange(onlineState: OnlineState): boolean {\n this.onlineState = onlineState;\n let raisedEvent = false;\n if (\n this.snap &&\n !this.raisedInitialEvent &&\n this.shouldRaiseInitialEvent(this.snap, onlineState)\n ) {\n this.raiseInitialEvent(this.snap);\n raisedEvent = true;\n }\n return raisedEvent;\n }\n\n private shouldRaiseInitialEvent(\n snap: ViewSnapshot,\n onlineState: OnlineState\n ): boolean {\n debugAssert(\n !this.raisedInitialEvent,\n 'Determining whether to raise first event but already had first event'\n );\n\n // Always raise the first event when we're synced\n if (!snap.fromCache) {\n return true;\n }\n\n // NOTE: We consider OnlineState.Unknown as online (it should become Offline\n // or Online if we wait long enough).\n const maybeOnline = onlineState !== OnlineState.Offline;\n // Don't raise the event if we're online, aren't synced yet (checked\n // above) and are waiting for a sync.\n if (this.options.waitForSyncWhenOnline && maybeOnline) {\n debugAssert(\n snap.fromCache,\n 'Waiting for sync, but snapshot is not from cache'\n );\n return false;\n }\n\n // Raise data from cache if we have any documents or we are offline\n return !snap.docs.isEmpty() || onlineState === OnlineState.Offline;\n }\n\n private shouldRaiseEvent(snap: ViewSnapshot): boolean {\n // We don't need to handle includeDocumentMetadataChanges here because\n // the Metadata only changes have already been stripped out if needed.\n // At this point the only changes we will see are the ones we should\n // propagate.\n if (snap.docChanges.length > 0) {\n return true;\n }\n\n const hasPendingWritesChanged =\n this.snap && this.snap.hasPendingWrites !== snap.hasPendingWrites;\n if (snap.syncStateChanged || hasPendingWritesChanged) {\n return this.options.includeMetadataChanges === true;\n }\n\n // Generally we should have hit one of the cases above, but it's possible\n // to get here if there were only metadata docChanges and they got\n // stripped out.\n return false;\n }\n\n private raiseInitialEvent(snap: ViewSnapshot): void {\n debugAssert(\n !this.raisedInitialEvent,\n 'Trying to raise initial events for second time'\n );\n snap = ViewSnapshot.fromInitialDocuments(\n snap.query,\n snap.docs,\n snap.mutatedKeys,\n snap.fromCache\n );\n this.raisedInitialEvent = true;\n this.queryObserver.next(snap);\n }\n}\n","/**\n * @license\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { QueryEngine } from './query_engine';\nimport { LocalDocumentsView } from './local_documents_view';\nimport { PersistenceTransaction } from './persistence';\nimport { PersistencePromise } from './persistence_promise';\nimport {\n LimitType,\n newQueryComparator,\n Query,\n queryMatches,\n stringifyQuery\n} from '../core/query';\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport {\n DocumentKeySet,\n DocumentMap,\n MaybeDocumentMap\n} from '../model/collections';\nimport { Document } from '../model/document';\nimport { debugAssert } from '../util/assert';\nimport { getLogLevel, LogLevel, logDebug } from '../util/log';\nimport { SortedSet } from '../util/sorted_set';\n\n// TOOD(b/140938512): Drop SimpleQueryEngine and rename IndexFreeQueryEngine.\n\n/**\n * A query engine that takes advantage of the target document mapping in the\n * QueryCache. The IndexFreeQueryEngine optimizes query execution by only\n * reading the documents that previously matched a query plus any documents that were\n * edited after the query was last listened to.\n *\n * There are some cases where Index-Free queries are not guaranteed to produce\n * the same results as full collection scans. In these cases, the\n * IndexFreeQueryEngine falls back to full query processing. These cases are:\n *\n * - Limit queries where a document that matched the query previously no longer\n * matches the query.\n *\n * - Limit queries where a document edit may cause the document to sort below\n * another document that is in the local cache.\n *\n * - Queries that have never been CURRENT or free of Limbo documents.\n */\nexport class IndexFreeQueryEngine implements QueryEngine {\n private localDocumentsView: LocalDocumentsView | undefined;\n\n setLocalDocumentsView(localDocuments: LocalDocumentsView): void {\n this.localDocumentsView = localDocuments;\n }\n\n getDocumentsMatchingQuery(\n transaction: PersistenceTransaction,\n query: Query,\n lastLimboFreeSnapshotVersion: SnapshotVersion,\n remoteKeys: DocumentKeySet\n ): PersistencePromise {\n debugAssert(\n this.localDocumentsView !== undefined,\n 'setLocalDocumentsView() not called'\n );\n\n // Queries that match all documents don't benefit from using\n // IndexFreeQueries. It is more efficient to scan all documents in a\n // collection, rather than to perform individual lookups.\n if (query.matchesAllDocuments()) {\n return this.executeFullCollectionScan(transaction, query);\n }\n\n // Queries that have never seen a snapshot without limbo free documents\n // should also be run as a full collection scan.\n if (lastLimboFreeSnapshotVersion.isEqual(SnapshotVersion.min())) {\n return this.executeFullCollectionScan(transaction, query);\n }\n\n return this.localDocumentsView!.getDocuments(transaction, remoteKeys).next(\n documents => {\n const previousResults = this.applyQuery(query, documents);\n\n if (\n (query.hasLimitToFirst() || query.hasLimitToLast()) &&\n this.needsRefill(\n query.limitType,\n previousResults,\n remoteKeys,\n lastLimboFreeSnapshotVersion\n )\n ) {\n return this.executeFullCollectionScan(transaction, query);\n }\n\n if (getLogLevel() <= LogLevel.DEBUG) {\n logDebug(\n 'IndexFreeQueryEngine',\n 'Re-using previous result from %s to execute query: %s',\n lastLimboFreeSnapshotVersion.toString(),\n stringifyQuery(query)\n );\n }\n\n // Retrieve all results for documents that were updated since the last\n // limbo-document free remote snapshot.\n return this.localDocumentsView!.getDocumentsMatchingQuery(\n transaction,\n query,\n lastLimboFreeSnapshotVersion\n ).next(updatedResults => {\n // We merge `previousResults` into `updateResults`, since\n // `updateResults` is already a DocumentMap. If a document is\n // contained in both lists, then its contents are the same.\n previousResults.forEach(doc => {\n updatedResults = updatedResults.insert(doc.key, doc);\n });\n return updatedResults;\n });\n }\n );\n }\n\n /** Applies the query filter and sorting to the provided documents. */\n private applyQuery(\n query: Query,\n documents: MaybeDocumentMap\n ): SortedSet {\n // Sort the documents and re-apply the query filter since previously\n // matching documents do not necessarily still match the query.\n let queryResults = new SortedSet(newQueryComparator(query));\n documents.forEach((_, maybeDoc) => {\n if (maybeDoc instanceof Document && queryMatches(query, maybeDoc)) {\n queryResults = queryResults.add(maybeDoc);\n }\n });\n return queryResults;\n }\n\n /**\n * Determines if a limit query needs to be refilled from cache, making it\n * ineligible for index-free execution.\n *\n * @param sortedPreviousResults The documents that matched the query when it\n * was last synchronized, sorted by the query's comparator.\n * @param remoteKeys The document keys that matched the query at the last\n * snapshot.\n * @param limboFreeSnapshotVersion The version of the snapshot when the query\n * was last synchronized.\n */\n private needsRefill(\n limitType: LimitType,\n sortedPreviousResults: SortedSet,\n remoteKeys: DocumentKeySet,\n limboFreeSnapshotVersion: SnapshotVersion\n ): boolean {\n // The query needs to be refilled if a previously matching document no\n // longer matches.\n if (remoteKeys.size !== sortedPreviousResults.size) {\n return true;\n }\n\n // Limit queries are not eligible for index-free query execution if there is\n // a potential that an older document from cache now sorts before a document\n // that was previously part of the limit. This, however, can only happen if\n // the document at the edge of the limit goes out of limit.\n // If a document that is not the limit boundary sorts differently,\n // the boundary of the limit itself did not change and documents from cache\n // will continue to be \"rejected\" by this boundary. Therefore, we can ignore\n // any modifications that don't affect the last document.\n const docAtLimitEdge =\n limitType === LimitType.First\n ? sortedPreviousResults.last()\n : sortedPreviousResults.first();\n if (!docAtLimitEdge) {\n // We don't need to refill the query if there were already no documents.\n return false;\n }\n return (\n docAtLimitEdge.hasPendingWrites ||\n docAtLimitEdge.version.compareTo(limboFreeSnapshotVersion) > 0\n );\n }\n\n private executeFullCollectionScan(\n transaction: PersistenceTransaction,\n query: Query\n ): PersistencePromise {\n if (getLogLevel() <= LogLevel.DEBUG) {\n logDebug(\n 'IndexFreeQueryEngine',\n 'Using full collection scan to execute query:',\n stringifyQuery(query)\n );\n }\n\n return this.localDocumentsView!.getDocumentsMatchingQuery(\n transaction,\n query,\n SnapshotVersion.min()\n );\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Timestamp } from '../api/timestamp';\nimport { Query } from '../core/query';\nimport { BatchId } from '../core/types';\nimport { DocumentKey } from '../model/document_key';\nimport { Mutation } from '../model/mutation';\nimport { MutationBatch, BATCHID_UNKNOWN } from '../model/mutation_batch';\nimport { debugAssert, hardAssert } from '../util/assert';\nimport { primitiveComparator } from '../util/misc';\nimport { SortedMap } from '../util/sorted_map';\nimport { SortedSet } from '../util/sorted_set';\n\nimport { IndexManager } from './index_manager';\nimport { MutationQueue } from './mutation_queue';\nimport { PersistenceTransaction, ReferenceDelegate } from './persistence';\nimport { PersistencePromise } from './persistence_promise';\nimport { DocReference } from './reference_set';\n\nexport class MemoryMutationQueue implements MutationQueue {\n /**\n * The set of all mutations that have been sent but not yet been applied to\n * the backend.\n */\n private mutationQueue: MutationBatch[] = [];\n\n /** Next value to use when assigning sequential IDs to each mutation batch. */\n private nextBatchId: BatchId = 1;\n\n /** An ordered mapping between documents and the mutations batch IDs. */\n private batchesByDocumentKey = new SortedSet(DocReference.compareByKey);\n\n constructor(\n private readonly indexManager: IndexManager,\n private readonly referenceDelegate: ReferenceDelegate\n ) {}\n\n checkEmpty(transaction: PersistenceTransaction): PersistencePromise {\n return PersistencePromise.resolve(this.mutationQueue.length === 0);\n }\n\n addMutationBatch(\n transaction: PersistenceTransaction,\n localWriteTime: Timestamp,\n baseMutations: Mutation[],\n mutations: Mutation[]\n ): PersistencePromise {\n debugAssert(mutations.length !== 0, 'Mutation batches should not be empty');\n\n const batchId = this.nextBatchId;\n this.nextBatchId++;\n\n if (this.mutationQueue.length > 0) {\n const prior = this.mutationQueue[this.mutationQueue.length - 1];\n debugAssert(\n prior.batchId < batchId,\n 'Mutation batchIDs must be monotonically increasing order'\n );\n }\n\n const batch = new MutationBatch(\n batchId,\n localWriteTime,\n baseMutations,\n mutations\n );\n this.mutationQueue.push(batch);\n\n // Track references by document key and index collection parents.\n for (const mutation of mutations) {\n this.batchesByDocumentKey = this.batchesByDocumentKey.add(\n new DocReference(mutation.key, batchId)\n );\n\n this.indexManager.addToCollectionParentIndex(\n transaction,\n mutation.key.path.popLast()\n );\n }\n\n return PersistencePromise.resolve(batch);\n }\n\n lookupMutationBatch(\n transaction: PersistenceTransaction,\n batchId: BatchId\n ): PersistencePromise {\n return PersistencePromise.resolve(this.findMutationBatch(batchId));\n }\n\n getNextMutationBatchAfterBatchId(\n transaction: PersistenceTransaction,\n batchId: BatchId\n ): PersistencePromise {\n const nextBatchId = batchId + 1;\n\n // The requested batchId may still be out of range so normalize it to the\n // start of the queue.\n const rawIndex = this.indexOfBatchId(nextBatchId);\n const index = rawIndex < 0 ? 0 : rawIndex;\n return PersistencePromise.resolve(\n this.mutationQueue.length > index ? this.mutationQueue[index] : null\n );\n }\n\n getHighestUnacknowledgedBatchId(): PersistencePromise {\n return PersistencePromise.resolve(\n this.mutationQueue.length === 0 ? BATCHID_UNKNOWN : this.nextBatchId - 1\n );\n }\n\n getAllMutationBatches(\n transaction: PersistenceTransaction\n ): PersistencePromise {\n return PersistencePromise.resolve(this.mutationQueue.slice());\n }\n\n getAllMutationBatchesAffectingDocumentKey(\n transaction: PersistenceTransaction,\n documentKey: DocumentKey\n ): PersistencePromise {\n const start = new DocReference(documentKey, 0);\n const end = new DocReference(documentKey, Number.POSITIVE_INFINITY);\n const result: MutationBatch[] = [];\n this.batchesByDocumentKey.forEachInRange([start, end], ref => {\n debugAssert(\n documentKey.isEqual(ref.key),\n \"Should only iterate over a single key's batches\"\n );\n const batch = this.findMutationBatch(ref.targetOrBatchId);\n debugAssert(\n batch !== null,\n 'Batches in the index must exist in the main table'\n );\n result.push(batch!);\n });\n\n return PersistencePromise.resolve(result);\n }\n\n getAllMutationBatchesAffectingDocumentKeys(\n transaction: PersistenceTransaction,\n documentKeys: SortedMap\n ): PersistencePromise {\n let uniqueBatchIDs = new SortedSet(primitiveComparator);\n\n documentKeys.forEach(documentKey => {\n const start = new DocReference(documentKey, 0);\n const end = new DocReference(documentKey, Number.POSITIVE_INFINITY);\n this.batchesByDocumentKey.forEachInRange([start, end], ref => {\n debugAssert(\n documentKey.isEqual(ref.key),\n \"For each key, should only iterate over a single key's batches\"\n );\n\n uniqueBatchIDs = uniqueBatchIDs.add(ref.targetOrBatchId);\n });\n });\n\n return PersistencePromise.resolve(this.findMutationBatches(uniqueBatchIDs));\n }\n\n getAllMutationBatchesAffectingQuery(\n transaction: PersistenceTransaction,\n query: Query\n ): PersistencePromise {\n debugAssert(\n !query.isCollectionGroupQuery(),\n 'CollectionGroup queries should be handled in LocalDocumentsView'\n );\n // Use the query path as a prefix for testing if a document matches the\n // query.\n const prefix = query.path;\n const immediateChildrenPathLength = prefix.length + 1;\n\n // Construct a document reference for actually scanning the index. Unlike\n // the prefix the document key in this reference must have an even number of\n // segments. The empty segment can be used a suffix of the query path\n // because it precedes all other segments in an ordered traversal.\n let startPath = prefix;\n if (!DocumentKey.isDocumentKey(startPath)) {\n startPath = startPath.child('');\n }\n\n const start = new DocReference(new DocumentKey(startPath), 0);\n\n // Find unique batchIDs referenced by all documents potentially matching the\n // query.\n let uniqueBatchIDs = new SortedSet(primitiveComparator);\n\n this.batchesByDocumentKey.forEachWhile(ref => {\n const rowKeyPath = ref.key.path;\n if (!prefix.isPrefixOf(rowKeyPath)) {\n return false;\n } else {\n // Rows with document keys more than one segment longer than the query\n // path can't be matches. For example, a query on 'rooms' can't match\n // the document /rooms/abc/messages/xyx.\n // TODO(mcg): we'll need a different scanner when we implement\n // ancestor queries.\n if (rowKeyPath.length === immediateChildrenPathLength) {\n uniqueBatchIDs = uniqueBatchIDs.add(ref.targetOrBatchId);\n }\n return true;\n }\n }, start);\n\n return PersistencePromise.resolve(this.findMutationBatches(uniqueBatchIDs));\n }\n\n private findMutationBatches(batchIDs: SortedSet): MutationBatch[] {\n // Construct an array of matching batches, sorted by batchID to ensure that\n // multiple mutations affecting the same document key are applied in order.\n const result: MutationBatch[] = [];\n batchIDs.forEach(batchId => {\n const batch = this.findMutationBatch(batchId);\n if (batch !== null) {\n result.push(batch);\n }\n });\n return result;\n }\n\n removeMutationBatch(\n transaction: PersistenceTransaction,\n batch: MutationBatch\n ): PersistencePromise {\n // Find the position of the first batch for removal.\n const batchIndex = this.indexOfExistingBatchId(batch.batchId, 'removed');\n hardAssert(\n batchIndex === 0,\n 'Can only remove the first entry of the mutation queue'\n );\n this.mutationQueue.shift();\n\n let references = this.batchesByDocumentKey;\n return PersistencePromise.forEach(batch.mutations, (mutation: Mutation) => {\n const ref = new DocReference(mutation.key, batch.batchId);\n references = references.delete(ref);\n return this.referenceDelegate.markPotentiallyOrphaned(\n transaction,\n mutation.key\n );\n }).next(() => {\n this.batchesByDocumentKey = references;\n });\n }\n\n removeCachedMutationKeys(batchId: BatchId): void {\n // No-op since the memory mutation queue does not maintain a separate cache.\n }\n\n containsKey(\n txn: PersistenceTransaction,\n key: DocumentKey\n ): PersistencePromise {\n const ref = new DocReference(key, 0);\n const firstRef = this.batchesByDocumentKey.firstAfterOrEqual(ref);\n return PersistencePromise.resolve(key.isEqual(firstRef && firstRef.key));\n }\n\n performConsistencyCheck(\n txn: PersistenceTransaction\n ): PersistencePromise {\n if (this.mutationQueue.length === 0) {\n debugAssert(\n this.batchesByDocumentKey.isEmpty(),\n 'Document leak -- detected dangling mutation references when queue is empty.'\n );\n }\n return PersistencePromise.resolve();\n }\n\n /**\n * Finds the index of the given batchId in the mutation queue and asserts that\n * the resulting index is within the bounds of the queue.\n *\n * @param batchId The batchId to search for\n * @param action A description of what the caller is doing, phrased in passive\n * form (e.g. \"acknowledged\" in a routine that acknowledges batches).\n */\n private indexOfExistingBatchId(batchId: BatchId, action: string): number {\n const index = this.indexOfBatchId(batchId);\n debugAssert(\n index >= 0 && index < this.mutationQueue.length,\n 'Batches must exist to be ' + action\n );\n return index;\n }\n\n /**\n * Finds the index of the given batchId in the mutation queue. This operation\n * is O(1).\n *\n * @return The computed index of the batch with the given batchId, based on\n * the state of the queue. Note this index can be negative if the requested\n * batchId has already been remvoed from the queue or past the end of the\n * queue if the batchId is larger than the last added batch.\n */\n private indexOfBatchId(batchId: BatchId): number {\n if (this.mutationQueue.length === 0) {\n // As an index this is past the end of the queue\n return 0;\n }\n\n // Examine the front of the queue to figure out the difference between the\n // batchId and indexes in the array. Note that since the queue is ordered\n // by batchId, if the first batch has a larger batchId then the requested\n // batchId doesn't exist in the queue.\n const firstBatchId = this.mutationQueue[0].batchId;\n return batchId - firstBatchId;\n }\n\n /**\n * A version of lookupMutationBatch that doesn't return a promise, this makes\n * other functions that uses this code easier to read and more efficent.\n */\n private findMutationBatch(batchId: BatchId): MutationBatch | null {\n const index = this.indexOfBatchId(batchId);\n if (index < 0 || index >= this.mutationQueue.length) {\n return null;\n }\n\n const batch = this.mutationQueue[index];\n debugAssert(batch.batchId === batchId, 'If found batch must match');\n return batch;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Query, queryMatches } from '../core/query';\nimport {\n DocumentKeySet,\n DocumentMap,\n documentMap,\n DocumentSizeEntry,\n NullableMaybeDocumentMap,\n nullableMaybeDocumentMap\n} from '../model/collections';\nimport { Document, MaybeDocument } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\n\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { debugAssert } from '../util/assert';\nimport { SortedMap } from '../util/sorted_map';\nimport { IndexManager } from './index_manager';\nimport { PersistenceTransaction } from './persistence';\nimport { PersistencePromise } from './persistence_promise';\nimport { RemoteDocumentCache } from './remote_document_cache';\nimport { RemoteDocumentChangeBuffer } from './remote_document_change_buffer';\n\nexport type DocumentSizer = (doc: MaybeDocument) => number;\n\n/** Miscellaneous collection types / constants. */\ninterface MemoryRemoteDocumentCacheEntry extends DocumentSizeEntry {\n readTime: SnapshotVersion;\n}\n\ntype DocumentEntryMap = SortedMap;\nfunction documentEntryMap(): DocumentEntryMap {\n return new SortedMap(\n DocumentKey.comparator\n );\n}\n\nexport class MemoryRemoteDocumentCache implements RemoteDocumentCache {\n /** Underlying cache of documents and their read times. */\n private docs = documentEntryMap();\n\n /** Size of all cached documents. */\n private size = 0;\n\n /**\n * @param sizer Used to assess the size of a document. For eager GC, this is expected to just\n * return 0 to avoid unnecessarily doing the work of calculating the size.\n */\n constructor(\n private readonly indexManager: IndexManager,\n private readonly sizer: DocumentSizer\n ) {}\n\n /**\n * Adds the supplied entry to the cache and updates the cache size as appropriate.\n *\n * All calls of `addEntry` are required to go through the RemoteDocumentChangeBuffer\n * returned by `newChangeBuffer()`.\n */\n private addEntry(\n transaction: PersistenceTransaction,\n doc: MaybeDocument,\n readTime: SnapshotVersion\n ): PersistencePromise {\n debugAssert(\n !readTime.isEqual(SnapshotVersion.min()),\n 'Cannot add a document with a read time of zero'\n );\n\n const key = doc.key;\n const entry = this.docs.get(key);\n const previousSize = entry ? entry.size : 0;\n const currentSize = this.sizer(doc);\n\n this.docs = this.docs.insert(key, {\n maybeDocument: doc,\n size: currentSize,\n readTime\n });\n\n this.size += currentSize - previousSize;\n\n return this.indexManager.addToCollectionParentIndex(\n transaction,\n key.path.popLast()\n );\n }\n\n /**\n * Removes the specified entry from the cache and updates the cache size as appropriate.\n *\n * All calls of `removeEntry` are required to go through the RemoteDocumentChangeBuffer\n * returned by `newChangeBuffer()`.\n */\n private removeEntry(documentKey: DocumentKey): void {\n const entry = this.docs.get(documentKey);\n if (entry) {\n this.docs = this.docs.remove(documentKey);\n this.size -= entry.size;\n }\n }\n\n getEntry(\n transaction: PersistenceTransaction,\n documentKey: DocumentKey\n ): PersistencePromise {\n const entry = this.docs.get(documentKey);\n return PersistencePromise.resolve(entry ? entry.maybeDocument : null);\n }\n\n getEntries(\n transaction: PersistenceTransaction,\n documentKeys: DocumentKeySet\n ): PersistencePromise {\n let results = nullableMaybeDocumentMap();\n documentKeys.forEach(documentKey => {\n const entry = this.docs.get(documentKey);\n results = results.insert(documentKey, entry ? entry.maybeDocument : null);\n });\n return PersistencePromise.resolve(results);\n }\n\n getDocumentsMatchingQuery(\n transaction: PersistenceTransaction,\n query: Query,\n sinceReadTime: SnapshotVersion\n ): PersistencePromise {\n debugAssert(\n !query.isCollectionGroupQuery(),\n 'CollectionGroup queries should be handled in LocalDocumentsView'\n );\n let results = documentMap();\n\n // Documents are ordered by key, so we can use a prefix scan to narrow down\n // the documents we need to match the query against.\n const prefix = new DocumentKey(query.path.child(''));\n const iterator = this.docs.getIteratorFrom(prefix);\n while (iterator.hasNext()) {\n const {\n key,\n value: { maybeDocument, readTime }\n } = iterator.getNext();\n if (!query.path.isPrefixOf(key.path)) {\n break;\n }\n if (readTime.compareTo(sinceReadTime) <= 0) {\n continue;\n }\n if (\n maybeDocument instanceof Document &&\n queryMatches(query, maybeDocument)\n ) {\n results = results.insert(maybeDocument.key, maybeDocument);\n }\n }\n return PersistencePromise.resolve(results);\n }\n\n forEachDocumentKey(\n transaction: PersistenceTransaction,\n f: (key: DocumentKey) => PersistencePromise\n ): PersistencePromise {\n return PersistencePromise.forEach(this.docs, (key: DocumentKey) => f(key));\n }\n\n newChangeBuffer(options?: {\n trackRemovals: boolean;\n }): RemoteDocumentChangeBuffer {\n // `trackRemovals` is ignores since the MemoryRemoteDocumentCache keeps\n // a separate changelog and does not need special handling for removals.\n return new MemoryRemoteDocumentCache.RemoteDocumentChangeBuffer(this);\n }\n\n getSize(txn: PersistenceTransaction): PersistencePromise {\n return PersistencePromise.resolve(this.size);\n }\n\n /**\n * Handles the details of adding and updating documents in the MemoryRemoteDocumentCache.\n */\n private static RemoteDocumentChangeBuffer = class extends RemoteDocumentChangeBuffer {\n constructor(private readonly documentCache: MemoryRemoteDocumentCache) {\n super();\n }\n\n protected applyChanges(\n transaction: PersistenceTransaction\n ): PersistencePromise {\n const promises: Array> = [];\n this.changes.forEach((key, doc) => {\n if (doc) {\n promises.push(\n this.documentCache.addEntry(transaction, doc, this.readTime)\n );\n } else {\n this.documentCache.removeEntry(key);\n }\n });\n return PersistencePromise.waitFor(promises);\n }\n\n protected getFromCache(\n transaction: PersistenceTransaction,\n documentKey: DocumentKey\n ): PersistencePromise {\n return this.documentCache.getEntry(transaction, documentKey);\n }\n\n protected getAllFromCache(\n transaction: PersistenceTransaction,\n documentKeys: DocumentKeySet\n ): PersistencePromise {\n return this.documentCache.getEntries(transaction, documentKeys);\n }\n };\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DocumentKeySet, NullableMaybeDocumentMap } from '../model/collections';\nimport { MaybeDocument } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport { debugAssert } from '../util/assert';\nimport { ObjectMap } from '../util/obj_map';\n\nimport { PersistenceTransaction } from './persistence';\nimport { PersistencePromise } from './persistence_promise';\nimport { SnapshotVersion } from '../core/snapshot_version';\n\n/**\n * An in-memory buffer of entries to be written to a RemoteDocumentCache.\n * It can be used to batch up a set of changes to be written to the cache, but\n * additionally supports reading entries back with the `getEntry()` method,\n * falling back to the underlying RemoteDocumentCache if no entry is\n * buffered.\n *\n * Entries added to the cache *must* be read first. This is to facilitate\n * calculating the size delta of the pending changes.\n *\n * PORTING NOTE: This class was implemented then removed from other platforms.\n * If byte-counting ends up being needed on the other platforms, consider\n * porting this class as part of that implementation work.\n */\nexport abstract class RemoteDocumentChangeBuffer {\n // A mapping of document key to the new cache entry that should be written (or null if any\n // existing cache entry should be removed).\n protected changes: ObjectMap<\n DocumentKey,\n MaybeDocument | null\n > = new ObjectMap(\n key => key.toString(),\n (l, r) => l.isEqual(r)\n );\n\n // The read time to use for all added documents in this change buffer.\n private _readTime: SnapshotVersion | undefined;\n\n private changesApplied = false;\n\n protected abstract getFromCache(\n transaction: PersistenceTransaction,\n documentKey: DocumentKey\n ): PersistencePromise;\n\n protected abstract getAllFromCache(\n transaction: PersistenceTransaction,\n documentKeys: DocumentKeySet\n ): PersistencePromise;\n\n protected abstract applyChanges(\n transaction: PersistenceTransaction\n ): PersistencePromise;\n\n protected set readTime(value: SnapshotVersion) {\n // Right now (for simplicity) we just track a single readTime for all the\n // added entries since we expect them to all be the same, but we could\n // rework to store per-entry readTimes if necessary.\n debugAssert(\n this._readTime === undefined || this._readTime.isEqual(value),\n 'All changes in a RemoteDocumentChangeBuffer must have the same read time'\n );\n this._readTime = value;\n }\n\n protected get readTime(): SnapshotVersion {\n debugAssert(\n this._readTime !== undefined,\n 'Read time is not set. All removeEntry() calls must include a readTime if `trackRemovals` is used.'\n );\n return this._readTime;\n }\n\n /**\n * Buffers a `RemoteDocumentCache.addEntry()` call.\n *\n * You can only modify documents that have already been retrieved via\n * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`).\n */\n addEntry(maybeDocument: MaybeDocument, readTime: SnapshotVersion): void {\n this.assertNotApplied();\n this.readTime = readTime;\n this.changes.set(maybeDocument.key, maybeDocument);\n }\n\n /**\n * Buffers a `RemoteDocumentCache.removeEntry()` call.\n *\n * You can only remove documents that have already been retrieved via\n * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`).\n */\n removeEntry(key: DocumentKey, readTime?: SnapshotVersion): void {\n this.assertNotApplied();\n if (readTime) {\n this.readTime = readTime;\n }\n this.changes.set(key, null);\n }\n\n /**\n * Looks up an entry in the cache. The buffered changes will first be checked,\n * and if no buffered change applies, this will forward to\n * `RemoteDocumentCache.getEntry()`.\n *\n * @param transaction The transaction in which to perform any persistence\n * operations.\n * @param documentKey The key of the entry to look up.\n * @return The cached Document or NoDocument entry, or null if we have nothing\n * cached.\n */\n getEntry(\n transaction: PersistenceTransaction,\n documentKey: DocumentKey\n ): PersistencePromise {\n this.assertNotApplied();\n const bufferedEntry = this.changes.get(documentKey);\n if (bufferedEntry !== undefined) {\n return PersistencePromise.resolve(bufferedEntry);\n } else {\n return this.getFromCache(transaction, documentKey);\n }\n }\n\n /**\n * Looks up several entries in the cache, forwarding to\n * `RemoteDocumentCache.getEntry()`.\n *\n * @param transaction The transaction in which to perform any persistence\n * operations.\n * @param documentKeys The keys of the entries to look up.\n * @return A map of cached `Document`s or `NoDocument`s, indexed by key. If an\n * entry cannot be found, the corresponding key will be mapped to a null\n * value.\n */\n getEntries(\n transaction: PersistenceTransaction,\n documentKeys: DocumentKeySet\n ): PersistencePromise {\n return this.getAllFromCache(transaction, documentKeys);\n }\n\n /**\n * Applies buffered changes to the underlying RemoteDocumentCache, using\n * the provided transaction.\n */\n apply(transaction: PersistenceTransaction): PersistencePromise {\n this.assertNotApplied();\n this.changesApplied = true;\n return this.applyChanges(transaction);\n }\n\n /** Helper to assert this.changes is not null */\n protected assertNotApplied(): void {\n debugAssert(!this.changesApplied, 'Changes have already been applied.');\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { TargetIdGenerator } from '../core/target_id_generator';\nimport { ListenSequenceNumber, TargetId } from '../core/types';\nimport { DocumentKeySet } from '../model/collections';\nimport { DocumentKey } from '../model/document_key';\nimport { debugAssert } from '../util/assert';\nimport { ObjectMap } from '../util/obj_map';\n\nimport { ActiveTargets } from './lru_garbage_collector';\nimport { MemoryPersistence } from './memory_persistence';\nimport { PersistenceTransaction } from './persistence';\nimport { PersistencePromise } from './persistence_promise';\nimport { ReferenceSet } from './reference_set';\nimport { TargetCache } from './target_cache';\nimport { TargetData } from './target_data';\nimport { canonifyTarget, Target, targetEquals } from '../core/target';\n\nexport class MemoryTargetCache implements TargetCache {\n /**\n * Maps a target to the data about that target\n */\n private targets = new ObjectMap(\n t => canonifyTarget(t),\n targetEquals\n );\n\n /** The last received snapshot version. */\n private lastRemoteSnapshotVersion = SnapshotVersion.min();\n /** The highest numbered target ID encountered. */\n private highestTargetId: TargetId = 0;\n /** The highest sequence number encountered. */\n private highestSequenceNumber: ListenSequenceNumber = 0;\n /**\n * A ordered bidirectional mapping between documents and the remote target\n * IDs.\n */\n private references = new ReferenceSet();\n\n private targetCount = 0;\n\n private targetIdGenerator = TargetIdGenerator.forTargetCache();\n\n constructor(private readonly persistence: MemoryPersistence) {}\n\n forEachTarget(\n txn: PersistenceTransaction,\n f: (q: TargetData) => void\n ): PersistencePromise {\n this.targets.forEach((_, targetData) => f(targetData));\n return PersistencePromise.resolve();\n }\n\n getLastRemoteSnapshotVersion(\n transaction: PersistenceTransaction\n ): PersistencePromise {\n return PersistencePromise.resolve(this.lastRemoteSnapshotVersion);\n }\n\n getHighestSequenceNumber(\n transaction: PersistenceTransaction\n ): PersistencePromise {\n return PersistencePromise.resolve(this.highestSequenceNumber);\n }\n\n allocateTargetId(\n transaction: PersistenceTransaction\n ): PersistencePromise {\n this.highestTargetId = this.targetIdGenerator.next();\n return PersistencePromise.resolve(this.highestTargetId);\n }\n\n setTargetsMetadata(\n transaction: PersistenceTransaction,\n highestListenSequenceNumber: number,\n lastRemoteSnapshotVersion?: SnapshotVersion\n ): PersistencePromise {\n if (lastRemoteSnapshotVersion) {\n this.lastRemoteSnapshotVersion = lastRemoteSnapshotVersion;\n }\n if (highestListenSequenceNumber > this.highestSequenceNumber) {\n this.highestSequenceNumber = highestListenSequenceNumber;\n }\n return PersistencePromise.resolve();\n }\n\n private saveTargetData(targetData: TargetData): void {\n this.targets.set(targetData.target, targetData);\n const targetId = targetData.targetId;\n if (targetId > this.highestTargetId) {\n this.targetIdGenerator = new TargetIdGenerator(targetId);\n this.highestTargetId = targetId;\n }\n if (targetData.sequenceNumber > this.highestSequenceNumber) {\n this.highestSequenceNumber = targetData.sequenceNumber;\n }\n }\n\n addTargetData(\n transaction: PersistenceTransaction,\n targetData: TargetData\n ): PersistencePromise {\n debugAssert(\n !this.targets.has(targetData.target),\n 'Adding a target that already exists'\n );\n this.saveTargetData(targetData);\n this.targetCount += 1;\n return PersistencePromise.resolve();\n }\n\n updateTargetData(\n transaction: PersistenceTransaction,\n targetData: TargetData\n ): PersistencePromise {\n debugAssert(\n this.targets.has(targetData.target),\n 'Updating a non-existent target'\n );\n this.saveTargetData(targetData);\n return PersistencePromise.resolve();\n }\n\n removeTargetData(\n transaction: PersistenceTransaction,\n targetData: TargetData\n ): PersistencePromise {\n debugAssert(this.targetCount > 0, 'Removing a target from an empty cache');\n debugAssert(\n this.targets.has(targetData.target),\n 'Removing a non-existent target from the cache'\n );\n this.targets.delete(targetData.target);\n this.references.removeReferencesForId(targetData.targetId);\n this.targetCount -= 1;\n return PersistencePromise.resolve();\n }\n\n removeTargets(\n transaction: PersistenceTransaction,\n upperBound: ListenSequenceNumber,\n activeTargetIds: ActiveTargets\n ): PersistencePromise {\n let count = 0;\n const removals: Array> = [];\n this.targets.forEach((key, targetData) => {\n if (\n targetData.sequenceNumber <= upperBound &&\n activeTargetIds.get(targetData.targetId) === null\n ) {\n this.targets.delete(key);\n removals.push(\n this.removeMatchingKeysForTargetId(transaction, targetData.targetId)\n );\n count++;\n }\n });\n return PersistencePromise.waitFor(removals).next(() => count);\n }\n\n getTargetCount(\n transaction: PersistenceTransaction\n ): PersistencePromise {\n return PersistencePromise.resolve(this.targetCount);\n }\n\n getTargetData(\n transaction: PersistenceTransaction,\n target: Target\n ): PersistencePromise {\n const targetData = this.targets.get(target) || null;\n return PersistencePromise.resolve(targetData);\n }\n\n addMatchingKeys(\n txn: PersistenceTransaction,\n keys: DocumentKeySet,\n targetId: TargetId\n ): PersistencePromise {\n this.references.addReferences(keys, targetId);\n return PersistencePromise.resolve();\n }\n\n removeMatchingKeys(\n txn: PersistenceTransaction,\n keys: DocumentKeySet,\n targetId: TargetId\n ): PersistencePromise {\n this.references.removeReferences(keys, targetId);\n const referenceDelegate = this.persistence.referenceDelegate;\n const promises: Array> = [];\n if (referenceDelegate) {\n keys.forEach(key => {\n promises.push(referenceDelegate.markPotentiallyOrphaned(txn, key));\n });\n }\n return PersistencePromise.waitFor(promises);\n }\n\n removeMatchingKeysForTargetId(\n txn: PersistenceTransaction,\n targetId: TargetId\n ): PersistencePromise {\n this.references.removeReferencesForId(targetId);\n return PersistencePromise.resolve();\n }\n\n getMatchingKeysForTargetId(\n txn: PersistenceTransaction,\n targetId: TargetId\n ): PersistencePromise {\n const matchingKeys = this.references.referencesForId(targetId);\n return PersistencePromise.resolve(matchingKeys);\n }\n\n containsKey(\n txn: PersistenceTransaction,\n key: DocumentKey\n ): PersistencePromise {\n return PersistencePromise.resolve(this.references.containsKey(key));\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { User } from '../auth/user';\nimport { Document, MaybeDocument } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport { fail } from '../util/assert';\nimport { logDebug } from '../util/log';\nimport { ObjectMap } from '../util/obj_map';\nimport { encodeResourcePath } from './encoded_resource_path';\nimport {\n ActiveTargets,\n LruDelegate,\n LruGarbageCollector,\n LruParams\n} from './lru_garbage_collector';\nimport { ListenSequence } from '../core/listen_sequence';\nimport { ListenSequenceNumber, TargetId } from '../core/types';\nimport { estimateByteSize } from '../model/values';\nimport { MemoryIndexManager } from './memory_index_manager';\nimport { MemoryMutationQueue } from './memory_mutation_queue';\nimport { MemoryRemoteDocumentCache } from './memory_remote_document_cache';\nimport { MemoryTargetCache } from './memory_target_cache';\nimport { MutationQueue } from './mutation_queue';\nimport {\n Persistence,\n PersistenceTransaction,\n PersistenceTransactionMode,\n ReferenceDelegate\n} from './persistence';\nimport { PersistencePromise } from './persistence_promise';\nimport { ReferenceSet } from './reference_set';\nimport { TargetData } from './target_data';\n\nconst LOG_TAG = 'MemoryPersistence';\n/**\n * A memory-backed instance of Persistence. Data is stored only in RAM and\n * not persisted across sessions.\n */\nexport class MemoryPersistence implements Persistence {\n /**\n * Note that these are retained here to make it easier to write tests\n * affecting both the in-memory and IndexedDB-backed persistence layers. Tests\n * can create a new LocalStore wrapping this Persistence instance and this\n * will make the in-memory persistence layer behave as if it were actually\n * persisting values.\n */\n private readonly indexManager: MemoryIndexManager;\n private mutationQueues: { [user: string]: MemoryMutationQueue } = {};\n private readonly remoteDocumentCache: MemoryRemoteDocumentCache;\n private readonly targetCache: MemoryTargetCache;\n private readonly listenSequence = new ListenSequence(0);\n\n private _started = false;\n\n readonly referenceDelegate: MemoryReferenceDelegate;\n\n /**\n * The constructor accepts a factory for creating a reference delegate. This\n * allows both the delegate and this instance to have strong references to\n * each other without having nullable fields that would then need to be\n * checked or asserted on every access.\n */\n constructor(\n referenceDelegateFactory: (p: MemoryPersistence) => MemoryReferenceDelegate\n ) {\n this._started = true;\n this.referenceDelegate = referenceDelegateFactory(this);\n this.targetCache = new MemoryTargetCache(this);\n const sizer = (doc: MaybeDocument): number =>\n this.referenceDelegate.documentSize(doc);\n this.indexManager = new MemoryIndexManager();\n this.remoteDocumentCache = new MemoryRemoteDocumentCache(\n this.indexManager,\n sizer\n );\n }\n\n start(): Promise {\n return Promise.resolve();\n }\n\n shutdown(): Promise {\n // No durable state to ensure is closed on shutdown.\n this._started = false;\n return Promise.resolve();\n }\n\n get started(): boolean {\n return this._started;\n }\n\n setDatabaseDeletedListener(): void {\n // No op.\n }\n\n getIndexManager(): MemoryIndexManager {\n return this.indexManager;\n }\n\n getMutationQueue(user: User): MutationQueue {\n let queue = this.mutationQueues[user.toKey()];\n if (!queue) {\n queue = new MemoryMutationQueue(\n this.indexManager,\n this.referenceDelegate\n );\n this.mutationQueues[user.toKey()] = queue;\n }\n return queue;\n }\n\n getTargetCache(): MemoryTargetCache {\n return this.targetCache;\n }\n\n getRemoteDocumentCache(): MemoryRemoteDocumentCache {\n return this.remoteDocumentCache;\n }\n\n runTransaction(\n action: string,\n mode: PersistenceTransactionMode,\n transactionOperation: (\n transaction: PersistenceTransaction\n ) => PersistencePromise\n ): Promise {\n logDebug(LOG_TAG, 'Starting transaction:', action);\n const txn = new MemoryTransaction(this.listenSequence.next());\n this.referenceDelegate.onTransactionStarted();\n return transactionOperation(txn)\n .next(result => {\n return this.referenceDelegate\n .onTransactionCommitted(txn)\n .next(() => result);\n })\n .toPromise()\n .then(result => {\n txn.raiseOnCommittedEvent();\n return result;\n });\n }\n\n mutationQueuesContainKey(\n transaction: PersistenceTransaction,\n key: DocumentKey\n ): PersistencePromise {\n return PersistencePromise.or(\n Object.values(this.mutationQueues).map(queue => () =>\n queue.containsKey(transaction, key)\n )\n );\n }\n}\n\n/**\n * Memory persistence is not actually transactional, but future implementations\n * may have transaction-scoped state.\n */\nexport class MemoryTransaction extends PersistenceTransaction {\n constructor(readonly currentSequenceNumber: ListenSequenceNumber) {\n super();\n }\n}\n\nexport interface MemoryReferenceDelegate extends ReferenceDelegate {\n documentSize(doc: MaybeDocument): number;\n onTransactionStarted(): void;\n onTransactionCommitted(txn: PersistenceTransaction): PersistencePromise;\n}\n\nexport class MemoryEagerDelegate implements MemoryReferenceDelegate {\n /** Tracks all documents that are active in Query views. */\n private localViewReferences: ReferenceSet = new ReferenceSet();\n /** The list of documents that are potentially GCed after each transaction. */\n private _orphanedDocuments: Set | null = null;\n\n private constructor(private readonly persistence: MemoryPersistence) {}\n\n static factory(persistence: MemoryPersistence): MemoryEagerDelegate {\n return new MemoryEagerDelegate(persistence);\n }\n\n private get orphanedDocuments(): Set {\n if (!this._orphanedDocuments) {\n throw fail('orphanedDocuments is only valid during a transaction.');\n } else {\n return this._orphanedDocuments;\n }\n }\n\n addReference(\n txn: PersistenceTransaction,\n targetId: TargetId,\n key: DocumentKey\n ): PersistencePromise {\n this.localViewReferences.addReference(key, targetId);\n this.orphanedDocuments.delete(key);\n return PersistencePromise.resolve();\n }\n\n removeReference(\n txn: PersistenceTransaction,\n targetId: TargetId,\n key: DocumentKey\n ): PersistencePromise {\n this.localViewReferences.removeReference(key, targetId);\n this.orphanedDocuments.add(key);\n return PersistencePromise.resolve();\n }\n\n markPotentiallyOrphaned(\n txn: PersistenceTransaction,\n key: DocumentKey\n ): PersistencePromise {\n this.orphanedDocuments.add(key);\n return PersistencePromise.resolve();\n }\n\n removeTarget(\n txn: PersistenceTransaction,\n targetData: TargetData\n ): PersistencePromise {\n const orphaned = this.localViewReferences.removeReferencesForId(\n targetData.targetId\n );\n orphaned.forEach(key => this.orphanedDocuments.add(key));\n const cache = this.persistence.getTargetCache();\n return cache\n .getMatchingKeysForTargetId(txn, targetData.targetId)\n .next(keys => {\n keys.forEach(key => this.orphanedDocuments.add(key));\n })\n .next(() => cache.removeTargetData(txn, targetData));\n }\n\n onTransactionStarted(): void {\n this._orphanedDocuments = new Set();\n }\n\n onTransactionCommitted(\n txn: PersistenceTransaction\n ): PersistencePromise {\n // Remove newly orphaned documents.\n const cache = this.persistence.getRemoteDocumentCache();\n const changeBuffer = cache.newChangeBuffer();\n return PersistencePromise.forEach(\n this.orphanedDocuments,\n (key: DocumentKey) => {\n return this.isReferenced(txn, key).next(isReferenced => {\n if (!isReferenced) {\n changeBuffer.removeEntry(key);\n }\n });\n }\n ).next(() => {\n this._orphanedDocuments = null;\n return changeBuffer.apply(txn);\n });\n }\n\n updateLimboDocument(\n txn: PersistenceTransaction,\n key: DocumentKey\n ): PersistencePromise {\n return this.isReferenced(txn, key).next(isReferenced => {\n if (isReferenced) {\n this.orphanedDocuments.delete(key);\n } else {\n this.orphanedDocuments.add(key);\n }\n });\n }\n\n documentSize(doc: MaybeDocument): number {\n // For eager GC, we don't care about the document size, there are no size thresholds.\n return 0;\n }\n\n private isReferenced(\n txn: PersistenceTransaction,\n key: DocumentKey\n ): PersistencePromise {\n return PersistencePromise.or([\n () =>\n PersistencePromise.resolve(this.localViewReferences.containsKey(key)),\n () => this.persistence.getTargetCache().containsKey(txn, key),\n () => this.persistence.mutationQueuesContainKey(txn, key)\n ]);\n }\n}\n\nexport class MemoryLruDelegate implements ReferenceDelegate, LruDelegate {\n private orphanedSequenceNumbers: ObjectMap<\n DocumentKey,\n ListenSequenceNumber\n > = new ObjectMap(\n k => encodeResourcePath(k.path),\n (l, r) => l.isEqual(r)\n );\n\n readonly garbageCollector: LruGarbageCollector;\n\n constructor(\n private readonly persistence: MemoryPersistence,\n lruParams: LruParams\n ) {\n this.garbageCollector = new LruGarbageCollector(this, lruParams);\n }\n\n // No-ops, present so memory persistence doesn't have to care which delegate\n // it has.\n onTransactionStarted(): void {}\n\n onTransactionCommitted(\n txn: PersistenceTransaction\n ): PersistencePromise {\n return PersistencePromise.resolve();\n }\n\n forEachTarget(\n txn: PersistenceTransaction,\n f: (q: TargetData) => void\n ): PersistencePromise {\n return this.persistence.getTargetCache().forEachTarget(txn, f);\n }\n\n getSequenceNumberCount(\n txn: PersistenceTransaction\n ): PersistencePromise {\n const docCountPromise = this.orphanedDocumentCount(txn);\n const targetCountPromise = this.persistence\n .getTargetCache()\n .getTargetCount(txn);\n return targetCountPromise.next(targetCount =>\n docCountPromise.next(docCount => targetCount + docCount)\n );\n }\n\n private orphanedDocumentCount(\n txn: PersistenceTransaction\n ): PersistencePromise {\n let orphanedCount = 0;\n return this.forEachOrphanedDocumentSequenceNumber(txn, _ => {\n orphanedCount++;\n }).next(() => orphanedCount);\n }\n\n forEachOrphanedDocumentSequenceNumber(\n txn: PersistenceTransaction,\n f: (sequenceNumber: ListenSequenceNumber) => void\n ): PersistencePromise {\n return PersistencePromise.forEach(\n this.orphanedSequenceNumbers,\n (key, sequenceNumber) => {\n // Pass in the exact sequence number as the upper bound so we know it won't be pinned by\n // being too recent.\n return this.isPinned(txn, key, sequenceNumber).next(isPinned => {\n if (!isPinned) {\n return f(sequenceNumber);\n } else {\n return PersistencePromise.resolve();\n }\n });\n }\n );\n }\n\n removeTargets(\n txn: PersistenceTransaction,\n upperBound: ListenSequenceNumber,\n activeTargetIds: ActiveTargets\n ): PersistencePromise {\n return this.persistence\n .getTargetCache()\n .removeTargets(txn, upperBound, activeTargetIds);\n }\n\n removeOrphanedDocuments(\n txn: PersistenceTransaction,\n upperBound: ListenSequenceNumber\n ): PersistencePromise {\n let count = 0;\n const cache = this.persistence.getRemoteDocumentCache();\n const changeBuffer = cache.newChangeBuffer();\n const p = cache.forEachDocumentKey(txn, key => {\n return this.isPinned(txn, key, upperBound).next(isPinned => {\n if (!isPinned) {\n count++;\n changeBuffer.removeEntry(key);\n }\n });\n });\n return p.next(() => changeBuffer.apply(txn)).next(() => count);\n }\n\n markPotentiallyOrphaned(\n txn: PersistenceTransaction,\n key: DocumentKey\n ): PersistencePromise {\n this.orphanedSequenceNumbers.set(key, txn.currentSequenceNumber);\n return PersistencePromise.resolve();\n }\n\n removeTarget(\n txn: PersistenceTransaction,\n targetData: TargetData\n ): PersistencePromise {\n const updated = targetData.withSequenceNumber(txn.currentSequenceNumber);\n return this.persistence.getTargetCache().updateTargetData(txn, updated);\n }\n\n addReference(\n txn: PersistenceTransaction,\n targetId: TargetId,\n key: DocumentKey\n ): PersistencePromise {\n this.orphanedSequenceNumbers.set(key, txn.currentSequenceNumber);\n return PersistencePromise.resolve();\n }\n\n removeReference(\n txn: PersistenceTransaction,\n targetId: TargetId,\n key: DocumentKey\n ): PersistencePromise {\n this.orphanedSequenceNumbers.set(key, txn.currentSequenceNumber);\n return PersistencePromise.resolve();\n }\n\n updateLimboDocument(\n txn: PersistenceTransaction,\n key: DocumentKey\n ): PersistencePromise {\n this.orphanedSequenceNumbers.set(key, txn.currentSequenceNumber);\n return PersistencePromise.resolve();\n }\n\n documentSize(maybeDoc: MaybeDocument): number {\n let documentSize = maybeDoc.key.toString().length;\n if (maybeDoc instanceof Document) {\n documentSize += estimateByteSize(maybeDoc.toProto());\n }\n return documentSize;\n }\n\n private isPinned(\n txn: PersistenceTransaction,\n key: DocumentKey,\n upperBound: ListenSequenceNumber\n ): PersistencePromise {\n return PersistencePromise.or([\n () => this.persistence.mutationQueuesContainKey(txn, key),\n () => this.persistence.getTargetCache().containsKey(txn, key),\n () => {\n const orphanedAt = this.orphanedSequenceNumbers.get(key);\n return PersistencePromise.resolve(\n orphanedAt !== undefined && orphanedAt > upperBound\n );\n }\n ]);\n }\n\n getCacheSize(txn: PersistenceTransaction): PersistencePromise {\n return this.persistence.getRemoteDocumentCache().getSize(txn);\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { debugAssert } from '../util/assert';\nimport { FirestoreError } from '../util/error';\n\nimport { Stream } from './connection';\n\n/**\n * Provides a simple helper class that implements the Stream interface to\n * bridge to other implementations that are streams but do not implement the\n * interface. The stream callbacks are invoked with the callOn... methods.\n */\nexport class StreamBridge implements Stream {\n private wrappedOnOpen: (() => void) | undefined;\n private wrappedOnClose: ((err?: FirestoreError) => void) | undefined;\n private wrappedOnMessage: ((msg: O) => void) | undefined;\n\n private sendFn: (msg: I) => void;\n private closeFn: () => void;\n\n constructor(args: { sendFn: (msg: I) => void; closeFn: () => void }) {\n this.sendFn = args.sendFn;\n this.closeFn = args.closeFn;\n }\n\n onOpen(callback: () => void): void {\n debugAssert(!this.wrappedOnOpen, 'Called onOpen on stream twice!');\n this.wrappedOnOpen = callback;\n }\n\n onClose(callback: (err?: FirestoreError) => void): void {\n debugAssert(!this.wrappedOnClose, 'Called onClose on stream twice!');\n this.wrappedOnClose = callback;\n }\n\n onMessage(callback: (msg: O) => void): void {\n debugAssert(!this.wrappedOnMessage, 'Called onMessage on stream twice!');\n this.wrappedOnMessage = callback;\n }\n\n close(): void {\n this.closeFn();\n }\n\n send(msg: I): void {\n this.sendFn(msg);\n }\n\n callOnOpen(): void {\n debugAssert(\n this.wrappedOnOpen !== undefined,\n 'Cannot call onOpen because no callback was set'\n );\n this.wrappedOnOpen();\n }\n\n callOnClose(err?: FirestoreError): void {\n debugAssert(\n this.wrappedOnClose !== undefined,\n 'Cannot call onClose because no callback was set'\n );\n this.wrappedOnClose(err);\n }\n\n callOnMessage(msg: O): void {\n debugAssert(\n this.wrappedOnMessage !== undefined,\n 'Cannot call onMessage because no callback was set'\n );\n this.wrappedOnMessage(msg);\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n createWebChannelTransport,\n ErrorCode,\n EventType,\n WebChannel,\n WebChannelError,\n WebChannelOptions,\n XhrIo\n} from '@firebase/webchannel-wrapper';\n\nimport {\n isBrowserExtension,\n isElectron,\n isIE,\n isMobileCordova,\n isReactNative,\n isUWP\n} from '@firebase/util';\n\nimport { Token } from '../../api/credentials';\nimport { DatabaseId, DatabaseInfo } from '../../core/database_info';\nimport { SDK_VERSION } from '../../core/version';\nimport { Connection, Stream } from '../../remote/connection';\nimport {\n mapCodeFromRpcStatus,\n mapCodeFromHttpResponseErrorStatus\n} from '../../remote/rpc_error';\nimport { StreamBridge } from '../../remote/stream_bridge';\nimport { debugAssert, fail, hardAssert } from '../../util/assert';\nimport { Code, FirestoreError } from '../../util/error';\nimport { logDebug, logWarn } from '../../util/log';\nimport { Indexable } from '../../util/misc';\nimport { Rejecter, Resolver } from '../../util/promise';\nimport { StringMap } from '../../util/types';\n\nconst LOG_TAG = 'Connection';\n\nconst RPC_STREAM_SERVICE = 'google.firestore.v1.Firestore';\nconst RPC_URL_VERSION = 'v1';\n\n/**\n * Maps RPC names to the corresponding REST endpoint name.\n * Uses Object Literal notation to avoid renaming.\n */\nconst RPC_NAME_REST_MAPPING: { [key: string]: string } = {};\nRPC_NAME_REST_MAPPING['BatchGetDocuments'] = 'batchGet';\nRPC_NAME_REST_MAPPING['Commit'] = 'commit';\n\n// TODO(b/38203344): The SDK_VERSION is set independently from Firebase because\n// we are doing out-of-band releases. Once we release as part of Firebase, we\n// should use the Firebase version instead.\nconst X_GOOG_API_CLIENT_VALUE = 'gl-js/ fire/' + SDK_VERSION;\n\nconst XHR_TIMEOUT_SECS = 15;\n\nexport class WebChannelConnection implements Connection {\n private readonly databaseId: DatabaseId;\n private readonly baseUrl: string;\n private readonly forceLongPolling: boolean;\n\n constructor(info: DatabaseInfo) {\n this.databaseId = info.databaseId;\n const proto = info.ssl ? 'https' : 'http';\n this.baseUrl = proto + '://' + info.host;\n this.forceLongPolling = info.forceLongPolling;\n }\n\n /**\n * Modifies the headers for a request, adding any authorization token if\n * present and any additional headers for the request.\n */\n private modifyHeadersForRequest(\n headers: StringMap,\n token: Token | null\n ): void {\n if (token) {\n for (const header in token.authHeaders) {\n if (token.authHeaders.hasOwnProperty(header)) {\n headers[header] = token.authHeaders[header];\n }\n }\n }\n headers['X-Goog-Api-Client'] = X_GOOG_API_CLIENT_VALUE;\n }\n\n invokeRPC(\n rpcName: string,\n request: Req,\n token: Token | null\n ): Promise {\n const url = this.makeUrl(rpcName);\n\n return new Promise((resolve: Resolver, reject: Rejecter) => {\n const xhr = new XhrIo();\n xhr.listenOnce(EventType.COMPLETE, () => {\n try {\n switch (xhr.getLastErrorCode()) {\n case ErrorCode.NO_ERROR:\n const json = xhr.getResponseJson() as Resp;\n logDebug(LOG_TAG, 'XHR received:', JSON.stringify(json));\n resolve(json);\n break;\n case ErrorCode.TIMEOUT:\n logDebug(LOG_TAG, 'RPC \"' + rpcName + '\" timed out');\n reject(\n new FirestoreError(Code.DEADLINE_EXCEEDED, 'Request time out')\n );\n break;\n case ErrorCode.HTTP_ERROR:\n const status = xhr.getStatus();\n logDebug(\n LOG_TAG,\n 'RPC \"' + rpcName + '\" failed with status:',\n status,\n 'response text:',\n xhr.getResponseText()\n );\n if (status > 0) {\n const responseError = (xhr.getResponseJson() as WebChannelError)\n .error;\n if (\n !!responseError &&\n !!responseError.status &&\n !!responseError.message\n ) {\n const firestoreErrorCode = mapCodeFromHttpResponseErrorStatus(\n responseError.status\n );\n reject(\n new FirestoreError(\n firestoreErrorCode,\n responseError.message\n )\n );\n } else {\n reject(\n new FirestoreError(\n Code.UNKNOWN,\n 'Server responded with status ' + xhr.getStatus()\n )\n );\n }\n } else {\n // If we received an HTTP_ERROR but there's no status code,\n // it's most probably a connection issue\n logDebug(LOG_TAG, 'RPC \"' + rpcName + '\" failed');\n reject(\n new FirestoreError(Code.UNAVAILABLE, 'Connection failed.')\n );\n }\n break;\n default:\n fail(\n 'RPC \"' +\n rpcName +\n '\" failed with unanticipated ' +\n 'webchannel error ' +\n xhr.getLastErrorCode() +\n ': ' +\n xhr.getLastError() +\n ', giving up.'\n );\n }\n } finally {\n logDebug(LOG_TAG, 'RPC \"' + rpcName + '\" completed.');\n }\n });\n\n // The database field is already encoded in URL. Specifying it again in\n // the body is not necessary in production, and will cause duplicate field\n // errors in the Firestore Emulator. Let's remove it.\n const jsonObj = ({ ...request } as unknown) as Indexable;\n delete jsonObj.database;\n\n const requestString = JSON.stringify(jsonObj);\n logDebug(LOG_TAG, 'XHR sending: ', url + ' ' + requestString);\n // Content-Type: text/plain will avoid preflight requests which might\n // mess with CORS and redirects by proxies. If we add custom headers\n // we will need to change this code to potentially use the\n // $httpOverwrite parameter supported by ESF to avoid\n // triggering preflight requests.\n const headers: StringMap = { 'Content-Type': 'text/plain' };\n\n this.modifyHeadersForRequest(headers, token);\n\n xhr.send(url, 'POST', requestString, headers, XHR_TIMEOUT_SECS);\n });\n }\n\n invokeStreamingRPC(\n rpcName: string,\n request: Req,\n token: Token | null\n ): Promise {\n // The REST API automatically aggregates all of the streamed results, so we\n // can just use the normal invoke() method.\n return this.invokeRPC(rpcName, request, token);\n }\n\n openStream(\n rpcName: string,\n token: Token | null\n ): Stream {\n const urlParts = [\n this.baseUrl,\n '/',\n RPC_STREAM_SERVICE,\n '/',\n rpcName,\n '/channel'\n ];\n const webchannelTransport = createWebChannelTransport();\n const request: WebChannelOptions = {\n // Required for backend stickiness, routing behavior is based on this\n // parameter.\n httpSessionIdParam: 'gsessionid',\n initMessageHeaders: {},\n messageUrlParams: {\n // This param is used to improve routing and project isolation by the\n // backend and must be included in every request.\n database: `projects/${this.databaseId.projectId}/databases/${this.databaseId.database}`\n },\n sendRawJson: true,\n supportsCrossDomainXhr: true,\n internalChannelParams: {\n // Override the default timeout (randomized between 10-20 seconds) since\n // a large write batch on a slow internet connection may take a long\n // time to send to the backend. Rather than have WebChannel impose a\n // tight timeout which could lead to infinite timeouts and retries, we\n // set it very large (5-10 minutes) and rely on the browser's builtin\n // timeouts to kick in if the request isn't working.\n forwardChannelRequestTimeoutMs: 10 * 60 * 1000\n },\n forceLongPolling: this.forceLongPolling\n };\n\n this.modifyHeadersForRequest(request.initMessageHeaders!, token);\n\n // Sending the custom headers we just added to request.initMessageHeaders\n // (Authorization, etc.) will trigger the browser to make a CORS preflight\n // request because the XHR will no longer meet the criteria for a \"simple\"\n // CORS request:\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Simple_requests\n //\n // Therefore to avoid the CORS preflight request (an extra network\n // roundtrip), we use the httpHeadersOverwriteParam option to specify that\n // the headers should instead be encoded into a special \"$httpHeaders\" query\n // parameter, which is recognized by the webchannel backend. This is\n // formally defined here:\n // https://github.com/google/closure-library/blob/b0e1815b13fb92a46d7c9b3c30de5d6a396a3245/closure/goog/net/rpc/httpcors.js#L32\n //\n // TODO(b/145624756): There is a backend bug where $httpHeaders isn't respected if the request\n // doesn't have an Origin header. So we have to exclude a few browser environments that are\n // known to (sometimes) not include an Origin. See\n // https://github.com/firebase/firebase-js-sdk/issues/1491.\n if (\n !isMobileCordova() &&\n !isReactNative() &&\n !isElectron() &&\n !isIE() &&\n !isUWP() &&\n !isBrowserExtension()\n ) {\n request.httpHeadersOverwriteParam = '$httpHeaders';\n }\n\n const url = urlParts.join('');\n logDebug(LOG_TAG, 'Creating WebChannel: ' + url + ' ' + request);\n const channel = webchannelTransport.createWebChannel(url, request);\n\n // WebChannel supports sending the first message with the handshake - saving\n // a network round trip. However, it will have to call send in the same\n // JS event loop as open. In order to enforce this, we delay actually\n // opening the WebChannel until send is called. Whether we have called\n // open is tracked with this variable.\n let opened = false;\n\n // A flag to determine whether the stream was closed (by us or through an\n // error/close event) to avoid delivering multiple close events or sending\n // on a closed stream\n let closed = false;\n\n const streamBridge = new StreamBridge({\n sendFn: (msg: Req) => {\n if (!closed) {\n if (!opened) {\n logDebug(LOG_TAG, 'Opening WebChannel transport.');\n channel.open();\n opened = true;\n }\n logDebug(LOG_TAG, 'WebChannel sending:', msg);\n channel.send(msg);\n } else {\n logDebug(LOG_TAG, 'Not sending because WebChannel is closed:', msg);\n }\n },\n closeFn: () => channel.close()\n });\n\n // Closure events are guarded and exceptions are swallowed, so catch any\n // exception and rethrow using a setTimeout so they become visible again.\n // Note that eventually this function could go away if we are confident\n // enough the code is exception free.\n const unguardedEventListen = (\n type: string,\n fn: (param?: T) => void\n ): void => {\n // TODO(dimond): closure typing seems broken because WebChannel does\n // not implement goog.events.Listenable\n channel.listen(type, (param: unknown) => {\n try {\n fn(param as T);\n } catch (e) {\n setTimeout(() => {\n throw e;\n }, 0);\n }\n });\n };\n\n unguardedEventListen(WebChannel.EventType.OPEN, () => {\n if (!closed) {\n logDebug(LOG_TAG, 'WebChannel transport opened.');\n }\n });\n\n unguardedEventListen(WebChannel.EventType.CLOSE, () => {\n if (!closed) {\n closed = true;\n logDebug(LOG_TAG, 'WebChannel transport closed');\n streamBridge.callOnClose();\n }\n });\n\n unguardedEventListen(WebChannel.EventType.ERROR, err => {\n if (!closed) {\n closed = true;\n logWarn(LOG_TAG, 'WebChannel transport errored:', err);\n streamBridge.callOnClose(\n new FirestoreError(\n Code.UNAVAILABLE,\n 'The operation could not be completed'\n )\n );\n }\n });\n\n // WebChannel delivers message events as array. If batching is not enabled\n // (it's off by default) each message will be delivered alone, resulting in\n // a single element array.\n interface WebChannelResponse {\n data: Resp[];\n }\n\n unguardedEventListen(\n WebChannel.EventType.MESSAGE,\n msg => {\n if (!closed) {\n const msgData = msg!.data[0];\n hardAssert(!!msgData, 'Got a webchannel message without data.');\n // TODO(b/35143891): There is a bug in One Platform that caused errors\n // (and only errors) to be wrapped in an extra array. To be forward\n // compatible with the bug we need to check either condition. The latter\n // can be removed once the fix has been rolled out.\n // Use any because msgData.error is not typed.\n const msgDataOrError: WebChannelError | object = msgData;\n const error =\n msgDataOrError.error ||\n (msgDataOrError as WebChannelError[])[0]?.error;\n if (error) {\n logDebug(LOG_TAG, 'WebChannel received error:', error);\n // error.status will be a string like 'OK' or 'NOT_FOUND'.\n const status: string = error.status;\n let code = mapCodeFromRpcStatus(status);\n let message = error.message;\n if (code === undefined) {\n code = Code.INTERNAL;\n message =\n 'Unknown error status: ' +\n status +\n ' with message ' +\n error.message;\n }\n // Mark closed so no further events are propagated\n closed = true;\n streamBridge.callOnClose(new FirestoreError(code, message));\n channel.close();\n } else {\n logDebug(LOG_TAG, 'WebChannel received:', msgData);\n streamBridge.callOnMessage(msgData);\n }\n }\n }\n );\n\n setTimeout(() => {\n // Technically we could/should wait for the WebChannel opened event,\n // but because we want to send the first message with the WebChannel\n // handshake we pretend the channel opened here (asynchronously), and\n // then delay the actual open until the first message is sent.\n streamBridge.callOnOpen();\n }, 0);\n return streamBridge;\n }\n\n // visible for testing\n makeUrl(rpcName: string): string {\n const urlRpcName = RPC_NAME_REST_MAPPING[rpcName];\n debugAssert(\n urlRpcName !== undefined,\n 'Unknown REST mapping for: ' + rpcName\n );\n return (\n this.baseUrl +\n '/' +\n RPC_URL_VERSION +\n '/projects/' +\n this.databaseId.projectId +\n '/databases/' +\n this.databaseId.database +\n '/documents:' +\n urlRpcName\n );\n }\n}\n","/**\n * @license\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { logDebug } from '../../util/log';\nimport {\n ConnectivityMonitor,\n ConnectivityMonitorCallback,\n NetworkStatus\n} from '../../remote/connectivity_monitor';\n\n// References to `window` are guarded by BrowserConnectivityMonitor.isAvailable()\n/* eslint-disable no-restricted-globals */\n\nconst LOG_TAG = 'ConnectivityMonitor';\n\n/**\n * Browser implementation of ConnectivityMonitor.\n */\nexport class BrowserConnectivityMonitor implements ConnectivityMonitor {\n private readonly networkAvailableListener = (): void =>\n this.onNetworkAvailable();\n private readonly networkUnavailableListener = (): void =>\n this.onNetworkUnavailable();\n private callbacks: ConnectivityMonitorCallback[] = [];\n\n constructor() {\n this.configureNetworkMonitoring();\n }\n\n addCallback(callback: (status: NetworkStatus) => void): void {\n this.callbacks.push(callback);\n }\n\n shutdown(): void {\n window.removeEventListener('online', this.networkAvailableListener);\n window.removeEventListener('offline', this.networkUnavailableListener);\n }\n\n private configureNetworkMonitoring(): void {\n window.addEventListener('online', this.networkAvailableListener);\n window.addEventListener('offline', this.networkUnavailableListener);\n }\n\n private onNetworkAvailable(): void {\n logDebug(LOG_TAG, 'Network connectivity changed: AVAILABLE');\n for (const callback of this.callbacks) {\n callback(NetworkStatus.AVAILABLE);\n }\n }\n\n private onNetworkUnavailable(): void {\n logDebug(LOG_TAG, 'Network connectivity changed: UNAVAILABLE');\n for (const callback of this.callbacks) {\n callback(NetworkStatus.UNAVAILABLE);\n }\n }\n\n // TODO(chenbrian): Consider passing in window either into this component or\n // here for testing via FakeWindow.\n /** Checks that all used attributes of window are available. */\n static isAvailable(): boolean {\n return (\n typeof window !== 'undefined' &&\n window.addEventListener !== undefined &&\n window.removeEventListener !== undefined\n );\n }\n}\n","/**\n * @license\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ConnectivityMonitor, NetworkStatus } from './connectivity_monitor';\n\nexport class NoopConnectivityMonitor implements ConnectivityMonitor {\n addCallback(callback: (status: NetworkStatus) => void): void {\n // No-op.\n }\n\n shutdown(): void {\n // No-op.\n }\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ClientId,\n MemorySharedClientState,\n SharedClientState,\n WebStorageSharedClientState\n} from '../local/shared_client_state';\nimport {\n LocalStore,\n MultiTabLocalStore,\n newLocalStore,\n newMultiTabLocalStore\n} from '../local/local_store';\nimport {\n MultiTabSyncEngine,\n newMultiTabSyncEngine,\n newSyncEngine,\n SyncEngine\n} from './sync_engine';\nimport { RemoteStore } from '../remote/remote_store';\nimport { EventManager } from './event_manager';\nimport { AsyncQueue } from '../util/async_queue';\nimport { DatabaseId, DatabaseInfo } from './database_info';\nimport { Datastore } from '../remote/datastore';\nimport { User } from '../auth/user';\nimport { PersistenceSettings } from './firestore_client';\nimport { debugAssert } from '../util/assert';\nimport { GarbageCollectionScheduler, Persistence } from '../local/persistence';\nimport { Code, FirestoreError } from '../util/error';\nimport { OnlineStateSource } from './types';\nimport { LruParams, LruScheduler } from '../local/lru_garbage_collector';\nimport { IndexFreeQueryEngine } from '../local/index_free_query_engine';\nimport {\n indexedDbStoragePrefix,\n IndexedDbPersistence,\n indexedDbClearPersistence\n} from '../local/indexeddb_persistence';\nimport {\n MemoryEagerDelegate,\n MemoryPersistence\n} from '../local/memory_persistence';\nimport { newConnectivityMonitor } from '../platform/connection';\nimport { newSerializer } from '../platform/serializer';\nimport { getDocument, getWindow } from '../platform/dom';\n\nconst MEMORY_ONLY_PERSISTENCE_ERROR_MESSAGE =\n 'You are using the memory-only build of Firestore. Persistence support is ' +\n 'only available via the @firebase/firestore bundle or the ' +\n 'firebase-firestore.js build.';\n\nexport interface ComponentConfiguration {\n asyncQueue: AsyncQueue;\n databaseInfo: DatabaseInfo;\n datastore: Datastore;\n clientId: ClientId;\n initialUser: User;\n maxConcurrentLimboResolutions: number;\n persistenceSettings: PersistenceSettings;\n}\n\n/**\n * Initializes and wires up all core components for Firestore. Implementations\n * override `initialize()` to provide all components.\n */\nexport interface ComponentProvider {\n persistence: Persistence;\n sharedClientState: SharedClientState;\n localStore: LocalStore;\n syncEngine: SyncEngine;\n gcScheduler: GarbageCollectionScheduler | null;\n remoteStore: RemoteStore;\n eventManager: EventManager;\n\n initialize(cfg: ComponentConfiguration): Promise;\n\n clearPersistence(\n databaseId: DatabaseId,\n persistenceKey: string\n ): Promise;\n}\n\n/**\n * Provides all components needed for Firestore with in-memory persistence.\n * Uses EagerGC garbage collection.\n */\nexport class MemoryComponentProvider implements ComponentProvider {\n persistence!: Persistence;\n sharedClientState!: SharedClientState;\n localStore!: LocalStore;\n syncEngine!: SyncEngine;\n gcScheduler!: GarbageCollectionScheduler | null;\n remoteStore!: RemoteStore;\n eventManager!: EventManager;\n\n async initialize(cfg: ComponentConfiguration): Promise {\n this.sharedClientState = this.createSharedClientState(cfg);\n this.persistence = this.createPersistence(cfg);\n await this.persistence.start();\n this.gcScheduler = this.createGarbageCollectionScheduler(cfg);\n this.localStore = this.createLocalStore(cfg);\n this.remoteStore = this.createRemoteStore(cfg);\n this.syncEngine = this.createSyncEngine(cfg);\n this.eventManager = this.createEventManager(cfg);\n\n this.sharedClientState.onlineStateHandler = onlineState =>\n this.syncEngine.applyOnlineStateChange(\n onlineState,\n OnlineStateSource.SharedClientState\n );\n this.remoteStore.syncEngine = this.syncEngine;\n\n await this.localStore.start();\n await this.sharedClientState.start();\n await this.remoteStore.start();\n\n await this.remoteStore.applyPrimaryState(this.syncEngine.isPrimaryClient);\n }\n\n createEventManager(cfg: ComponentConfiguration): EventManager {\n return new EventManager(this.syncEngine);\n }\n\n createGarbageCollectionScheduler(\n cfg: ComponentConfiguration\n ): GarbageCollectionScheduler | null {\n return null;\n }\n\n createLocalStore(cfg: ComponentConfiguration): LocalStore {\n return newLocalStore(\n this.persistence,\n new IndexFreeQueryEngine(),\n cfg.initialUser\n );\n }\n\n createPersistence(cfg: ComponentConfiguration): Persistence {\n if (cfg.persistenceSettings.durable) {\n throw new FirestoreError(\n Code.FAILED_PRECONDITION,\n MEMORY_ONLY_PERSISTENCE_ERROR_MESSAGE\n );\n }\n return new MemoryPersistence(MemoryEagerDelegate.factory);\n }\n\n createRemoteStore(cfg: ComponentConfiguration): RemoteStore {\n return new RemoteStore(\n this.localStore,\n cfg.datastore,\n cfg.asyncQueue,\n onlineState =>\n this.syncEngine.applyOnlineStateChange(\n onlineState,\n OnlineStateSource.RemoteStore\n ),\n newConnectivityMonitor()\n );\n }\n\n createSharedClientState(cfg: ComponentConfiguration): SharedClientState {\n return new MemorySharedClientState();\n }\n\n createSyncEngine(cfg: ComponentConfiguration): SyncEngine {\n return newSyncEngine(\n this.localStore,\n this.remoteStore,\n cfg.datastore,\n this.sharedClientState,\n cfg.initialUser,\n cfg.maxConcurrentLimboResolutions\n );\n }\n\n clearPersistence(\n databaseId: DatabaseId,\n persistenceKey: string\n ): Promise {\n throw new FirestoreError(\n Code.FAILED_PRECONDITION,\n MEMORY_ONLY_PERSISTENCE_ERROR_MESSAGE\n );\n }\n}\n\n/**\n * Provides all components needed for Firestore with IndexedDB persistence.\n */\nexport class IndexedDbComponentProvider extends MemoryComponentProvider {\n persistence!: IndexedDbPersistence;\n\n createLocalStore(cfg: ComponentConfiguration): LocalStore {\n return newLocalStore(\n this.persistence,\n new IndexFreeQueryEngine(),\n cfg.initialUser\n );\n }\n\n createSyncEngine(cfg: ComponentConfiguration): SyncEngine {\n return newSyncEngine(\n this.localStore,\n this.remoteStore,\n cfg.datastore,\n this.sharedClientState,\n cfg.initialUser,\n cfg.maxConcurrentLimboResolutions\n );\n }\n\n createGarbageCollectionScheduler(\n cfg: ComponentConfiguration\n ): GarbageCollectionScheduler | null {\n const garbageCollector = this.persistence.referenceDelegate\n .garbageCollector;\n return new LruScheduler(garbageCollector, cfg.asyncQueue);\n }\n\n createPersistence(cfg: ComponentConfiguration): Persistence {\n debugAssert(\n cfg.persistenceSettings.durable,\n 'Can only start durable persistence'\n );\n\n const persistenceKey = indexedDbStoragePrefix(\n cfg.databaseInfo.databaseId,\n cfg.databaseInfo.persistenceKey\n );\n const serializer = newSerializer(cfg.databaseInfo.databaseId);\n return new IndexedDbPersistence(\n cfg.persistenceSettings.synchronizeTabs,\n persistenceKey,\n cfg.clientId,\n LruParams.withCacheSize(cfg.persistenceSettings.cacheSizeBytes),\n cfg.asyncQueue,\n getWindow(),\n getDocument(),\n serializer,\n this.sharedClientState,\n cfg.persistenceSettings.forceOwningTab\n );\n }\n\n createSharedClientState(cfg: ComponentConfiguration): SharedClientState {\n return new MemorySharedClientState();\n }\n\n clearPersistence(\n databaseId: DatabaseId,\n persistenceKey: string\n ): Promise {\n return indexedDbClearPersistence(\n indexedDbStoragePrefix(databaseId, persistenceKey)\n );\n }\n}\n\n/**\n * Provides all components needed for Firestore with multi-tab IndexedDB\n * persistence.\n *\n * In the legacy client, this provider is used to provide both multi-tab and\n * non-multi-tab persistence since we cannot tell at build time whether\n * `synchronizeTabs` will be enabled.\n */\nexport class MultiTabIndexedDbComponentProvider extends IndexedDbComponentProvider {\n localStore!: MultiTabLocalStore;\n syncEngine!: MultiTabSyncEngine;\n\n async initialize(cfg: ComponentConfiguration): Promise {\n await super.initialize(cfg);\n\n // NOTE: This will immediately call the listener, so we make sure to\n // set it after localStore / remoteStore are started.\n await this.persistence.setPrimaryStateListener(async isPrimary => {\n await (this.syncEngine as MultiTabSyncEngine).applyPrimaryState(\n isPrimary\n );\n if (this.gcScheduler) {\n if (isPrimary && !this.gcScheduler.started) {\n this.gcScheduler.start(this.localStore);\n } else if (!isPrimary) {\n this.gcScheduler.stop();\n }\n }\n });\n }\n\n createLocalStore(cfg: ComponentConfiguration): LocalStore {\n return newMultiTabLocalStore(\n this.persistence,\n new IndexFreeQueryEngine(),\n cfg.initialUser\n );\n }\n\n createSyncEngine(cfg: ComponentConfiguration): SyncEngine {\n const syncEngine = newMultiTabSyncEngine(\n this.localStore,\n this.remoteStore,\n cfg.datastore,\n this.sharedClientState,\n cfg.initialUser,\n cfg.maxConcurrentLimboResolutions\n );\n if (this.sharedClientState instanceof WebStorageSharedClientState) {\n this.sharedClientState.syncEngine = syncEngine;\n }\n return syncEngine;\n }\n\n createSharedClientState(cfg: ComponentConfiguration): SharedClientState {\n if (\n cfg.persistenceSettings.durable &&\n cfg.persistenceSettings.synchronizeTabs\n ) {\n const window = getWindow();\n if (!WebStorageSharedClientState.isAvailable(window)) {\n throw new FirestoreError(\n Code.UNIMPLEMENTED,\n 'IndexedDB persistence is only available on platforms that support LocalStorage.'\n );\n }\n const persistenceKey = indexedDbStoragePrefix(\n cfg.databaseInfo.databaseId,\n cfg.databaseInfo.persistenceKey\n );\n return new WebStorageSharedClientState(\n window,\n cfg.asyncQueue,\n persistenceKey,\n cfg.clientId,\n cfg.initialUser\n );\n }\n return new MemorySharedClientState();\n }\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { WebChannelConnection } from './webchannel_connection';\nimport { DatabaseInfo } from '../../core/database_info';\nimport { Connection } from '../../remote/connection';\nimport { ConnectivityMonitor } from '../../remote/connectivity_monitor';\nimport { BrowserConnectivityMonitor } from './connectivity_monitor';\nimport { NoopConnectivityMonitor } from '../../remote/connectivity_monitor_noop';\n\n/** Initializes the WebChannelConnection for the browser. */\nexport function newConnection(databaseInfo: DatabaseInfo): Promise {\n return Promise.resolve(new WebChannelConnection(databaseInfo));\n}\n\n/** Return the Platform-specific connectivity monitor. */\nexport function newConnectivityMonitor(): ConnectivityMonitor {\n if (BrowserConnectivityMonitor.isAvailable()) {\n return new BrowserConnectivityMonitor();\n } else {\n return new NoopConnectivityMonitor();\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CredentialsProvider } from '../api/credentials';\nimport { User } from '../auth/user';\nimport { LocalStore } from '../local/local_store';\nimport { GarbageCollectionScheduler, Persistence } from '../local/persistence';\nimport { Document, NoDocument } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport { Mutation } from '../model/mutation';\nimport { newDatastore } from '../remote/datastore';\nimport { RemoteStore } from '../remote/remote_store';\nimport { AsyncQueue, wrapInUserErrorIfRecoverable } from '../util/async_queue';\nimport { Code, FirestoreError } from '../util/error';\nimport { logDebug } from '../util/log';\nimport { Deferred } from '../util/promise';\nimport {\n EventManager,\n ListenOptions,\n Observer,\n QueryListener\n} from './event_manager';\nimport { SyncEngine } from './sync_engine';\nimport { View } from './view';\n\nimport { SharedClientState } from '../local/shared_client_state';\nimport { AutoId } from '../util/misc';\nimport { DatabaseId, DatabaseInfo } from './database_info';\nimport { Query } from './query';\nimport { Transaction } from './transaction';\nimport { ViewSnapshot } from './view_snapshot';\nimport {\n ComponentProvider,\n MemoryComponentProvider\n} from './component_provider';\nimport { newConnection } from '../platform/connection';\nimport { newSerializer } from '../platform/serializer';\n\nconst LOG_TAG = 'FirestoreClient';\nconst MAX_CONCURRENT_LIMBO_RESOLUTIONS = 100;\n\n/** DOMException error code constants. */\nconst DOM_EXCEPTION_INVALID_STATE = 11;\nconst DOM_EXCEPTION_ABORTED = 20;\nconst DOM_EXCEPTION_QUOTA_EXCEEDED = 22;\n\nexport type PersistenceSettings =\n | {\n readonly durable: false;\n }\n | {\n readonly durable: true;\n readonly cacheSizeBytes: number;\n readonly synchronizeTabs: boolean;\n readonly forceOwningTab: boolean;\n };\n\n/**\n * FirestoreClient is a top-level class that constructs and owns all of the\n * pieces of the client SDK architecture. It is responsible for creating the\n * async queue that is shared by all of the other components in the system.\n */\nexport class FirestoreClient {\n // NOTE: These should technically have '|undefined' in the types, since\n // they're initialized asynchronously rather than in the constructor, but\n // given that all work is done on the async queue and we assert that\n // initialization completes before any other work is queued, we're cheating\n // with the types rather than littering the code with '!' or unnecessary\n // undefined checks.\n private databaseInfo!: DatabaseInfo;\n private eventMgr!: EventManager;\n private persistence!: Persistence;\n private localStore!: LocalStore;\n private remoteStore!: RemoteStore;\n private syncEngine!: SyncEngine;\n private gcScheduler!: GarbageCollectionScheduler | null;\n\n // PORTING NOTE: SharedClientState is only used for multi-tab web.\n private sharedClientState!: SharedClientState;\n\n private readonly clientId = AutoId.newId();\n\n constructor(\n private credentials: CredentialsProvider,\n /**\n * Asynchronous queue responsible for all of our internal processing. When\n * we get incoming work from the user (via public API) or the network\n * (incoming GRPC messages), we should always schedule onto this queue.\n * This ensures all of our work is properly serialized (e.g. we don't\n * start processing a new operation while the previous one is waiting for\n * an async I/O to complete).\n */\n private asyncQueue: AsyncQueue\n ) {}\n\n /**\n * Starts up the FirestoreClient, returning only whether or not enabling\n * persistence succeeded.\n *\n * The intent here is to \"do the right thing\" as far as users are concerned.\n * Namely, in cases where offline persistence is requested and possible,\n * enable it, but otherwise fall back to persistence disabled. For the most\n * part we expect this to succeed one way or the other so we don't expect our\n * users to actually wait on the firestore.enablePersistence Promise since\n * they generally won't care.\n *\n * Of course some users actually do care about whether or not persistence\n * was successfully enabled, so the Promise returned from this method\n * indicates this outcome.\n *\n * This presents a problem though: even before enablePersistence resolves or\n * rejects, users may have made calls to e.g. firestore.collection() which\n * means that the FirestoreClient in there will be available and will be\n * enqueuing actions on the async queue.\n *\n * Meanwhile any failure of an operation on the async queue causes it to\n * panic and reject any further work, on the premise that unhandled errors\n * are fatal.\n *\n * Consequently the fallback is handled internally here in start, and if the\n * fallback succeeds we signal success to the async queue even though the\n * start() itself signals failure.\n *\n * @param databaseInfo The connection information for the current instance.\n * @param componentProvider Provider that returns all core components.\n * @param persistenceSettings Settings object to configure offline\n * persistence.\n * @returns A deferred result indicating the user-visible result of enabling\n * offline persistence. This method will reject this if IndexedDB fails to\n * start for any reason. If usePersistence is false this is\n * unconditionally resolved.\n */\n start(\n databaseInfo: DatabaseInfo,\n componentProvider: ComponentProvider,\n persistenceSettings: PersistenceSettings\n ): Promise {\n this.verifyNotTerminated();\n\n this.databaseInfo = databaseInfo;\n\n // We defer our initialization until we get the current user from\n // setChangeListener(). We block the async queue until we got the initial\n // user and the initialization is completed. This will prevent any scheduled\n // work from happening before initialization is completed.\n //\n // If initializationDone resolved then the FirestoreClient is in a usable\n // state.\n const initializationDone = new Deferred();\n\n // If usePersistence is true, certain classes of errors while starting are\n // recoverable but only by falling back to persistence disabled.\n //\n // If there's an error in the first case but not in recovery we cannot\n // reject the promise blocking the async queue because this will cause the\n // async queue to panic.\n const persistenceResult = new Deferred();\n\n let initialized = false;\n this.credentials.setChangeListener(user => {\n if (!initialized) {\n initialized = true;\n\n logDebug(LOG_TAG, 'Initializing. user=', user.uid);\n\n return this.initializeComponents(\n componentProvider,\n persistenceSettings,\n user,\n persistenceResult\n ).then(initializationDone.resolve, initializationDone.reject);\n } else {\n this.asyncQueue.enqueueRetryable(() =>\n this.remoteStore.handleCredentialChange(user)\n );\n }\n });\n\n // Block the async queue until initialization is done\n this.asyncQueue.enqueueAndForget(() => {\n return initializationDone.promise;\n });\n\n // Return only the result of enabling persistence. Note that this does not\n // need to await the completion of initializationDone because the result of\n // this method should not reflect any other kind of failure to start.\n return persistenceResult.promise;\n }\n\n /** Enables the network connection and requeues all pending operations. */\n enableNetwork(): Promise {\n this.verifyNotTerminated();\n return this.asyncQueue.enqueue(() => {\n return this.syncEngine.enableNetwork();\n });\n }\n\n /**\n * Initializes persistent storage, attempting to use IndexedDB if\n * usePersistence is true or memory-only if false.\n *\n * If IndexedDB fails because it's already open in another tab or because the\n * platform can't possibly support our implementation then this method rejects\n * the persistenceResult and falls back on memory-only persistence.\n *\n * @param componentProvider The provider that provides all core componennts\n * for IndexedDB or memory-backed persistence\n * @param persistenceSettings Settings object to configure offline persistence\n * @param user The initial user\n * @param persistenceResult A deferred result indicating the user-visible\n * result of enabling offline persistence. This method will reject this if\n * IndexedDB fails to start for any reason. If usePersistence is false\n * this is unconditionally resolved.\n * @returns a Promise indicating whether or not initialization should\n * continue, i.e. that one of the persistence implementations actually\n * succeeded.\n */\n private async initializeComponents(\n componentProvider: ComponentProvider,\n persistenceSettings: PersistenceSettings,\n user: User,\n persistenceResult: Deferred\n ): Promise {\n try {\n // TODO(mrschmidt): Ideally, ComponentProvider would also initialize\n // Datastore (without duplicating the initializing logic once per\n // provider).\n\n const connection = await newConnection(this.databaseInfo);\n const serializer = newSerializer(this.databaseInfo.databaseId);\n const datastore = newDatastore(connection, this.credentials, serializer);\n\n await componentProvider.initialize({\n asyncQueue: this.asyncQueue,\n databaseInfo: this.databaseInfo,\n datastore,\n clientId: this.clientId,\n initialUser: user,\n maxConcurrentLimboResolutions: MAX_CONCURRENT_LIMBO_RESOLUTIONS,\n persistenceSettings\n });\n\n this.persistence = componentProvider.persistence;\n this.sharedClientState = componentProvider.sharedClientState;\n this.localStore = componentProvider.localStore;\n this.remoteStore = componentProvider.remoteStore;\n this.syncEngine = componentProvider.syncEngine;\n this.gcScheduler = componentProvider.gcScheduler;\n this.eventMgr = componentProvider.eventManager;\n\n // When a user calls clearPersistence() in one client, all other clients\n // need to be terminated to allow the delete to succeed.\n this.persistence.setDatabaseDeletedListener(async () => {\n await this.terminate();\n });\n\n persistenceResult.resolve();\n } catch (error) {\n // Regardless of whether or not the retry succeeds, from an user\n // perspective, offline persistence has failed.\n persistenceResult.reject(error);\n\n // An unknown failure on the first stage shuts everything down.\n if (!this.canFallback(error)) {\n throw error;\n }\n console.warn(\n 'Error enabling offline persistence. Falling back to' +\n ' persistence disabled: ' +\n error\n );\n return this.initializeComponents(\n new MemoryComponentProvider(),\n { durable: false },\n user,\n persistenceResult\n );\n }\n }\n\n /**\n * Decides whether the provided error allows us to gracefully disable\n * persistence (as opposed to crashing the client).\n */\n private canFallback(error: FirestoreError | DOMException): boolean {\n if (error.name === 'FirebaseError') {\n return (\n error.code === Code.FAILED_PRECONDITION ||\n error.code === Code.UNIMPLEMENTED\n );\n } else if (\n typeof DOMException !== 'undefined' &&\n error instanceof DOMException\n ) {\n // There are a few known circumstances where we can open IndexedDb but\n // trying to read/write will fail (e.g. quota exceeded). For\n // well-understood cases, we attempt to detect these and then gracefully\n // fall back to memory persistence.\n // NOTE: Rather than continue to add to this list, we could decide to\n // always fall back, with the risk that we might accidentally hide errors\n // representing actual SDK bugs.\n return (\n // When the browser is out of quota we could get either quota exceeded\n // or an aborted error depending on whether the error happened during\n // schema migration.\n error.code === DOM_EXCEPTION_QUOTA_EXCEEDED ||\n error.code === DOM_EXCEPTION_ABORTED ||\n // Firefox Private Browsing mode disables IndexedDb and returns\n // INVALID_STATE for any usage.\n error.code === DOM_EXCEPTION_INVALID_STATE\n );\n }\n\n return true;\n }\n\n /**\n * Checks that the client has not been terminated. Ensures that other methods on\n * this class cannot be called after the client is terminated.\n */\n private verifyNotTerminated(): void {\n if (this.asyncQueue.isShuttingDown) {\n throw new FirestoreError(\n Code.FAILED_PRECONDITION,\n 'The client has already been terminated.'\n );\n }\n }\n\n /** Disables the network connection. Pending operations will not complete. */\n disableNetwork(): Promise {\n this.verifyNotTerminated();\n return this.asyncQueue.enqueue(() => {\n return this.syncEngine.disableNetwork();\n });\n }\n\n terminate(): Promise {\n return this.asyncQueue.enqueueAndInitiateShutdown(async () => {\n // PORTING NOTE: LocalStore does not need an explicit shutdown on web.\n if (this.gcScheduler) {\n this.gcScheduler.stop();\n }\n\n await this.remoteStore.shutdown();\n await this.sharedClientState.shutdown();\n await this.persistence.shutdown();\n\n // `removeChangeListener` must be called after shutting down the\n // RemoteStore as it will prevent the RemoteStore from retrieving\n // auth tokens.\n this.credentials.removeChangeListener();\n });\n }\n\n /**\n * Returns a Promise that resolves when all writes that were pending at the time this\n * method was called received server acknowledgement. An acknowledgement can be either acceptance\n * or rejection.\n */\n waitForPendingWrites(): Promise {\n this.verifyNotTerminated();\n\n const deferred = new Deferred();\n this.asyncQueue.enqueueAndForget(() => {\n return this.syncEngine.registerPendingWritesCallback(deferred);\n });\n return deferred.promise;\n }\n\n listen(\n query: Query,\n observer: Observer,\n options: ListenOptions\n ): QueryListener {\n this.verifyNotTerminated();\n const listener = new QueryListener(query, observer, options);\n this.asyncQueue.enqueueAndForget(() => this.eventMgr.listen(listener));\n return listener;\n }\n\n unlisten(listener: QueryListener): void {\n // Checks for termination but does not raise error, allowing unlisten after\n // termination to be a no-op.\n if (this.clientTerminated) {\n return;\n }\n this.asyncQueue.enqueueAndForget(() => {\n return this.eventMgr.unlisten(listener);\n });\n }\n\n async getDocumentFromLocalCache(\n docKey: DocumentKey\n ): Promise {\n this.verifyNotTerminated();\n const deferred = new Deferred();\n await this.asyncQueue.enqueue(async () => {\n try {\n const maybeDoc = await this.localStore.readDocument(docKey);\n if (maybeDoc instanceof Document) {\n deferred.resolve(maybeDoc);\n } else if (maybeDoc instanceof NoDocument) {\n deferred.resolve(null);\n } else {\n deferred.reject(\n new FirestoreError(\n Code.UNAVAILABLE,\n 'Failed to get document from cache. (However, this document may ' +\n \"exist on the server. Run again without setting 'source' in \" +\n 'the GetOptions to attempt to retrieve the document from the ' +\n 'server.)'\n )\n );\n }\n } catch (e) {\n const firestoreError = wrapInUserErrorIfRecoverable(\n e,\n `Failed to get document '${docKey} from cache`\n );\n deferred.reject(firestoreError);\n }\n });\n\n return deferred.promise;\n }\n\n async getDocumentsFromLocalCache(query: Query): Promise {\n this.verifyNotTerminated();\n const deferred = new Deferred();\n await this.asyncQueue.enqueue(async () => {\n try {\n const queryResult = await this.localStore.executeQuery(\n query,\n /* usePreviousResults= */ true\n );\n const view = new View(query, queryResult.remoteKeys);\n const viewDocChanges = view.computeDocChanges(queryResult.documents);\n const viewChange = view.applyChanges(\n viewDocChanges,\n /* updateLimboDocuments= */ false\n );\n deferred.resolve(viewChange.snapshot!);\n } catch (e) {\n const firestoreError = wrapInUserErrorIfRecoverable(\n e,\n `Failed to execute query '${query} against cache`\n );\n deferred.reject(firestoreError);\n }\n });\n return deferred.promise;\n }\n\n write(mutations: Mutation[]): Promise {\n this.verifyNotTerminated();\n const deferred = new Deferred();\n this.asyncQueue.enqueueAndForget(() =>\n this.syncEngine.write(mutations, deferred)\n );\n return deferred.promise;\n }\n\n databaseId(): DatabaseId {\n return this.databaseInfo.databaseId;\n }\n\n addSnapshotsInSyncListener(observer: Observer): void {\n this.verifyNotTerminated();\n this.asyncQueue.enqueueAndForget(() => {\n this.eventMgr.addSnapshotsInSyncListener(observer);\n return Promise.resolve();\n });\n }\n\n removeSnapshotsInSyncListener(observer: Observer): void {\n // Checks for shutdown but does not raise error, allowing remove after\n // shutdown to be a no-op.\n if (this.clientTerminated) {\n return;\n }\n this.asyncQueue.enqueueAndForget(() => {\n this.eventMgr.removeSnapshotsInSyncListener(observer);\n return Promise.resolve();\n });\n }\n\n get clientTerminated(): boolean {\n // Technically, the asyncQueue is still running, but only accepting operations\n // related to termination or supposed to be run after termination. It is effectively\n // terminated to the eyes of users.\n return this.asyncQueue.isShuttingDown;\n }\n\n transaction(\n updateFunction: (transaction: Transaction) => Promise\n ): Promise {\n this.verifyNotTerminated();\n const deferred = new Deferred();\n this.asyncQueue.enqueueAndForget(() => {\n this.syncEngine.runTransaction(this.asyncQueue, updateFunction, deferred);\n return Promise.resolve();\n });\n return deferred.promise;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Observer } from '../core/event_manager';\nimport { EventHandler } from './misc';\n\n/*\n * A wrapper implementation of Observer that will dispatch events\n * asynchronously. To allow immediate silencing, a mute call is added which\n * causes events scheduled to no longer be raised.\n */\nexport class AsyncObserver implements Observer {\n /**\n * When set to true, will not raise future events. Necessary to deal with\n * async detachment of listener.\n */\n private muted = false;\n\n constructor(private observer: Observer) {}\n\n next(value: T): void {\n this.scheduleEvent(this.observer.next, value);\n }\n\n error(error: Error): void {\n this.scheduleEvent(this.observer.error, error);\n }\n\n mute(): void {\n this.muted = true;\n }\n\n private scheduleEvent(eventHandler: EventHandler, event: E): void {\n if (!this.muted) {\n setTimeout(() => {\n if (!this.muted) {\n eventHandler(event);\n }\n }, 0);\n }\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject } from '../model/object_value';\n\n/**\n * Observer/Subscribe interfaces.\n */\nexport type NextFn = (value: T) => void;\nexport type ErrorFn = (error: Error) => void;\nexport type CompleteFn = () => void;\n\n// Allow for any of the Observer methods to be undefined.\nexport interface PartialObserver {\n next?: NextFn;\n error?: ErrorFn;\n complete?: CompleteFn;\n}\n\nexport interface Unsubscribe {\n (): void;\n}\n\nexport function isPartialObserver(obj: unknown): boolean {\n return implementsAnyMethods(obj, ['next', 'error', 'complete']);\n}\n\n/**\n * Returns true if obj is an object and contains at least one of the specified\n * methods.\n */\nfunction implementsAnyMethods(obj: unknown, methods: string[]): boolean {\n if (typeof obj !== 'object' || obj === null) {\n return false;\n }\n\n const object = obj as JsonObject;\n for (const method of methods) {\n if (method in object && typeof object[method] === 'function') {\n return true;\n }\n }\n return false;\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as firestore from '@firebase/firestore-types';\n\nimport * as api from '../protos/firestore_proto_api';\n\nimport { DocumentKeyReference } from './user_data_reader';\nimport { Blob } from './blob';\nimport { GeoPoint } from './geo_point';\nimport { Timestamp } from './timestamp';\nimport { DatabaseId } from '../core/database_info';\nimport { DocumentKey } from '../model/document_key';\nimport {\n normalizeByteString,\n normalizeNumber,\n normalizeTimestamp,\n typeOrder\n} from '../model/values';\nimport {\n getLocalWriteTime,\n getPreviousValue\n} from '../model/server_timestamps';\nimport { fail, hardAssert } from '../util/assert';\nimport { forEach } from '../util/obj';\nimport { TypeOrder } from '../model/object_value';\nimport { ResourcePath } from '../model/path';\nimport { isValidResourceName } from '../remote/serializer';\nimport { logError } from '../util/log';\n\nexport type ServerTimestampBehavior = 'estimate' | 'previous' | 'none';\n\n/**\n * Converts Firestore's internal types to the JavaScript types that we expose\n * to the user.\n */\nexport class UserDataWriter {\n constructor(\n private readonly databaseId: DatabaseId,\n private readonly timestampsInSnapshots: boolean,\n private readonly serverTimestampBehavior: ServerTimestampBehavior,\n private readonly referenceFactory: (\n key: DocumentKey\n ) => DocumentKeyReference\n ) {}\n\n convertValue(value: api.Value): unknown {\n switch (typeOrder(value)) {\n case TypeOrder.NullValue:\n return null;\n case TypeOrder.BooleanValue:\n return value.booleanValue!;\n case TypeOrder.NumberValue:\n return normalizeNumber(value.integerValue || value.doubleValue);\n case TypeOrder.TimestampValue:\n return this.convertTimestamp(value.timestampValue!);\n case TypeOrder.ServerTimestampValue:\n return this.convertServerTimestamp(value);\n case TypeOrder.StringValue:\n return value.stringValue!;\n case TypeOrder.BlobValue:\n return new Blob(normalizeByteString(value.bytesValue!));\n case TypeOrder.RefValue:\n return this.convertReference(value.referenceValue!);\n case TypeOrder.GeoPointValue:\n return this.convertGeoPoint(value.geoPointValue!);\n case TypeOrder.ArrayValue:\n return this.convertArray(value.arrayValue!);\n case TypeOrder.ObjectValue:\n return this.convertObject(value.mapValue!);\n default:\n throw fail('Invalid value type: ' + JSON.stringify(value));\n }\n }\n\n private convertObject(mapValue: api.MapValue): firestore.DocumentData {\n const result: firestore.DocumentData = {};\n forEach(mapValue.fields || {}, (key, value) => {\n result[key] = this.convertValue(value);\n });\n return result;\n }\n\n private convertGeoPoint(value: api.LatLng): GeoPoint {\n return new GeoPoint(\n normalizeNumber(value.latitude),\n normalizeNumber(value.longitude)\n );\n }\n\n private convertArray(arrayValue: api.ArrayValue): unknown[] {\n return (arrayValue.values || []).map(value => this.convertValue(value));\n }\n\n private convertServerTimestamp(value: api.Value): unknown {\n switch (this.serverTimestampBehavior) {\n case 'previous':\n const previousValue = getPreviousValue(value);\n if (previousValue == null) {\n return null;\n }\n return this.convertValue(previousValue);\n case 'estimate':\n return this.convertTimestamp(getLocalWriteTime(value));\n default:\n return null;\n }\n }\n\n private convertTimestamp(value: api.Timestamp): Timestamp | Date {\n const normalizedValue = normalizeTimestamp(value);\n const timestamp = new Timestamp(\n normalizedValue.seconds,\n normalizedValue.nanos\n );\n if (this.timestampsInSnapshots) {\n return timestamp;\n } else {\n return timestamp.toDate();\n }\n }\n\n private convertReference(\n name: string\n ): DocumentKeyReference {\n const resourcePath = ResourcePath.fromString(name);\n hardAssert(\n isValidResourceName(resourcePath),\n 'ReferenceValue is not valid ' + name\n );\n const databaseId = new DatabaseId(resourcePath.get(1), resourcePath.get(3));\n const key = new DocumentKey(resourcePath.popFirst(5));\n\n if (!databaseId.isEqual(this.databaseId)) {\n // TODO(b/64130202): Somehow support foreign references.\n logError(\n `Document ${key} contains a document ` +\n `reference within a different database (` +\n `${databaseId.projectId}/${databaseId.database}) which is not ` +\n `supported. It will be treated as a reference in the current ` +\n `database (${this.databaseId.projectId}/${this.databaseId.database}) ` +\n `instead.`\n );\n }\n\n return this.referenceFactory(key);\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as firestore from '@firebase/firestore-types';\n\nimport * as api from '../protos/firestore_proto_api';\n\nimport { FirebaseApp } from '@firebase/app-types';\nimport { _FirebaseApp, FirebaseService } from '@firebase/app-types/private';\nimport { DatabaseId, DatabaseInfo } from '../core/database_info';\nimport { ListenOptions } from '../core/event_manager';\nimport {\n ComponentProvider,\n MemoryComponentProvider\n} from '../core/component_provider';\nimport { FirestoreClient, PersistenceSettings } from '../core/firestore_client';\nimport {\n Bound,\n Direction,\n FieldFilter,\n Filter,\n newQueryComparator,\n Operator,\n OrderBy,\n Query as InternalQuery,\n queryEquals\n} from '../core/query';\nimport { Transaction as InternalTransaction } from '../core/transaction';\nimport { ChangeType, ViewSnapshot } from '../core/view_snapshot';\nimport { LruParams } from '../local/lru_garbage_collector';\nimport { Document, MaybeDocument, NoDocument } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport { DeleteMutation, Mutation, Precondition } from '../model/mutation';\nimport { FieldPath, ResourcePath } from '../model/path';\nimport { isServerTimestamp } from '../model/server_timestamps';\nimport { refValue } from '../model/values';\nimport { debugAssert, fail } from '../util/assert';\nimport { AsyncObserver } from '../util/async_observer';\nimport { AsyncQueue } from '../util/async_queue';\nimport { Code, FirestoreError } from '../util/error';\nimport {\n invalidClassError,\n validateArgType,\n validateAtLeastNumberOfArgs,\n validateBetweenNumberOfArgs,\n validateDefined,\n validateExactNumberOfArgs,\n validateNamedOptionalPropertyEquals,\n validateNamedOptionalType,\n validateNamedType,\n validateOptionalArgType,\n validateOptionalArrayElements,\n validateOptionNames,\n validatePositiveNumber,\n validateStringEnum,\n valueDescription\n} from '../util/input_validation';\nimport { getLogLevel, logError, LogLevel, setLogLevel } from '../util/log';\nimport { AutoId } from '../util/misc';\nimport { Deferred } from '../util/promise';\nimport { FieldPath as ExternalFieldPath } from './field_path';\n\nimport {\n CredentialsProvider,\n CredentialsSettings,\n EmptyCredentialsProvider,\n FirebaseCredentialsProvider,\n makeCredentialsProvider\n} from './credentials';\nimport {\n CompleteFn,\n ErrorFn,\n isPartialObserver,\n NextFn,\n PartialObserver,\n Unsubscribe\n} from './observer';\nimport {\n DocumentKeyReference,\n fieldPathFromArgument,\n parseQueryValue,\n parseSetData,\n parseUpdateData,\n parseUpdateVarargs,\n UntypedFirestoreDataConverter,\n UserDataReader\n} from './user_data_reader';\nimport { UserDataWriter } from './user_data_writer';\nimport { FirebaseAuthInternalName } from '@firebase/auth-interop-types';\nimport { Provider } from '@firebase/component';\n\n// settings() defaults:\nconst DEFAULT_HOST = 'firestore.googleapis.com';\nconst DEFAULT_SSL = true;\nconst DEFAULT_TIMESTAMPS_IN_SNAPSHOTS = true;\nconst DEFAULT_FORCE_LONG_POLLING = false;\nconst DEFAULT_IGNORE_UNDEFINED_PROPERTIES = false;\n\n/**\n * Constant used to indicate the LRU garbage collection should be disabled.\n * Set this value as the `cacheSizeBytes` on the settings passed to the\n * `Firestore` instance.\n */\nexport const CACHE_SIZE_UNLIMITED = LruParams.COLLECTION_DISABLED;\n\n// enablePersistence() defaults:\nconst DEFAULT_SYNCHRONIZE_TABS = false;\n\n/** Undocumented, private additional settings not exposed in our public API. */\ninterface PrivateSettings extends firestore.Settings {\n // Can be a google-auth-library or gapi client.\n credentials?: CredentialsSettings;\n}\n\n/**\n * Options that can be provided in the Firestore constructor when not using\n * Firebase (aka standalone mode).\n */\nexport interface FirestoreDatabase {\n projectId: string;\n database?: string;\n}\n\n/**\n * A concrete type describing all the values that can be applied via a\n * user-supplied firestore.Settings object. This is a separate type so that\n * defaults can be supplied and the value can be checked for equality.\n */\nclass FirestoreSettings {\n /** The hostname to connect to. */\n readonly host: string;\n\n /** Whether to use SSL when connecting. */\n readonly ssl: boolean;\n\n readonly timestampsInSnapshots: boolean;\n\n readonly cacheSizeBytes: number;\n\n readonly forceLongPolling: boolean;\n\n readonly ignoreUndefinedProperties: boolean;\n\n // Can be a google-auth-library or gapi client.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n credentials?: any;\n\n constructor(settings: PrivateSettings) {\n if (settings.host === undefined) {\n if (settings.ssl !== undefined) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n \"Can't provide ssl option if host option is not set\"\n );\n }\n this.host = DEFAULT_HOST;\n this.ssl = DEFAULT_SSL;\n } else {\n validateNamedType('settings', 'non-empty string', 'host', settings.host);\n this.host = settings.host;\n\n validateNamedOptionalType('settings', 'boolean', 'ssl', settings.ssl);\n this.ssl = settings.ssl ?? DEFAULT_SSL;\n }\n validateOptionNames('settings', settings, [\n 'host',\n 'ssl',\n 'credentials',\n 'timestampsInSnapshots',\n 'cacheSizeBytes',\n 'experimentalForceLongPolling',\n 'ignoreUndefinedProperties'\n ]);\n\n validateNamedOptionalType(\n 'settings',\n 'object',\n 'credentials',\n settings.credentials\n );\n this.credentials = settings.credentials;\n\n validateNamedOptionalType(\n 'settings',\n 'boolean',\n 'timestampsInSnapshots',\n settings.timestampsInSnapshots\n );\n\n validateNamedOptionalType(\n 'settings',\n 'boolean',\n 'ignoreUndefinedProperties',\n settings.ignoreUndefinedProperties\n );\n\n // Nobody should set timestampsInSnapshots anymore, but the error depends on\n // whether they set it to true or false...\n if (settings.timestampsInSnapshots === true) {\n logError(\n \"The setting 'timestampsInSnapshots: true' is no longer required \" +\n 'and should be removed.'\n );\n } else if (settings.timestampsInSnapshots === false) {\n logError(\n \"Support for 'timestampsInSnapshots: false' will be removed soon. \" +\n 'You must update your code to handle Timestamp objects.'\n );\n }\n this.timestampsInSnapshots =\n settings.timestampsInSnapshots ?? DEFAULT_TIMESTAMPS_IN_SNAPSHOTS;\n this.ignoreUndefinedProperties =\n settings.ignoreUndefinedProperties ?? DEFAULT_IGNORE_UNDEFINED_PROPERTIES;\n\n validateNamedOptionalType(\n 'settings',\n 'number',\n 'cacheSizeBytes',\n settings.cacheSizeBytes\n );\n if (settings.cacheSizeBytes === undefined) {\n this.cacheSizeBytes = LruParams.DEFAULT_CACHE_SIZE_BYTES;\n } else {\n if (\n settings.cacheSizeBytes !== CACHE_SIZE_UNLIMITED &&\n settings.cacheSizeBytes < LruParams.MINIMUM_CACHE_SIZE_BYTES\n ) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `cacheSizeBytes must be at least ${LruParams.MINIMUM_CACHE_SIZE_BYTES}`\n );\n } else {\n this.cacheSizeBytes = settings.cacheSizeBytes;\n }\n }\n\n validateNamedOptionalType(\n 'settings',\n 'boolean',\n 'experimentalForceLongPolling',\n settings.experimentalForceLongPolling\n );\n this.forceLongPolling =\n settings.experimentalForceLongPolling ?? DEFAULT_FORCE_LONG_POLLING;\n }\n\n isEqual(other: FirestoreSettings): boolean {\n return (\n this.host === other.host &&\n this.ssl === other.ssl &&\n this.timestampsInSnapshots === other.timestampsInSnapshots &&\n this.credentials === other.credentials &&\n this.cacheSizeBytes === other.cacheSizeBytes &&\n this.forceLongPolling === other.forceLongPolling &&\n this.ignoreUndefinedProperties === other.ignoreUndefinedProperties\n );\n }\n}\n\n/**\n * The root reference to the database.\n */\nexport class Firestore implements firestore.FirebaseFirestore, FirebaseService {\n // The objects that are a part of this API are exposed to third-parties as\n // compiled javascript so we want to flag our private members with a leading\n // underscore to discourage their use.\n readonly _databaseId: DatabaseId;\n private readonly _persistenceKey: string;\n private readonly _componentProvider: ComponentProvider;\n private _credentials: CredentialsProvider;\n private readonly _firebaseApp: FirebaseApp | null = null;\n private _settings: FirestoreSettings;\n\n // The firestore client instance. This will be available as soon as\n // configureClient is called, but any calls against it will block until\n // setup has completed.\n //\n // Operations on the _firestoreClient don't block on _firestoreReady. Those\n // are already set to synchronize on the async queue.\n private _firestoreClient: FirestoreClient | undefined;\n\n // Public for use in tests.\n // TODO(mikelehen): Use modularized initialization instead.\n readonly _queue = new AsyncQueue();\n\n _userDataReader: UserDataReader | undefined;\n\n // Note: We are using `MemoryComponentProvider` as a default\n // ComponentProvider to ensure backwards compatibility with the format\n // expected by the console build.\n constructor(\n databaseIdOrApp: FirestoreDatabase | FirebaseApp,\n authProvider: Provider,\n componentProvider: ComponentProvider = new MemoryComponentProvider()\n ) {\n if (typeof (databaseIdOrApp as FirebaseApp).options === 'object') {\n // This is very likely a Firebase app object\n // TODO(b/34177605): Can we somehow use instanceof?\n const app = databaseIdOrApp as FirebaseApp;\n this._firebaseApp = app;\n this._databaseId = Firestore.databaseIdFromApp(app);\n this._persistenceKey = app.name;\n this._credentials = new FirebaseCredentialsProvider(authProvider);\n } else {\n const external = databaseIdOrApp as FirestoreDatabase;\n if (!external.projectId) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Must provide projectId'\n );\n }\n\n this._databaseId = new DatabaseId(external.projectId, external.database);\n // Use a default persistenceKey that lines up with FirebaseApp.\n this._persistenceKey = '[DEFAULT]';\n this._credentials = new EmptyCredentialsProvider();\n }\n\n this._componentProvider = componentProvider;\n this._settings = new FirestoreSettings({});\n }\n\n get _dataReader(): UserDataReader {\n debugAssert(\n !!this._firestoreClient,\n 'Cannot obtain UserDataReader before instance is intitialized'\n );\n if (!this._userDataReader) {\n // Lazy initialize UserDataReader once the settings are frozen\n this._userDataReader = new UserDataReader(\n this._databaseId,\n this._settings.ignoreUndefinedProperties\n );\n }\n return this._userDataReader;\n }\n\n settings(settingsLiteral: firestore.Settings): void {\n validateExactNumberOfArgs('Firestore.settings', arguments, 1);\n validateArgType('Firestore.settings', 'object', 1, settingsLiteral);\n\n const newSettings = new FirestoreSettings(settingsLiteral);\n if (this._firestoreClient && !this._settings.isEqual(newSettings)) {\n throw new FirestoreError(\n Code.FAILED_PRECONDITION,\n 'Firestore has already been started and its settings can no longer ' +\n 'be changed. You can only call settings() before calling any other ' +\n 'methods on a Firestore object.'\n );\n }\n\n this._settings = newSettings;\n if (newSettings.credentials !== undefined) {\n this._credentials = makeCredentialsProvider(newSettings.credentials);\n }\n }\n\n enableNetwork(): Promise {\n this.ensureClientConfigured();\n return this._firestoreClient!.enableNetwork();\n }\n\n disableNetwork(): Promise {\n this.ensureClientConfigured();\n return this._firestoreClient!.disableNetwork();\n }\n\n enablePersistence(settings?: firestore.PersistenceSettings): Promise {\n if (this._firestoreClient) {\n throw new FirestoreError(\n Code.FAILED_PRECONDITION,\n 'Firestore has already been started and persistence can no longer ' +\n 'be enabled. You can only call enablePersistence() before calling ' +\n 'any other methods on a Firestore object.'\n );\n }\n\n let synchronizeTabs = false;\n let experimentalForceOwningTab = false;\n\n if (settings) {\n if (settings.experimentalTabSynchronization !== undefined) {\n logError(\n \"The 'experimentalTabSynchronization' setting will be removed. Use 'synchronizeTabs' instead.\"\n );\n }\n synchronizeTabs =\n settings.synchronizeTabs ??\n settings.experimentalTabSynchronization ??\n DEFAULT_SYNCHRONIZE_TABS;\n\n experimentalForceOwningTab = settings.experimentalForceOwningTab\n ? settings.experimentalForceOwningTab\n : false;\n\n if (synchronizeTabs && experimentalForceOwningTab) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n \"The 'experimentalForceOwningTab' setting cannot be used with 'synchronizeTabs'.\"\n );\n }\n }\n\n return this.configureClient(this._componentProvider, {\n durable: true,\n cacheSizeBytes: this._settings.cacheSizeBytes,\n synchronizeTabs,\n forceOwningTab: experimentalForceOwningTab\n });\n }\n\n async clearPersistence(): Promise {\n if (\n this._firestoreClient !== undefined &&\n !this._firestoreClient.clientTerminated\n ) {\n throw new FirestoreError(\n Code.FAILED_PRECONDITION,\n 'Persistence can only be cleared before a Firestore instance is ' +\n 'initialized or after it is terminated.'\n );\n }\n\n const deferred = new Deferred();\n this._queue.enqueueAndForgetEvenAfterShutdown(async () => {\n try {\n await this._componentProvider.clearPersistence(\n this._databaseId,\n this._persistenceKey\n );\n deferred.resolve();\n } catch (e) {\n deferred.reject(e);\n }\n });\n return deferred.promise;\n }\n\n terminate(): Promise {\n (this.app as _FirebaseApp)._removeServiceInstance('firestore');\n return this.INTERNAL.delete();\n }\n\n get _isTerminated(): boolean {\n this.ensureClientConfigured();\n return this._firestoreClient!.clientTerminated;\n }\n\n waitForPendingWrites(): Promise {\n this.ensureClientConfigured();\n return this._firestoreClient!.waitForPendingWrites();\n }\n\n onSnapshotsInSync(observer: PartialObserver): Unsubscribe;\n onSnapshotsInSync(onSync: () => void): Unsubscribe;\n onSnapshotsInSync(arg: unknown): Unsubscribe {\n this.ensureClientConfigured();\n\n if (isPartialObserver(arg)) {\n return addSnapshotsInSyncListener(\n this._firestoreClient!,\n arg as PartialObserver\n );\n } else {\n validateArgType('Firestore.onSnapshotsInSync', 'function', 1, arg);\n const observer: PartialObserver = {\n next: arg as () => void\n };\n return addSnapshotsInSyncListener(this._firestoreClient!, observer);\n }\n }\n\n ensureClientConfigured(): FirestoreClient {\n if (!this._firestoreClient) {\n // Kick off starting the client but don't actually wait for it.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.configureClient(new MemoryComponentProvider(), {\n durable: false\n });\n }\n return this._firestoreClient as FirestoreClient;\n }\n\n private makeDatabaseInfo(): DatabaseInfo {\n return new DatabaseInfo(\n this._databaseId,\n this._persistenceKey,\n this._settings.host,\n this._settings.ssl,\n this._settings.forceLongPolling\n );\n }\n\n private configureClient(\n componentProvider: ComponentProvider,\n persistenceSettings: PersistenceSettings\n ): Promise {\n debugAssert(!!this._settings.host, 'FirestoreSettings.host is not set');\n\n debugAssert(\n !this._firestoreClient,\n 'configureClient() called multiple times'\n );\n\n const databaseInfo = this.makeDatabaseInfo();\n\n this._firestoreClient = new FirestoreClient(this._credentials, this._queue);\n\n return this._firestoreClient.start(\n databaseInfo,\n componentProvider,\n persistenceSettings\n );\n }\n\n private static databaseIdFromApp(app: FirebaseApp): DatabaseId {\n if (!contains(app.options, 'projectId')) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n '\"projectId\" not provided in firebase.initializeApp.'\n );\n }\n\n const projectId = app.options.projectId;\n if (!projectId || typeof projectId !== 'string') {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'projectId must be a string in FirebaseApp.options'\n );\n }\n return new DatabaseId(projectId);\n }\n\n get app(): FirebaseApp {\n if (!this._firebaseApp) {\n throw new FirestoreError(\n Code.FAILED_PRECONDITION,\n \"Firestore was not initialized using the Firebase SDK. 'app' is \" +\n 'not available'\n );\n }\n return this._firebaseApp;\n }\n\n INTERNAL = {\n delete: async (): Promise => {\n // The client must be initalized to ensure that all subsequent API usage\n // throws an exception.\n this.ensureClientConfigured();\n await this._firestoreClient!.terminate();\n }\n };\n\n collection(pathString: string): firestore.CollectionReference {\n validateExactNumberOfArgs('Firestore.collection', arguments, 1);\n validateArgType('Firestore.collection', 'non-empty string', 1, pathString);\n this.ensureClientConfigured();\n return new CollectionReference(\n ResourcePath.fromString(pathString),\n this,\n /* converter= */ null\n );\n }\n\n doc(pathString: string): firestore.DocumentReference {\n validateExactNumberOfArgs('Firestore.doc', arguments, 1);\n validateArgType('Firestore.doc', 'non-empty string', 1, pathString);\n this.ensureClientConfigured();\n return DocumentReference.forPath(\n ResourcePath.fromString(pathString),\n this,\n /* converter= */ null\n );\n }\n\n collectionGroup(collectionId: string): firestore.Query {\n validateExactNumberOfArgs('Firestore.collectionGroup', arguments, 1);\n validateArgType(\n 'Firestore.collectionGroup',\n 'non-empty string',\n 1,\n collectionId\n );\n if (collectionId.indexOf('/') >= 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid collection ID '${collectionId}' passed to function ` +\n `Firestore.collectionGroup(). Collection IDs must not contain '/'.`\n );\n }\n this.ensureClientConfigured();\n return new Query(\n new InternalQuery(ResourcePath.emptyPath(), collectionId),\n this,\n /* converter= */ null\n );\n }\n\n runTransaction(\n updateFunction: (transaction: firestore.Transaction) => Promise\n ): Promise {\n validateExactNumberOfArgs('Firestore.runTransaction', arguments, 1);\n validateArgType('Firestore.runTransaction', 'function', 1, updateFunction);\n return this.ensureClientConfigured().transaction(\n (transaction: InternalTransaction) => {\n return updateFunction(new Transaction(this, transaction));\n }\n );\n }\n\n batch(): firestore.WriteBatch {\n this.ensureClientConfigured();\n\n return new WriteBatch(this);\n }\n\n static get logLevel(): firestore.LogLevel {\n switch (getLogLevel()) {\n case LogLevel.DEBUG:\n return 'debug';\n case LogLevel.ERROR:\n return 'error';\n case LogLevel.SILENT:\n return 'silent';\n case LogLevel.WARN:\n return 'warn';\n case LogLevel.INFO:\n return 'info';\n case LogLevel.VERBOSE:\n return 'verbose';\n default:\n // The default log level is error\n return 'error';\n }\n }\n\n static setLogLevel(level: firestore.LogLevel): void {\n validateExactNumberOfArgs('Firestore.setLogLevel', arguments, 1);\n validateStringEnum(\n 'setLogLevel',\n ['debug', 'error', 'silent', 'warn', 'info', 'verbose'],\n 1,\n level\n );\n setLogLevel(level);\n }\n\n // Note: this is not a property because the minifier can't work correctly with\n // the way TypeScript compiler outputs properties.\n _areTimestampsInSnapshotsEnabled(): boolean {\n return this._settings.timestampsInSnapshots;\n }\n}\n\n/** Registers the listener for onSnapshotsInSync() */\nexport function addSnapshotsInSyncListener(\n firestoreClient: FirestoreClient,\n observer: PartialObserver\n): Unsubscribe {\n const errHandler = (err: Error): void => {\n throw fail('Uncaught Error in onSnapshotsInSync');\n };\n const asyncObserver = new AsyncObserver({\n next: () => {\n if (observer.next) {\n observer.next();\n }\n },\n error: errHandler\n });\n firestoreClient.addSnapshotsInSyncListener(asyncObserver);\n return () => {\n asyncObserver.mute();\n firestoreClient.removeSnapshotsInSyncListener(asyncObserver);\n };\n}\n\n/**\n * A reference to a transaction.\n */\nexport class Transaction implements firestore.Transaction {\n constructor(\n private _firestore: Firestore,\n private _transaction: InternalTransaction\n ) {}\n\n get(\n documentRef: firestore.DocumentReference\n ): Promise> {\n validateExactNumberOfArgs('Transaction.get', arguments, 1);\n const ref = validateReference(\n 'Transaction.get',\n documentRef,\n this._firestore\n );\n return this._transaction\n .lookup([ref._key])\n .then((docs: MaybeDocument[]) => {\n if (!docs || docs.length !== 1) {\n return fail('Mismatch in docs returned from document lookup.');\n }\n const doc = docs[0];\n if (doc instanceof NoDocument) {\n return new DocumentSnapshot(\n this._firestore,\n ref._key,\n null,\n /* fromCache= */ false,\n /* hasPendingWrites= */ false,\n ref._converter\n );\n } else if (doc instanceof Document) {\n return new DocumentSnapshot(\n this._firestore,\n ref._key,\n doc,\n /* fromCache= */ false,\n /* hasPendingWrites= */ false,\n ref._converter\n );\n } else {\n throw fail(\n `BatchGetDocumentsRequest returned unexpected document type: ${doc.constructor.name}`\n );\n }\n });\n }\n\n set(\n documentRef: DocumentReference,\n data: Partial,\n options: firestore.SetOptions\n ): Transaction;\n set(documentRef: DocumentReference, data: T): Transaction;\n set(\n documentRef: firestore.DocumentReference,\n value: T | Partial,\n options?: firestore.SetOptions\n ): Transaction {\n validateBetweenNumberOfArgs('Transaction.set', arguments, 2, 3);\n const ref = validateReference(\n 'Transaction.set',\n documentRef,\n this._firestore\n );\n options = validateSetOptions('Transaction.set', options);\n const convertedValue = applyFirestoreDataConverter(\n ref._converter,\n value,\n options\n );\n const parsed = parseSetData(\n this._firestore._dataReader,\n 'Transaction.set',\n ref._key,\n convertedValue,\n ref._converter !== null,\n options\n );\n this._transaction.set(ref._key, parsed);\n return this;\n }\n\n update(\n documentRef: firestore.DocumentReference,\n value: firestore.UpdateData\n ): Transaction;\n update(\n documentRef: firestore.DocumentReference,\n field: string | ExternalFieldPath,\n value: unknown,\n ...moreFieldsAndValues: unknown[]\n ): Transaction;\n update(\n documentRef: firestore.DocumentReference,\n fieldOrUpdateData: string | ExternalFieldPath | firestore.UpdateData,\n value?: unknown,\n ...moreFieldsAndValues: unknown[]\n ): Transaction {\n let ref;\n let parsed;\n\n if (\n typeof fieldOrUpdateData === 'string' ||\n fieldOrUpdateData instanceof ExternalFieldPath\n ) {\n validateAtLeastNumberOfArgs('Transaction.update', arguments, 3);\n ref = validateReference(\n 'Transaction.update',\n documentRef,\n this._firestore\n );\n parsed = parseUpdateVarargs(\n this._firestore._dataReader,\n 'Transaction.update',\n ref._key,\n fieldOrUpdateData,\n value,\n moreFieldsAndValues\n );\n } else {\n validateExactNumberOfArgs('Transaction.update', arguments, 2);\n ref = validateReference(\n 'Transaction.update',\n documentRef,\n this._firestore\n );\n parsed = parseUpdateData(\n this._firestore._dataReader,\n 'Transaction.update',\n ref._key,\n fieldOrUpdateData\n );\n }\n\n this._transaction.update(ref._key, parsed);\n return this;\n }\n\n delete(documentRef: firestore.DocumentReference): Transaction {\n validateExactNumberOfArgs('Transaction.delete', arguments, 1);\n const ref = validateReference(\n 'Transaction.delete',\n documentRef,\n this._firestore\n );\n this._transaction.delete(ref._key);\n return this;\n }\n}\n\nexport class WriteBatch implements firestore.WriteBatch {\n private _mutations = [] as Mutation[];\n private _committed = false;\n\n constructor(private _firestore: Firestore) {}\n\n set(\n documentRef: DocumentReference,\n data: Partial,\n options: firestore.SetOptions\n ): WriteBatch;\n set(documentRef: DocumentReference, data: T): WriteBatch;\n set(\n documentRef: firestore.DocumentReference,\n value: T | Partial,\n options?: firestore.SetOptions\n ): WriteBatch {\n validateBetweenNumberOfArgs('WriteBatch.set', arguments, 2, 3);\n this.verifyNotCommitted();\n const ref = validateReference(\n 'WriteBatch.set',\n documentRef,\n this._firestore\n );\n options = validateSetOptions('WriteBatch.set', options);\n const convertedValue = applyFirestoreDataConverter(\n ref._converter,\n value,\n options\n );\n const parsed = parseSetData(\n this._firestore._dataReader,\n 'WriteBatch.set',\n ref._key,\n convertedValue,\n ref._converter !== null,\n options\n );\n this._mutations = this._mutations.concat(\n parsed.toMutations(ref._key, Precondition.none())\n );\n return this;\n }\n\n update(\n documentRef: firestore.DocumentReference,\n value: firestore.UpdateData\n ): WriteBatch;\n update(\n documentRef: firestore.DocumentReference,\n field: string | ExternalFieldPath,\n value: unknown,\n ...moreFieldsAndValues: unknown[]\n ): WriteBatch;\n update(\n documentRef: firestore.DocumentReference,\n fieldOrUpdateData: string | ExternalFieldPath | firestore.UpdateData,\n value?: unknown,\n ...moreFieldsAndValues: unknown[]\n ): WriteBatch {\n this.verifyNotCommitted();\n\n let ref;\n let parsed;\n\n if (\n typeof fieldOrUpdateData === 'string' ||\n fieldOrUpdateData instanceof ExternalFieldPath\n ) {\n validateAtLeastNumberOfArgs('WriteBatch.update', arguments, 3);\n ref = validateReference(\n 'WriteBatch.update',\n documentRef,\n this._firestore\n );\n parsed = parseUpdateVarargs(\n this._firestore._dataReader,\n 'WriteBatch.update',\n ref._key,\n fieldOrUpdateData,\n value,\n moreFieldsAndValues\n );\n } else {\n validateExactNumberOfArgs('WriteBatch.update', arguments, 2);\n ref = validateReference(\n 'WriteBatch.update',\n documentRef,\n this._firestore\n );\n parsed = parseUpdateData(\n this._firestore._dataReader,\n 'WriteBatch.update',\n ref._key,\n fieldOrUpdateData\n );\n }\n\n this._mutations = this._mutations.concat(\n parsed.toMutations(ref._key, Precondition.exists(true))\n );\n return this;\n }\n\n delete(documentRef: firestore.DocumentReference): WriteBatch {\n validateExactNumberOfArgs('WriteBatch.delete', arguments, 1);\n this.verifyNotCommitted();\n const ref = validateReference(\n 'WriteBatch.delete',\n documentRef,\n this._firestore\n );\n this._mutations = this._mutations.concat(\n new DeleteMutation(ref._key, Precondition.none())\n );\n return this;\n }\n\n commit(): Promise {\n this.verifyNotCommitted();\n this._committed = true;\n if (this._mutations.length > 0) {\n return this._firestore.ensureClientConfigured().write(this._mutations);\n }\n\n return Promise.resolve();\n }\n\n private verifyNotCommitted(): void {\n if (this._committed) {\n throw new FirestoreError(\n Code.FAILED_PRECONDITION,\n 'A write batch can no longer be used after commit() ' +\n 'has been called.'\n );\n }\n }\n}\n\n/**\n * A reference to a particular document in a collection in the database.\n */\nexport class DocumentReference\n extends DocumentKeyReference\n implements firestore.DocumentReference {\n private _firestoreClient: FirestoreClient;\n\n constructor(\n public _key: DocumentKey,\n readonly firestore: Firestore,\n readonly _converter: firestore.FirestoreDataConverter | null\n ) {\n super(firestore._databaseId, _key, _converter);\n this._firestoreClient = this.firestore.ensureClientConfigured();\n }\n\n static forPath(\n path: ResourcePath,\n firestore: Firestore,\n converter: firestore.FirestoreDataConverter | null\n ): DocumentReference {\n if (path.length % 2 !== 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Invalid document reference. Document ' +\n 'references must have an even number of segments, but ' +\n `${path.canonicalString()} has ${path.length}`\n );\n }\n return new DocumentReference(new DocumentKey(path), firestore, converter);\n }\n\n get id(): string {\n return this._key.path.lastSegment();\n }\n\n get parent(): firestore.CollectionReference {\n return new CollectionReference(\n this._key.path.popLast(),\n this.firestore,\n this._converter\n );\n }\n\n get path(): string {\n return this._key.path.canonicalString();\n }\n\n collection(\n pathString: string\n ): firestore.CollectionReference {\n validateExactNumberOfArgs('DocumentReference.collection', arguments, 1);\n validateArgType(\n 'DocumentReference.collection',\n 'non-empty string',\n 1,\n pathString\n );\n if (!pathString) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Must provide a non-empty collection name to collection()'\n );\n }\n const path = ResourcePath.fromString(pathString);\n return new CollectionReference(\n this._key.path.child(path),\n this.firestore,\n /* converter= */ null\n );\n }\n\n isEqual(other: firestore.DocumentReference): boolean {\n if (!(other instanceof DocumentReference)) {\n throw invalidClassError('isEqual', 'DocumentReference', 1, other);\n }\n return (\n this.firestore === other.firestore &&\n this._key.isEqual(other._key) &&\n this._converter === other._converter\n );\n }\n\n set(value: Partial, options: firestore.SetOptions): Promise;\n set(value: T): Promise;\n set(value: T | Partial, options?: firestore.SetOptions): Promise {\n validateBetweenNumberOfArgs('DocumentReference.set', arguments, 1, 2);\n options = validateSetOptions('DocumentReference.set', options);\n const convertedValue = applyFirestoreDataConverter(\n this._converter,\n value,\n options\n );\n const parsed = parseSetData(\n this.firestore._dataReader,\n 'DocumentReference.set',\n this._key,\n convertedValue,\n this._converter !== null,\n options\n );\n return this._firestoreClient.write(\n parsed.toMutations(this._key, Precondition.none())\n );\n }\n\n update(value: firestore.UpdateData): Promise;\n update(\n field: string | ExternalFieldPath,\n value: unknown,\n ...moreFieldsAndValues: unknown[]\n ): Promise;\n update(\n fieldOrUpdateData: string | ExternalFieldPath | firestore.UpdateData,\n value?: unknown,\n ...moreFieldsAndValues: unknown[]\n ): Promise {\n let parsed;\n\n if (\n typeof fieldOrUpdateData === 'string' ||\n fieldOrUpdateData instanceof ExternalFieldPath\n ) {\n validateAtLeastNumberOfArgs('DocumentReference.update', arguments, 2);\n parsed = parseUpdateVarargs(\n this.firestore._dataReader,\n 'DocumentReference.update',\n this._key,\n fieldOrUpdateData,\n value,\n moreFieldsAndValues\n );\n } else {\n validateExactNumberOfArgs('DocumentReference.update', arguments, 1);\n parsed = parseUpdateData(\n this.firestore._dataReader,\n 'DocumentReference.update',\n this._key,\n fieldOrUpdateData\n );\n }\n\n return this._firestoreClient.write(\n parsed.toMutations(this._key, Precondition.exists(true))\n );\n }\n\n delete(): Promise {\n validateExactNumberOfArgs('DocumentReference.delete', arguments, 0);\n return this._firestoreClient.write([\n new DeleteMutation(this._key, Precondition.none())\n ]);\n }\n\n onSnapshot(\n observer: PartialObserver>\n ): Unsubscribe;\n onSnapshot(\n options: firestore.SnapshotListenOptions,\n observer: PartialObserver>\n ): Unsubscribe;\n onSnapshot(\n onNext: NextFn>,\n onError?: ErrorFn,\n onCompletion?: CompleteFn\n ): Unsubscribe;\n onSnapshot(\n options: firestore.SnapshotListenOptions,\n onNext: NextFn>,\n onError?: ErrorFn,\n onCompletion?: CompleteFn\n ): Unsubscribe;\n\n onSnapshot(...args: unknown[]): Unsubscribe {\n validateBetweenNumberOfArgs(\n 'DocumentReference.onSnapshot',\n arguments,\n 1,\n 4\n );\n let options: firestore.SnapshotListenOptions = {\n includeMetadataChanges: false\n };\n let currArg = 0;\n if (\n typeof args[currArg] === 'object' &&\n !isPartialObserver(args[currArg])\n ) {\n options = args[currArg] as firestore.SnapshotListenOptions;\n validateOptionNames('DocumentReference.onSnapshot', options, [\n 'includeMetadataChanges'\n ]);\n validateNamedOptionalType(\n 'DocumentReference.onSnapshot',\n 'boolean',\n 'includeMetadataChanges',\n options.includeMetadataChanges\n );\n currArg++;\n }\n\n const internalOptions = {\n includeMetadataChanges: options.includeMetadataChanges\n };\n\n if (isPartialObserver(args[currArg])) {\n const userObserver = args[currArg] as PartialObserver<\n firestore.DocumentSnapshot\n >;\n args[currArg] = userObserver.next?.bind(userObserver);\n args[currArg + 1] = userObserver.error?.bind(userObserver);\n args[currArg + 2] = userObserver.complete?.bind(userObserver);\n } else {\n validateArgType(\n 'DocumentReference.onSnapshot',\n 'function',\n currArg,\n args[currArg]\n );\n validateOptionalArgType(\n 'DocumentReference.onSnapshot',\n 'function',\n currArg + 1,\n args[currArg + 1]\n );\n validateOptionalArgType(\n 'DocumentReference.onSnapshot',\n 'function',\n currArg + 2,\n args[currArg + 2]\n );\n }\n\n const observer: PartialObserver = {\n next: snapshot => {\n if (args[currArg]) {\n (args[currArg] as NextFn>)(\n this._convertToDocSnapshot(snapshot)\n );\n }\n },\n error: args[currArg + 1] as ErrorFn,\n complete: args[currArg + 2] as CompleteFn\n };\n\n return addDocSnapshotListener(\n this._firestoreClient,\n this._key,\n internalOptions,\n observer\n );\n }\n\n get(options?: firestore.GetOptions): Promise> {\n validateBetweenNumberOfArgs('DocumentReference.get', arguments, 0, 1);\n validateGetOptions('DocumentReference.get', options);\n\n if (options && options.source === 'cache') {\n return this.firestore\n .ensureClientConfigured()\n .getDocumentFromLocalCache(this._key)\n .then(\n doc =>\n new DocumentSnapshot(\n this.firestore,\n this._key,\n doc,\n /*fromCache=*/ true,\n doc instanceof Document ? doc.hasLocalMutations : false,\n this._converter\n )\n );\n } else {\n return getDocViaSnapshotListener(\n this._firestoreClient,\n this._key,\n options\n ).then(snapshot => this._convertToDocSnapshot(snapshot));\n }\n }\n\n withConverter(\n converter: firestore.FirestoreDataConverter\n ): firestore.DocumentReference {\n return new DocumentReference(this._key, this.firestore, converter);\n }\n\n /**\n * Converts a ViewSnapshot that contains the current document to a\n * DocumentSnapshot.\n */\n private _convertToDocSnapshot(snapshot: ViewSnapshot): DocumentSnapshot {\n debugAssert(\n snapshot.docs.size <= 1,\n 'Too many documents returned on a document query'\n );\n const doc = snapshot.docs.get(this._key);\n\n return new DocumentSnapshot(\n this.firestore,\n this._key,\n doc,\n snapshot.fromCache,\n snapshot.hasPendingWrites,\n this._converter\n );\n }\n}\n\n/** Registers an internal snapshot listener for `ref`. */\nexport function addDocSnapshotListener(\n firestoreClient: FirestoreClient,\n key: DocumentKey,\n options: ListenOptions,\n observer: PartialObserver\n): Unsubscribe {\n let errHandler = (err: Error): void => {\n console.error('Uncaught Error in onSnapshot:', err);\n };\n if (observer.error) {\n errHandler = observer.error.bind(observer);\n }\n\n const asyncObserver = new AsyncObserver({\n next: snapshot => {\n if (observer.next) {\n observer.next(snapshot);\n }\n },\n error: errHandler\n });\n const internalListener = firestoreClient.listen(\n InternalQuery.atPath(key.path),\n asyncObserver,\n options\n );\n\n return () => {\n asyncObserver.mute();\n firestoreClient.unlisten(internalListener);\n };\n}\n\n/**\n * Retrieves a latency-compensated document from the backend via a\n * SnapshotListener.\n */\nexport function getDocViaSnapshotListener(\n firestoreClient: FirestoreClient,\n key: DocumentKey,\n options?: firestore.GetOptions\n): Promise {\n const result = new Deferred();\n const unlisten = addDocSnapshotListener(\n firestoreClient,\n key,\n {\n includeMetadataChanges: true,\n waitForSyncWhenOnline: true\n },\n {\n next: (snap: ViewSnapshot) => {\n // Remove query first before passing event to user to avoid\n // user actions affecting the now stale query.\n unlisten();\n\n const exists = snap.docs.has(key);\n if (!exists && snap.fromCache) {\n // TODO(dimond): If we're online and the document doesn't\n // exist then we resolve with a doc.exists set to false. If\n // we're offline however, we reject the Promise in this\n // case. Two options: 1) Cache the negative response from\n // the server so we can deliver that even when you're\n // offline 2) Actually reject the Promise in the online case\n // if the document doesn't exist.\n result.reject(\n new FirestoreError(\n Code.UNAVAILABLE,\n 'Failed to get document because the client is ' + 'offline.'\n )\n );\n } else if (\n exists &&\n snap.fromCache &&\n options &&\n options.source === 'server'\n ) {\n result.reject(\n new FirestoreError(\n Code.UNAVAILABLE,\n 'Failed to get document from server. (However, this ' +\n 'document does exist in the local cache. Run again ' +\n 'without setting source to \"server\" to ' +\n 'retrieve the cached document.)'\n )\n );\n } else {\n result.resolve(snap);\n }\n },\n error: e => result.reject(e)\n }\n );\n return result.promise;\n}\n\nexport class SnapshotMetadata implements firestore.SnapshotMetadata {\n constructor(\n readonly hasPendingWrites: boolean,\n readonly fromCache: boolean\n ) {}\n\n isEqual(other: firestore.SnapshotMetadata): boolean {\n return (\n this.hasPendingWrites === other.hasPendingWrites &&\n this.fromCache === other.fromCache\n );\n }\n}\n\n/**\n * Options interface that can be provided to configure the deserialization of\n * DocumentSnapshots.\n */\nexport interface SnapshotOptions extends firestore.SnapshotOptions {}\n\nexport class DocumentSnapshot\n implements firestore.DocumentSnapshot {\n constructor(\n private _firestore: Firestore,\n private _key: DocumentKey,\n public _document: Document | null,\n private _fromCache: boolean,\n private _hasPendingWrites: boolean,\n private readonly _converter: firestore.FirestoreDataConverter | null\n ) {}\n\n data(options?: firestore.SnapshotOptions): T | undefined {\n validateBetweenNumberOfArgs('DocumentSnapshot.data', arguments, 0, 1);\n options = validateSnapshotOptions('DocumentSnapshot.data', options);\n if (!this._document) {\n return undefined;\n } else {\n // We only want to use the converter and create a new DocumentSnapshot\n // if a converter has been provided.\n if (this._converter) {\n const snapshot = new QueryDocumentSnapshot(\n this._firestore,\n this._key,\n this._document,\n this._fromCache,\n this._hasPendingWrites,\n /* converter= */ null\n );\n return this._converter.fromFirestore(snapshot, options);\n } else {\n const userDataWriter = new UserDataWriter(\n this._firestore._databaseId,\n this._firestore._areTimestampsInSnapshotsEnabled(),\n options.serverTimestamps || 'none',\n key =>\n new DocumentReference(key, this._firestore, /* converter= */ null)\n );\n return userDataWriter.convertValue(this._document.toProto()) as T;\n }\n }\n }\n\n get(\n fieldPath: string | ExternalFieldPath,\n options?: firestore.SnapshotOptions\n ): unknown {\n validateBetweenNumberOfArgs('DocumentSnapshot.get', arguments, 1, 2);\n options = validateSnapshotOptions('DocumentSnapshot.get', options);\n if (this._document) {\n const value = this._document\n .data()\n .field(\n fieldPathFromArgument('DocumentSnapshot.get', fieldPath, this._key)\n );\n if (value !== null) {\n const userDataWriter = new UserDataWriter(\n this._firestore._databaseId,\n this._firestore._areTimestampsInSnapshotsEnabled(),\n options.serverTimestamps || 'none',\n key => new DocumentReference(key, this._firestore, this._converter)\n );\n return userDataWriter.convertValue(value);\n }\n }\n return undefined;\n }\n\n get id(): string {\n return this._key.path.lastSegment();\n }\n\n get ref(): firestore.DocumentReference {\n return new DocumentReference(\n this._key,\n this._firestore,\n this._converter\n );\n }\n\n get exists(): boolean {\n return this._document !== null;\n }\n\n get metadata(): firestore.SnapshotMetadata {\n return new SnapshotMetadata(this._hasPendingWrites, this._fromCache);\n }\n\n isEqual(other: firestore.DocumentSnapshot): boolean {\n if (!(other instanceof DocumentSnapshot)) {\n throw invalidClassError('isEqual', 'DocumentSnapshot', 1, other);\n }\n return (\n this._firestore === other._firestore &&\n this._fromCache === other._fromCache &&\n this._key.isEqual(other._key) &&\n (this._document === null\n ? other._document === null\n : this._document.isEqual(other._document)) &&\n this._converter === other._converter\n );\n }\n}\n\nexport class QueryDocumentSnapshot\n extends DocumentSnapshot\n implements firestore.QueryDocumentSnapshot {\n data(options?: SnapshotOptions): T {\n const data = super.data(options);\n debugAssert(\n data !== undefined,\n 'Document in a QueryDocumentSnapshot should exist'\n );\n return data;\n }\n}\n\n/** The query class that is shared between the full, lite and legacy SDK. */\nexport class BaseQuery {\n constructor(\n protected _databaseId: DatabaseId,\n protected _dataReader: UserDataReader,\n protected _query: InternalQuery\n ) {}\n\n protected createFilter(\n fieldPath: FieldPath,\n op: Operator,\n value: unknown\n ): FieldFilter {\n let fieldValue: api.Value;\n if (fieldPath.isKeyField()) {\n if (\n op === Operator.ARRAY_CONTAINS ||\n op === Operator.ARRAY_CONTAINS_ANY\n ) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid Query. You can't perform '${op}' ` +\n 'queries on FieldPath.documentId().'\n );\n } else if (op === Operator.IN) {\n this.validateDisjunctiveFilterElements(value, op);\n const referenceList: api.Value[] = [];\n for (const arrayValue of value as api.Value[]) {\n referenceList.push(this.parseDocumentIdValue(arrayValue));\n }\n fieldValue = { arrayValue: { values: referenceList } };\n } else {\n fieldValue = this.parseDocumentIdValue(value);\n }\n } else {\n if (op === Operator.IN || op === Operator.ARRAY_CONTAINS_ANY) {\n this.validateDisjunctiveFilterElements(value, op);\n }\n fieldValue = parseQueryValue(\n this._dataReader,\n 'Query.where',\n value,\n op === Operator.IN\n );\n }\n const filter = FieldFilter.create(fieldPath, op, fieldValue);\n this.validateNewFilter(filter);\n return filter;\n }\n\n protected createOrderBy(fieldPath: FieldPath, direction: Direction): OrderBy {\n if (this._query.startAt !== null) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Invalid query. You must not call Query.startAt() or ' +\n 'Query.startAfter() before calling Query.orderBy().'\n );\n }\n if (this._query.endAt !== null) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Invalid query. You must not call Query.endAt() or ' +\n 'Query.endBefore() before calling Query.orderBy().'\n );\n }\n const orderBy = new OrderBy(fieldPath, direction);\n this.validateNewOrderBy(orderBy);\n return orderBy;\n }\n\n /**\n * Create a Bound from a query and a document.\n *\n * Note that the Bound will always include the key of the document\n * and so only the provided document will compare equal to the returned\n * position.\n *\n * Will throw if the document does not contain all fields of the order by\n * of the query or if any of the fields in the order by are an uncommitted\n * server timestamp.\n */\n protected boundFromDocument(\n methodName: string,\n doc: Document | null,\n before: boolean\n ): Bound {\n if (!doc) {\n throw new FirestoreError(\n Code.NOT_FOUND,\n `Can't use a DocumentSnapshot that doesn't exist for ` +\n `${methodName}().`\n );\n }\n\n const components: api.Value[] = [];\n\n // Because people expect to continue/end a query at the exact document\n // provided, we need to use the implicit sort order rather than the explicit\n // sort order, because it's guaranteed to contain the document key. That way\n // the position becomes unambiguous and the query continues/ends exactly at\n // the provided document. Without the key (by using the explicit sort\n // orders), multiple documents could match the position, yielding duplicate\n // results.\n for (const orderBy of this._query.orderBy) {\n if (orderBy.field.isKeyField()) {\n components.push(refValue(this._databaseId, doc.key));\n } else {\n const value = doc.field(orderBy.field);\n if (isServerTimestamp(value)) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Invalid query. You are trying to start or end a query using a ' +\n 'document for which the field \"' +\n orderBy.field +\n '\" is an uncommitted server timestamp. (Since the value of ' +\n 'this field is unknown, you cannot start/end a query with it.)'\n );\n } else if (value !== null) {\n components.push(value);\n } else {\n const field = orderBy.field.canonicalString();\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid query. You are trying to start or end a query using a ` +\n `document for which the field '${field}' (used as the ` +\n `orderBy) does not exist.`\n );\n }\n }\n }\n return new Bound(components, before);\n }\n\n /**\n * Converts a list of field values to a Bound for the given query.\n */\n protected boundFromFields(\n methodName: string,\n values: unknown[],\n before: boolean\n ): Bound {\n // Use explicit order by's because it has to match the query the user made\n const orderBy = this._query.explicitOrderBy;\n if (values.length > orderBy.length) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Too many arguments provided to ${methodName}(). ` +\n `The number of arguments must be less than or equal to the ` +\n `number of Query.orderBy() clauses`\n );\n }\n\n const components: api.Value[] = [];\n for (let i = 0; i < values.length; i++) {\n const rawValue = values[i];\n const orderByComponent = orderBy[i];\n if (orderByComponent.field.isKeyField()) {\n if (typeof rawValue !== 'string') {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid query. Expected a string for document ID in ` +\n `${methodName}(), but got a ${typeof rawValue}`\n );\n }\n if (\n !this._query.isCollectionGroupQuery() &&\n rawValue.indexOf('/') !== -1\n ) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid query. When querying a collection and ordering by FieldPath.documentId(), ` +\n `the value passed to ${methodName}() must be a plain document ID, but ` +\n `'${rawValue}' contains a slash.`\n );\n }\n const path = this._query.path.child(ResourcePath.fromString(rawValue));\n if (!DocumentKey.isDocumentKey(path)) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid query. When querying a collection group and ordering by ` +\n `FieldPath.documentId(), the value passed to ${methodName}() must result in a ` +\n `valid document path, but '${path}' is not because it contains an odd number ` +\n `of segments.`\n );\n }\n const key = new DocumentKey(path);\n components.push(refValue(this._databaseId, key));\n } else {\n const wrapped = parseQueryValue(this._dataReader, methodName, rawValue);\n components.push(wrapped);\n }\n }\n\n return new Bound(components, before);\n }\n\n /**\n * Parses the given documentIdValue into a ReferenceValue, throwing\n * appropriate errors if the value is anything other than a DocumentReference\n * or String, or if the string is malformed.\n */\n private parseDocumentIdValue(documentIdValue: unknown): api.Value {\n if (typeof documentIdValue === 'string') {\n if (documentIdValue === '') {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Invalid query. When querying with FieldPath.documentId(), you ' +\n 'must provide a valid document ID, but it was an empty string.'\n );\n }\n if (\n !this._query.isCollectionGroupQuery() &&\n documentIdValue.indexOf('/') !== -1\n ) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid query. When querying a collection by ` +\n `FieldPath.documentId(), you must provide a plain document ID, but ` +\n `'${documentIdValue}' contains a '/' character.`\n );\n }\n const path = this._query.path.child(\n ResourcePath.fromString(documentIdValue)\n );\n if (!DocumentKey.isDocumentKey(path)) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid query. When querying a collection group by ` +\n `FieldPath.documentId(), the value provided must result in a valid document path, ` +\n `but '${path}' is not because it has an odd number of segments (${path.length}).`\n );\n }\n return refValue(this._databaseId, new DocumentKey(path));\n } else if (documentIdValue instanceof DocumentKeyReference) {\n return refValue(this._databaseId, documentIdValue._key);\n } else {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid query. When querying with FieldPath.documentId(), you must provide a valid ` +\n `string or a DocumentReference, but it was: ` +\n `${valueDescription(documentIdValue)}.`\n );\n }\n }\n\n /**\n * Validates that the value passed into a disjunctrive filter satisfies all\n * array requirements.\n */\n private validateDisjunctiveFilterElements(\n value: unknown,\n operator: Operator\n ): void {\n if (!Array.isArray(value) || value.length === 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Invalid Query. A non-empty array is required for ' +\n `'${operator.toString()}' filters.`\n );\n }\n if (value.length > 10) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid Query. '${operator.toString()}' filters support a ` +\n 'maximum of 10 elements in the value array.'\n );\n }\n if (value.indexOf(null) >= 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid Query. '${operator.toString()}' filters cannot contain 'null' ` +\n 'in the value array.'\n );\n }\n if (value.filter(element => Number.isNaN(element)).length > 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid Query. '${operator.toString()}' filters cannot contain 'NaN' ` +\n 'in the value array.'\n );\n }\n }\n\n private validateNewFilter(filter: Filter): void {\n if (filter instanceof FieldFilter) {\n const arrayOps = [Operator.ARRAY_CONTAINS, Operator.ARRAY_CONTAINS_ANY];\n const disjunctiveOps = [Operator.IN, Operator.ARRAY_CONTAINS_ANY];\n const isArrayOp = arrayOps.indexOf(filter.op) >= 0;\n const isDisjunctiveOp = disjunctiveOps.indexOf(filter.op) >= 0;\n\n if (filter.isInequality()) {\n const existingField = this._query.getInequalityFilterField();\n if (existingField !== null && !existingField.isEqual(filter.field)) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Invalid query. All where filters with an inequality' +\n ' (<, <=, >, or >=) must be on the same field. But you have' +\n ` inequality filters on '${existingField.toString()}'` +\n ` and '${filter.field.toString()}'`\n );\n }\n\n const firstOrderByField = this._query.getFirstOrderByField();\n if (firstOrderByField !== null) {\n this.validateOrderByAndInequalityMatch(\n filter.field,\n firstOrderByField\n );\n }\n } else if (isDisjunctiveOp || isArrayOp) {\n // You can have at most 1 disjunctive filter and 1 array filter. Check if\n // the new filter conflicts with an existing one.\n let conflictingOp: Operator | null = null;\n if (isDisjunctiveOp) {\n conflictingOp = this._query.findFilterOperator(disjunctiveOps);\n }\n if (conflictingOp === null && isArrayOp) {\n conflictingOp = this._query.findFilterOperator(arrayOps);\n }\n if (conflictingOp !== null) {\n // We special case when it's a duplicate op to give a slightly clearer error message.\n if (conflictingOp === filter.op) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Invalid query. You cannot use more than one ' +\n `'${filter.op.toString()}' filter.`\n );\n } else {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid query. You cannot use '${filter.op.toString()}' filters ` +\n `with '${conflictingOp.toString()}' filters.`\n );\n }\n }\n }\n }\n }\n\n private validateNewOrderBy(orderBy: OrderBy): void {\n if (this._query.getFirstOrderByField() === null) {\n // This is the first order by. It must match any inequality.\n const inequalityField = this._query.getInequalityFilterField();\n if (inequalityField !== null) {\n this.validateOrderByAndInequalityMatch(inequalityField, orderBy.field);\n }\n }\n }\n\n private validateOrderByAndInequalityMatch(\n inequality: FieldPath,\n orderBy: FieldPath\n ): void {\n if (!orderBy.isEqual(inequality)) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid query. You have a where filter with an inequality ` +\n `(<, <=, >, or >=) on field '${inequality.toString()}' ` +\n `and so you must also use '${inequality.toString()}' ` +\n `as your first Query.orderBy(), but your first Query.orderBy() ` +\n `is on field '${orderBy.toString()}' instead.`\n );\n }\n }\n}\n\nexport function validateHasExplicitOrderByForLimitToLast(\n query: InternalQuery\n): void {\n if (query.hasLimitToLast() && query.explicitOrderBy.length === 0) {\n throw new FirestoreError(\n Code.UNIMPLEMENTED,\n 'limitToLast() queries require specifying at least one orderBy() clause'\n );\n }\n}\n\nexport class Query extends BaseQuery\n implements firestore.Query {\n constructor(\n public _query: InternalQuery,\n readonly firestore: Firestore,\n protected readonly _converter: firestore.FirestoreDataConverter | null\n ) {\n super(firestore._databaseId, firestore._dataReader, _query);\n }\n\n where(\n field: string | ExternalFieldPath,\n opStr: firestore.WhereFilterOp,\n value: unknown\n ): firestore.Query {\n validateExactNumberOfArgs('Query.where', arguments, 3);\n validateDefined('Query.where', 3, value);\n\n // Enumerated from the WhereFilterOp type in index.d.ts.\n const whereFilterOpEnums = [\n Operator.LESS_THAN,\n Operator.LESS_THAN_OR_EQUAL,\n Operator.EQUAL,\n Operator.GREATER_THAN_OR_EQUAL,\n Operator.GREATER_THAN,\n Operator.ARRAY_CONTAINS,\n Operator.IN,\n Operator.ARRAY_CONTAINS_ANY\n ];\n const op = validateStringEnum('Query.where', whereFilterOpEnums, 2, opStr);\n const fieldPath = fieldPathFromArgument('Query.where', field);\n const filter = this.createFilter(fieldPath, op, value);\n return new Query(\n this._query.addFilter(filter),\n this.firestore,\n this._converter\n );\n }\n\n orderBy(\n field: string | ExternalFieldPath,\n directionStr?: firestore.OrderByDirection\n ): firestore.Query {\n validateBetweenNumberOfArgs('Query.orderBy', arguments, 1, 2);\n validateOptionalArgType(\n 'Query.orderBy',\n 'non-empty string',\n 2,\n directionStr\n );\n let direction: Direction;\n if (directionStr === undefined || directionStr === 'asc') {\n direction = Direction.ASCENDING;\n } else if (directionStr === 'desc') {\n direction = Direction.DESCENDING;\n } else {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Function Query.orderBy() has unknown direction '${directionStr}', ` +\n `expected 'asc' or 'desc'.`\n );\n }\n const fieldPath = fieldPathFromArgument('Query.orderBy', field);\n const orderBy = this.createOrderBy(fieldPath, direction);\n return new Query(\n this._query.addOrderBy(orderBy),\n this.firestore,\n this._converter\n );\n }\n\n limit(n: number): firestore.Query {\n validateExactNumberOfArgs('Query.limit', arguments, 1);\n validateArgType('Query.limit', 'number', 1, n);\n validatePositiveNumber('Query.limit', 1, n);\n return new Query(\n this._query.withLimitToFirst(n),\n this.firestore,\n this._converter\n );\n }\n\n limitToLast(n: number): firestore.Query {\n validateExactNumberOfArgs('Query.limitToLast', arguments, 1);\n validateArgType('Query.limitToLast', 'number', 1, n);\n validatePositiveNumber('Query.limitToLast', 1, n);\n return new Query(\n this._query.withLimitToLast(n),\n this.firestore,\n this._converter\n );\n }\n\n startAt(\n docOrField: unknown | firestore.DocumentSnapshot,\n ...fields: unknown[]\n ): firestore.Query {\n validateAtLeastNumberOfArgs('Query.startAt', arguments, 1);\n const bound = this.boundFromDocOrFields(\n 'Query.startAt',\n docOrField,\n fields,\n /*before=*/ true\n );\n return new Query(\n this._query.withStartAt(bound),\n this.firestore,\n this._converter\n );\n }\n\n startAfter(\n docOrField: unknown | firestore.DocumentSnapshot,\n ...fields: unknown[]\n ): firestore.Query {\n validateAtLeastNumberOfArgs('Query.startAfter', arguments, 1);\n const bound = this.boundFromDocOrFields(\n 'Query.startAfter',\n docOrField,\n fields,\n /*before=*/ false\n );\n return new Query(\n this._query.withStartAt(bound),\n this.firestore,\n this._converter\n );\n }\n\n endBefore(\n docOrField: unknown | firestore.DocumentSnapshot,\n ...fields: unknown[]\n ): firestore.Query {\n validateAtLeastNumberOfArgs('Query.endBefore', arguments, 1);\n const bound = this.boundFromDocOrFields(\n 'Query.endBefore',\n docOrField,\n fields,\n /*before=*/ true\n );\n return new Query(\n this._query.withEndAt(bound),\n this.firestore,\n this._converter\n );\n }\n\n endAt(\n docOrField: unknown | firestore.DocumentSnapshot,\n ...fields: unknown[]\n ): firestore.Query {\n validateAtLeastNumberOfArgs('Query.endAt', arguments, 1);\n const bound = this.boundFromDocOrFields(\n 'Query.endAt',\n docOrField,\n fields,\n /*before=*/ false\n );\n return new Query(\n this._query.withEndAt(bound),\n this.firestore,\n this._converter\n );\n }\n\n isEqual(other: firestore.Query): boolean {\n if (!(other instanceof Query)) {\n throw invalidClassError('isEqual', 'Query', 1, other);\n }\n return (\n this.firestore === other.firestore &&\n queryEquals(this._query, other._query) &&\n this._converter === other._converter\n );\n }\n\n withConverter(\n converter: firestore.FirestoreDataConverter\n ): firestore.Query {\n return new Query(this._query, this.firestore, converter);\n }\n\n /** Helper function to create a bound from a document or fields */\n private boundFromDocOrFields(\n methodName: string,\n docOrField: unknown | firestore.DocumentSnapshot,\n fields: unknown[],\n before: boolean\n ): Bound {\n validateDefined(methodName, 1, docOrField);\n if (docOrField instanceof DocumentSnapshot) {\n validateExactNumberOfArgs(methodName, [docOrField, ...fields], 1);\n return this.boundFromDocument(methodName, docOrField._document, before);\n } else {\n const allFields = [docOrField].concat(fields);\n return this.boundFromFields(methodName, allFields, before);\n }\n }\n\n onSnapshot(\n observer: PartialObserver>\n ): Unsubscribe;\n onSnapshot(\n options: firestore.SnapshotListenOptions,\n observer: PartialObserver>\n ): Unsubscribe;\n onSnapshot(\n onNext: NextFn>,\n onError?: ErrorFn,\n onCompletion?: CompleteFn\n ): Unsubscribe;\n onSnapshot(\n options: firestore.SnapshotListenOptions,\n onNext: NextFn>,\n onError?: ErrorFn,\n onCompletion?: CompleteFn\n ): Unsubscribe;\n\n onSnapshot(...args: unknown[]): Unsubscribe {\n validateBetweenNumberOfArgs('Query.onSnapshot', arguments, 1, 4);\n let options: firestore.SnapshotListenOptions = {};\n let currArg = 0;\n if (\n typeof args[currArg] === 'object' &&\n !isPartialObserver(args[currArg])\n ) {\n options = args[currArg] as firestore.SnapshotListenOptions;\n validateOptionNames('Query.onSnapshot', options, [\n 'includeMetadataChanges'\n ]);\n validateNamedOptionalType(\n 'Query.onSnapshot',\n 'boolean',\n 'includeMetadataChanges',\n options.includeMetadataChanges\n );\n currArg++;\n }\n\n if (isPartialObserver(args[currArg])) {\n const userObserver = args[currArg] as PartialObserver<\n firestore.QuerySnapshot\n >;\n args[currArg] = userObserver.next?.bind(userObserver);\n args[currArg + 1] = userObserver.error?.bind(userObserver);\n args[currArg + 2] = userObserver.complete?.bind(userObserver);\n } else {\n validateArgType('Query.onSnapshot', 'function', currArg, args[currArg]);\n validateOptionalArgType(\n 'Query.onSnapshot',\n 'function',\n currArg + 1,\n args[currArg + 1]\n );\n validateOptionalArgType(\n 'Query.onSnapshot',\n 'function',\n currArg + 2,\n args[currArg + 2]\n );\n }\n\n const observer: PartialObserver = {\n next: snapshot => {\n if (args[currArg]) {\n (args[currArg] as NextFn>)(\n new QuerySnapshot(\n this.firestore,\n this._query,\n snapshot,\n this._converter\n )\n );\n }\n },\n error: args[currArg + 1] as ErrorFn,\n complete: args[currArg + 2] as CompleteFn\n };\n\n validateHasExplicitOrderByForLimitToLast(this._query);\n const firestoreClient = this.firestore.ensureClientConfigured();\n return addQuerySnapshotListener(\n firestoreClient,\n this._query,\n options,\n observer\n );\n }\n\n get(options?: firestore.GetOptions): Promise> {\n validateBetweenNumberOfArgs('Query.get', arguments, 0, 1);\n validateGetOptions('Query.get', options);\n validateHasExplicitOrderByForLimitToLast(this._query);\n\n const firestoreClient = this.firestore.ensureClientConfigured();\n return (options && options.source === 'cache'\n ? firestoreClient.getDocumentsFromLocalCache(this._query)\n : getDocsViaSnapshotListener(firestoreClient, this._query, options)\n ).then(\n snap =>\n new QuerySnapshot(this.firestore, this._query, snap, this._converter)\n );\n }\n}\n\n/**\n * Retrieves a latency-compensated query snapshot from the backend via a\n * SnapshotListener.\n */\nexport function getDocsViaSnapshotListener(\n firestore: FirestoreClient,\n query: InternalQuery,\n options?: firestore.GetOptions\n): Promise {\n const result = new Deferred();\n const unlisten = addQuerySnapshotListener(\n firestore,\n query,\n {\n includeMetadataChanges: true,\n waitForSyncWhenOnline: true\n },\n {\n next: snapshot => {\n // Remove query first before passing event to user to avoid\n // user actions affecting the now stale query.\n unlisten();\n\n if (snapshot.fromCache && options && options.source === 'server') {\n result.reject(\n new FirestoreError(\n Code.UNAVAILABLE,\n 'Failed to get documents from server. (However, these ' +\n 'documents may exist in the local cache. Run again ' +\n 'without setting source to \"server\" to ' +\n 'retrieve the cached documents.)'\n )\n );\n } else {\n result.resolve(snapshot);\n }\n },\n error: e => result.reject(e)\n }\n );\n return result.promise;\n}\n\n/** Registers an internal snapshot listener for `query`. */\nexport function addQuerySnapshotListener(\n firestore: FirestoreClient,\n query: InternalQuery,\n options: ListenOptions,\n observer: PartialObserver\n): Unsubscribe {\n let errHandler = (err: Error): void => {\n console.error('Uncaught Error in onSnapshot:', err);\n };\n if (observer.error) {\n errHandler = observer.error.bind(observer);\n }\n const asyncObserver = new AsyncObserver({\n next: (result: ViewSnapshot): void => {\n if (observer.next) {\n observer.next(result);\n }\n },\n error: errHandler\n });\n\n const internalListener = firestore.listen(query, asyncObserver, options);\n return (): void => {\n asyncObserver.mute();\n firestore.unlisten(internalListener);\n };\n}\n\nexport class QuerySnapshot\n implements firestore.QuerySnapshot {\n private _cachedChanges: Array> | null = null;\n private _cachedChangesIncludeMetadataChanges: boolean | null = null;\n\n readonly metadata: firestore.SnapshotMetadata;\n\n constructor(\n private readonly _firestore: Firestore,\n private readonly _originalQuery: InternalQuery,\n private readonly _snapshot: ViewSnapshot,\n private readonly _converter: firestore.FirestoreDataConverter | null\n ) {\n this.metadata = new SnapshotMetadata(\n _snapshot.hasPendingWrites,\n _snapshot.fromCache\n );\n }\n\n get docs(): Array> {\n const result: Array> = [];\n this.forEach(doc => result.push(doc));\n return result;\n }\n\n get empty(): boolean {\n return this._snapshot.docs.isEmpty();\n }\n\n get size(): number {\n return this._snapshot.docs.size;\n }\n\n forEach(\n callback: (result: firestore.QueryDocumentSnapshot) => void,\n thisArg?: unknown\n ): void {\n validateBetweenNumberOfArgs('QuerySnapshot.forEach', arguments, 1, 2);\n validateArgType('QuerySnapshot.forEach', 'function', 1, callback);\n this._snapshot.docs.forEach(doc => {\n callback.call(\n thisArg,\n this.convertToDocumentImpl(\n doc,\n this.metadata.fromCache,\n this._snapshot.mutatedKeys.has(doc.key)\n )\n );\n });\n }\n\n get query(): firestore.Query {\n return new Query(this._originalQuery, this._firestore, this._converter);\n }\n\n docChanges(\n options?: firestore.SnapshotListenOptions\n ): Array> {\n if (options) {\n validateOptionNames('QuerySnapshot.docChanges', options, [\n 'includeMetadataChanges'\n ]);\n validateNamedOptionalType(\n 'QuerySnapshot.docChanges',\n 'boolean',\n 'includeMetadataChanges',\n options.includeMetadataChanges\n );\n }\n\n const includeMetadataChanges = !!(\n options && options.includeMetadataChanges\n );\n\n if (includeMetadataChanges && this._snapshot.excludesMetadataChanges) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'To include metadata changes with your document changes, you must ' +\n 'also pass { includeMetadataChanges:true } to onSnapshot().'\n );\n }\n\n if (\n !this._cachedChanges ||\n this._cachedChangesIncludeMetadataChanges !== includeMetadataChanges\n ) {\n this._cachedChanges = changesFromSnapshot>(\n this._snapshot,\n includeMetadataChanges,\n this.convertToDocumentImpl.bind(this)\n );\n this._cachedChangesIncludeMetadataChanges = includeMetadataChanges;\n }\n\n return this._cachedChanges;\n }\n\n /** Check the equality. The call can be very expensive. */\n isEqual(other: firestore.QuerySnapshot): boolean {\n if (!(other instanceof QuerySnapshot)) {\n throw invalidClassError('isEqual', 'QuerySnapshot', 1, other);\n }\n\n return (\n this._firestore === other._firestore &&\n queryEquals(this._originalQuery, other._originalQuery) &&\n this._snapshot.isEqual(other._snapshot) &&\n this._converter === other._converter\n );\n }\n\n private convertToDocumentImpl(\n doc: Document,\n fromCache: boolean,\n hasPendingWrites: boolean\n ): QueryDocumentSnapshot {\n return new QueryDocumentSnapshot(\n this._firestore,\n doc.key,\n doc,\n fromCache,\n hasPendingWrites,\n this._converter\n );\n }\n}\n\nexport class CollectionReference extends Query\n implements firestore.CollectionReference {\n constructor(\n readonly _path: ResourcePath,\n firestore: Firestore,\n _converter: firestore.FirestoreDataConverter | null\n ) {\n super(InternalQuery.atPath(_path), firestore, _converter);\n if (_path.length % 2 !== 1) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Invalid collection reference. Collection ' +\n 'references must have an odd number of segments, but ' +\n `${_path.canonicalString()} has ${_path.length}`\n );\n }\n }\n\n get id(): string {\n return this._query.path.lastSegment();\n }\n\n get parent(): firestore.DocumentReference | null {\n const parentPath = this._query.path.popLast();\n if (parentPath.isEmpty()) {\n return null;\n } else {\n return new DocumentReference(\n new DocumentKey(parentPath),\n this.firestore,\n /* converter= */ null\n );\n }\n }\n\n get path(): string {\n return this._query.path.canonicalString();\n }\n\n doc(pathString?: string): firestore.DocumentReference {\n validateBetweenNumberOfArgs('CollectionReference.doc', arguments, 0, 1);\n // We allow omission of 'pathString' but explicitly prohibit passing in both\n // 'undefined' and 'null'.\n if (arguments.length === 0) {\n pathString = AutoId.newId();\n }\n validateArgType(\n 'CollectionReference.doc',\n 'non-empty string',\n 1,\n pathString\n );\n const path = ResourcePath.fromString(pathString!);\n return DocumentReference.forPath(\n this._query.path.child(path),\n this.firestore,\n this._converter\n );\n }\n\n add(value: T): Promise> {\n validateExactNumberOfArgs('CollectionReference.add', arguments, 1);\n const convertedValue = this._converter\n ? this._converter.toFirestore(value)\n : value;\n validateArgType('CollectionReference.add', 'object', 1, convertedValue);\n const docRef = this.doc();\n return docRef.set(value).then(() => docRef);\n }\n\n withConverter(\n converter: firestore.FirestoreDataConverter\n ): firestore.CollectionReference {\n return new CollectionReference(this._path, this.firestore, converter);\n }\n}\n\nfunction validateSetOptions(\n methodName: string,\n options: firestore.SetOptions | undefined\n): firestore.SetOptions {\n if (options === undefined) {\n return {\n merge: false\n };\n }\n\n validateOptionNames(methodName, options, ['merge', 'mergeFields']);\n validateNamedOptionalType(methodName, 'boolean', 'merge', options.merge);\n validateOptionalArrayElements(\n methodName,\n 'mergeFields',\n 'a string or a FieldPath',\n options.mergeFields,\n element =>\n typeof element === 'string' || element instanceof ExternalFieldPath\n );\n\n if (options.mergeFields !== undefined && options.merge !== undefined) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid options passed to function ${methodName}(): You cannot specify both \"merge\" ` +\n `and \"mergeFields\".`\n );\n }\n\n return options;\n}\n\nfunction validateSnapshotOptions(\n methodName: string,\n options: firestore.SnapshotOptions | undefined\n): firestore.SnapshotOptions {\n if (options === undefined) {\n return {};\n }\n\n validateOptionNames(methodName, options, ['serverTimestamps']);\n validateNamedOptionalPropertyEquals(\n methodName,\n 'options',\n 'serverTimestamps',\n options.serverTimestamps,\n ['estimate', 'previous', 'none']\n );\n return options;\n}\n\nfunction validateGetOptions(\n methodName: string,\n options: firestore.GetOptions | undefined\n): void {\n validateOptionalArgType(methodName, 'object', 1, options);\n if (options) {\n validateOptionNames(methodName, options, ['source']);\n validateNamedOptionalPropertyEquals(\n methodName,\n 'options',\n 'source',\n options.source,\n ['default', 'server', 'cache']\n );\n }\n}\n\nfunction validateReference(\n methodName: string,\n documentRef: firestore.DocumentReference,\n firestore: Firestore\n): DocumentKeyReference {\n if (!(documentRef instanceof DocumentKeyReference)) {\n throw invalidClassError(methodName, 'DocumentReference', 1, documentRef);\n } else if (documentRef.firestore !== firestore) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Provided document reference is from a different Firestore instance.'\n );\n } else {\n return documentRef;\n }\n}\n\n/**\n * Calculates the array of firestore.DocumentChange's for a given ViewSnapshot.\n *\n * Exported for testing.\n *\n * @param snapshot The ViewSnapshot that represents the expected state.\n * @param includeMetadataChanges Whether to include metadata changes.\n * @param converter A factory function that returns a QueryDocumentSnapshot.\n * @return An objecyt that matches the firestore.DocumentChange API.\n */\nexport function changesFromSnapshot(\n snapshot: ViewSnapshot,\n includeMetadataChanges: boolean,\n converter: (\n doc: Document,\n fromCache: boolean,\n hasPendingWrite: boolean\n ) => DocSnap\n): Array<{\n type: firestore.DocumentChangeType;\n doc: DocSnap;\n oldIndex: number;\n newIndex: number;\n}> {\n if (snapshot.oldDocs.isEmpty()) {\n // Special case the first snapshot because index calculation is easy and\n // fast\n let lastDoc: Document;\n let index = 0;\n return snapshot.docChanges.map(change => {\n const doc = converter(\n change.doc,\n snapshot.fromCache,\n snapshot.mutatedKeys.has(change.doc.key)\n );\n debugAssert(\n change.type === ChangeType.Added,\n 'Invalid event type for first snapshot'\n );\n debugAssert(\n !lastDoc || newQueryComparator(snapshot.query)(lastDoc, change.doc) < 0,\n 'Got added events in wrong order'\n );\n lastDoc = change.doc;\n return {\n type: 'added' as firestore.DocumentChangeType,\n doc,\n oldIndex: -1,\n newIndex: index++\n };\n });\n } else {\n // A DocumentSet that is updated incrementally as changes are applied to use\n // to lookup the index of a document.\n let indexTracker = snapshot.oldDocs;\n return snapshot.docChanges\n .filter(\n change => includeMetadataChanges || change.type !== ChangeType.Metadata\n )\n .map(change => {\n const doc = converter(\n change.doc,\n snapshot.fromCache,\n snapshot.mutatedKeys.has(change.doc.key)\n );\n let oldIndex = -1;\n let newIndex = -1;\n if (change.type !== ChangeType.Added) {\n oldIndex = indexTracker.indexOf(change.doc.key);\n debugAssert(oldIndex >= 0, 'Index for document not found');\n indexTracker = indexTracker.delete(change.doc.key);\n }\n if (change.type !== ChangeType.Removed) {\n indexTracker = indexTracker.add(change.doc);\n newIndex = indexTracker.indexOf(change.doc.key);\n }\n return { type: resultChangeType(change.type), doc, oldIndex, newIndex };\n });\n }\n}\n\nfunction resultChangeType(type: ChangeType): firestore.DocumentChangeType {\n switch (type) {\n case ChangeType.Added:\n return 'added';\n case ChangeType.Modified:\n case ChangeType.Metadata:\n return 'modified';\n case ChangeType.Removed:\n return 'removed';\n default:\n return fail('Unknown change type: ' + type);\n }\n}\n\n/**\n * Converts custom model object of type T into DocumentData by applying the\n * converter if it exists.\n *\n * This function is used when converting user objects to DocumentData\n * because we want to provide the user with a more specific error message if\n * their set() or fails due to invalid data originating from a toFirestore()\n * call.\n */\nexport function applyFirestoreDataConverter(\n converter: UntypedFirestoreDataConverter | null,\n value: T,\n options?: firestore.SetOptions\n): firestore.DocumentData {\n let convertedValue;\n if (converter) {\n if (options && (options.merge || options.mergeFields)) {\n // Cast to `any` in order to satisfy the union type constraint on\n // toFirestore().\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n convertedValue = (converter as any).toFirestore(value, options);\n } else {\n convertedValue = converter.toFirestore(value);\n }\n } else {\n convertedValue = value as firestore.DocumentData;\n }\n return convertedValue;\n}\n\nfunction contains(obj: object, key: string): obj is { key: unknown } {\n return Object.prototype.hasOwnProperty.call(obj, key);\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { FirebaseApp, FirebaseNamespace } from '@firebase/app-types';\nimport { FirebaseAuthInternalName } from '@firebase/auth-interop-types';\nimport { _FirebaseNamespace } from '@firebase/app-types/private';\nimport { Component, ComponentType, Provider } from '@firebase/component';\nimport {\n CACHE_SIZE_UNLIMITED,\n CollectionReference,\n DocumentReference,\n DocumentSnapshot,\n Firestore,\n Query,\n QueryDocumentSnapshot,\n QuerySnapshot,\n Transaction,\n WriteBatch\n} from './api/database';\nimport { Blob } from './api/blob';\nimport { FieldPath } from './api/field_path';\nimport { GeoPoint } from './api/geo_point';\nimport { Timestamp } from './api/timestamp';\nimport { FieldValue } from './api/field_value';\n\nconst firestoreNamespace = {\n Firestore,\n GeoPoint,\n Timestamp,\n Blob,\n Transaction,\n WriteBatch,\n DocumentReference,\n DocumentSnapshot,\n Query,\n QueryDocumentSnapshot,\n QuerySnapshot,\n CollectionReference,\n FieldPath,\n FieldValue,\n setLogLevel: Firestore.setLogLevel,\n CACHE_SIZE_UNLIMITED\n};\n\n/**\n * Configures Firestore as part of the Firebase SDK by calling registerService.\n *\n * @param firebase The FirebaseNamespace to register Firestore with\n * @param firestoreFactory A factory function that returns a new Firestore\n * instance.\n */\nexport function configureForFirebase(\n firebase: FirebaseNamespace,\n firestoreFactory: (\n app: FirebaseApp,\n auth: Provider\n ) => Firestore\n): void {\n (firebase as _FirebaseNamespace).INTERNAL.registerComponent(\n new Component(\n 'firestore',\n container => {\n const app = container.getProvider('app').getImmediate()!;\n return firestoreFactory(app, container.getProvider('auth-internal'));\n },\n ComponentType.PUBLIC\n ).setServiceProps({ ...firestoreNamespace })\n );\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport firebase from '@firebase/app';\nimport { FirebaseNamespace } from '@firebase/app-types';\n\nimport { Firestore } from './src/api/database';\nimport { MemoryComponentProvider } from './src/core/component_provider';\nimport { configureForFirebase } from './src/config';\n\nimport './register-module';\n\nimport { name, version } from './package.json';\n\n/**\n * Registers the memory-only Firestore build with the components framework.\n */\nexport function registerFirestore(instance: FirebaseNamespace): void {\n configureForFirebase(\n instance,\n (app, auth) => new Firestore(app, auth, new MemoryComponentProvider())\n );\n instance.registerVersion(name, version);\n}\n\nregisterFirestore(firebase);\n"],"names":["SDK_VERSION","firebase","__PRIVATE_logClient","Logger","__PRIVATE_getLogLevel","logLevel","__PRIVATE_logDebug","msg","LogLevel","DEBUG","args","obj","map","__PRIVATE_argToString","debug","__PRIVATE_logError","ERROR","error","value","JSON","stringify","e","fail","__PRIVATE_failure","message","Error","__PRIVATE_hardAssert","assertion","__PRIVATE_debugCast","constructor","__PRIVATE_randomBytes","__PRIVATE_nBytes","crypto","self","msCrypto","bytes","Uint8Array","getRandomValues","__PRIVATE_i","Math","floor","random","[object Object]","__PRIVATE_chars","__PRIVATE_maxMultiple","length","__PRIVATE_autoId","charAt","__PRIVATE_primitiveComparator","left","right","__PRIVATE_arrayEquals","__PRIVATE_comparator","every","index","__PRIVATE_databaseId","persistenceKey","host","ssl","forceLongPolling","this","projectId","database","i","other","__PRIVATE_DatabaseId","__PRIVATE_objectSize","count","key","Object","prototype","hasOwnProperty","call","forEach","fn","__PRIVATE_isEmpty","__PRIVATE_mapKeyFn","__PRIVATE_equalsFn","id","matches","__PRIVATE_inner","__PRIVATE_otherKey","get","push","splice","__PRIVATE__","entries","k","v","Code","OK","CANCELLED","UNKNOWN","INVALID_ARGUMENT","DEADLINE_EXCEEDED","NOT_FOUND","ALREADY_EXISTS","PERMISSION_DENIED","UNAUTHENTICATED","RESOURCE_EXHAUSTED","FAILED_PRECONDITION","ABORTED","OUT_OF_RANGE","UNIMPLEMENTED","INTERNAL","UNAVAILABLE","DATA_LOSS","code","super","toString","name","seconds","nanoseconds","FirestoreError","Timestamp","fromMillis","Date","now","date","getTime","milliseconds","toMillis","__PRIVATE_adjustedSeconds","String","padStart","timestamp","__PRIVATE_SnapshotVersion","__PRIVATE__compareTo","isEqual","segments","offset","__PRIVATE_len","__PRIVATE_BasePath","__PRIVATE_nameOrPath","slice","limit","__PRIVATE_segment","__PRIVATE_construct","size","__PRIVATE_potentialChild","end","p1","p2","min","ResourcePath","__PRIVATE_toArray","join","__PRIVATE_canonicalString","path","indexOf","split","filter","__PRIVATE_identifierRegExp","FieldPath","test","str","replace","__PRIVATE_isValidIdentifier","__PRIVATE_current","__PRIVATE_addCurrentSegment","__PRIVATE_inBackticks","c","next","__PRIVATE_DocumentKey","__PRIVATE_fromString","__PRIVATE_popFirst","collectionId","k1","k2","__PRIVATE_isNullOrUndefined","__PRIVATE_isNegativeZero","collectionGroup","orderBy","filters","startAt","endAt","__PRIVATE_newTarget","__PRIVATE_TargetImpl","__PRIVATE_canonifyTarget","target","__PRIVATE_targetImpl","__PRIVATE_memoizedCanonicalId","canonicalId","f","__PRIVATE_canonifyFilter","field","op","o","__PRIVATE_canonifyOrderBy","dir","__PRIVATE_canonifyBound","__PRIVATE_targetEquals","__PRIVATE_orderByEquals","__PRIVATE_f1","__PRIVATE_f2","FieldFilter","__PRIVATE_valueEquals","__PRIVATE_boundEquals","__PRIVATE_isDocumentTarget","__PRIVATE_isDocumentKey","__PRIVATE_binaryString","base64","__PRIVATE_ByteString","atob","array","fromCharCode","raw","btoa","buffer","charCodeAt","__PRIVATE_RpcCode","targetId","__PRIVATE_purpose","sequenceNumber","__PRIVATE_snapshotVersion","lastLimboFreeSnapshotVersion","resumeToken","__PRIVATE_EMPTY_BYTE_STRING","__PRIVATE_TargetData","__PRIVATE_isPermanentError","__PRIVATE_mapCodeFromRpcCode","RpcCode","root","__PRIVATE_LLRBNode","EMPTY","__PRIVATE_SortedMap","__PRIVATE_insert","copy","__PRIVATE_BLACK","remove","node","cmp","__PRIVATE_prunedNodes","__PRIVATE_minKey","__PRIVATE_maxKey","action","__PRIVATE_inorderTraversal","__PRIVATE_descriptions","__PRIVATE_reverseTraversal","__PRIVATE_SortedMapIterator","__PRIVATE_startKey","__PRIVATE_isReverse","__PRIVATE_nodeStack","pop","result","color","RED","n","__PRIVATE_fixUp","__PRIVATE_isRed","__PRIVATE_moveRedLeft","__PRIVATE_removeMin","__PRIVATE_smallest","__PRIVATE_rotateRight","__PRIVATE_moveRedRight","__PRIVATE_rotateLeft","__PRIVATE_colorFlip","__PRIVATE_nl","__PRIVATE_nr","__PRIVATE_blackDepth","__PRIVATE_check","pow","data","__PRIVATE_elem","cb","range","__PRIVATE_iter","__PRIVATE_getIteratorFrom","__PRIVATE_hasNext","__PRIVATE_getNext","start","__PRIVATE_getIterator","__PRIVATE_SortedSetIterator","has","add","__PRIVATE_SortedSet","__PRIVATE_thisIt","__PRIVATE_otherIt","__PRIVATE_thisElem","__PRIVATE_otherElem","__PRIVATE_res","__PRIVATE_EMPTY_MAYBE_DOCUMENT_MAP","__PRIVATE_maybeDocumentMap","__PRIVATE_nullableMaybeDocumentMap","__PRIVATE_EMPTY_DOCUMENT_MAP","__PRIVATE_documentMap","__PRIVATE_EMPTY_DOCUMENT_VERSION_MAP","__PRIVATE_EMPTY_DOCUMENT_KEY_SET","__PRIVATE_documentKeySet","set","keys","__PRIVATE_EMPTY_TARGET_ID_SET","__PRIVATE_targetIdSet","__PRIVATE_comp","__PRIVATE_d1","__PRIVATE_d2","__PRIVATE_keyedMap","__PRIVATE_sortedSet","__PRIVATE_oldSet","__PRIVATE_DocumentSet","doc","delete","__PRIVATE_thisDoc","__PRIVATE_otherDoc","__PRIVATE_docStrings","__PRIVATE_newSet","__PRIVATE_change","__PRIVATE_oldChange","__PRIVATE_changeMap","type","__PRIVATE_changes","query","docs","__PRIVATE_oldDocs","docChanges","__PRIVATE_mutatedKeys","fromCache","__PRIVATE_syncStateChanged","__PRIVATE_excludesMetadataChanges","documents","__PRIVATE_ViewSnapshot","__PRIVATE_emptySet","hasPendingWrites","__PRIVATE_queryEquals","__PRIVATE_otherChanges","__PRIVATE_targetChanges","__PRIVATE_targetMismatches","__PRIVATE_documentUpdates","__PRIVATE_resolvedLimboDocuments","Map","TargetChange","__PRIVATE_createSynthesizedTargetChangeForCurrentChange","__PRIVATE_RemoteEvent","__PRIVATE_addedDocuments","__PRIVATE_modifiedDocuments","__PRIVATE_removedDocuments","__PRIVATE_updatedTargetIds","removedTargetIds","__PRIVATE_newDoc","__PRIVATE_existenceFilter","state","targetIds","cause","__PRIVATE_snapshotChangesMap","Ht","__PRIVATE__current","__PRIVATE__resumeToken","he","__PRIVATE_pendingResponses","ae","__PRIVATE__hasPendingChanges","__PRIVATE_approximateByteSize","__PRIVATE_documentChanges","__PRIVATE_changeType","__PRIVATE_metadataProvider","__PRIVATE_documentTargetMap","__PRIVATE_docChange","Document","__PRIVATE_addDocumentToTarget","__PRIVATE_NoDocument","__PRIVATE_removeDocumentFromTarget","targetChange","__PRIVATE_forEachTarget","__PRIVATE_targetState","__PRIVATE_ensureTargetState","__PRIVATE_isActiveTarget","__PRIVATE_updateResumeToken","__PRIVATE_recordTargetResponse","__PRIVATE_isPending","__PRIVATE_clearPendingChanges","removeTarget","__PRIVATE_markCurrent","__PRIVATE_resetTarget","__PRIVATE_targetStates","__PRIVATE_watchChange","__PRIVATE_expectedCount","__PRIVATE_targetData","__PRIVATE_targetDataForActiveTarget","__PRIVATE_getCurrentDocumentCountForTarget","__PRIVATE_pendingTargetResets","__PRIVATE_pendingDocumentUpdates","__PRIVATE_targetContainsDocument","__PRIVATE_hasPendingChanges","__PRIVATE_toTargetChange","__PRIVATE_pendingDocumentTargetMapping","__PRIVATE_targets","__PRIVATE_isOnlyLimboTarget","__PRIVATE_forEachWhile","__PRIVATE_remoteEvent","document","__PRIVATE_addDocumentChange","__PRIVATE_ensureDocumentTargetMapping","__PRIVATE_updatedDocument","__PRIVATE_removeDocumentChange","__PRIVATE_getRemoteKeysForTarget","__PRIVATE_recordPendingTargetRequest","__PRIVATE_TargetState","__PRIVATE_targetMapping","__PRIVATE_targetActive","__PRIVATE_getTargetDataForTarget","__PRIVATE_isServerTimestamp","mapValue","fields","__type__","stringValue","__PRIVATE_getLocalWriteTime","__PRIVATE_localWriteTime","__PRIVATE_normalizeTimestamp","__local_write_time__","timestampValue","nanos","__PRIVATE_ISO_TIMESTAMP_REG_EXP","RegExp","__PRIVATE_typeOrder","__PRIVATE_leftType","booleanValue","__PRIVATE_leftTimestamp","__PRIVATE_rightTimestamp","__PRIVATE_normalizeByteString","bytesValue","referenceValue","__PRIVATE_normalizeNumber","geoPointValue","latitude","longitude","integerValue","__PRIVATE_n1","doubleValue","__PRIVATE_n2","isNaN","arrayValue","values","__PRIVATE_leftMap","__PRIVATE_rightMap","__PRIVATE_arrayValueContains","__PRIVATE_haystack","__PRIVATE_needle","find","__PRIVATE_valueCompare","__PRIVATE_rightType","__PRIVATE_leftNumber","__PRIVATE_rightNumber","__PRIVATE_compareTimestamps","__PRIVATE_leftBytes","__PRIVATE_rightBytes","__PRIVATE_compareTo","__PRIVATE_leftPath","__PRIVATE_rightPath","__PRIVATE_leftSegments","__PRIVATE_rightSegments","__PRIVATE_comparison","__PRIVATE_leftArray","__PRIVATE_rightArray","compare","__PRIVATE_leftKeys","__PRIVATE_rightKeys","sort","__PRIVATE_keyCompare","__PRIVATE_canonifyValue","__PRIVATE_normalizedTimestamp","toBase64","__PRIVATE_fromName","__PRIVATE_geoPoint","first","__PRIVATE_sortedKeys","__PRIVATE_fraction","exec","__PRIVATE_nanoStr","substr","Number","__PRIVATE_parsedDate","blob","fromBase64String","fromUint8Array","__PRIVATE_refValue","isInteger","isArray","__PRIVATE_isNullValue","__PRIVATE_isNanValue","__PRIVATE_isMapValue","__PRIVATE_DIRECTIONS","asc","desc","__PRIVATE_OPERATORS","<","<=",">",">=","==","array-contains","in","array-contains-any","__PRIVATE_useProto3Json","__PRIVATE_toInteger","__PRIVATE_toDouble","serializer","Infinity","__PRIVATE_toNumber","MAX_SAFE_INTEGER","MIN_SAFE_INTEGER","__PRIVATE_toTimestamp","toISOString","__PRIVATE_toBytes","toUint8Array","toVersion","version","fromVersion","__PRIVATE_fromTimestamp","__PRIVATE_toResourceName","child","__PRIVATE_toName","__PRIVATE_resourceName","__PRIVATE_resource","__PRIVATE_isValidResourceName","__PRIVATE_toQueryPath","__PRIVATE_getEncodedDatabaseId","__PRIVATE_toMutationDocument","proto","__PRIVATE_toMutation","__PRIVATE_mutation","__PRIVATE_SetMutation","update","__PRIVATE_DeleteMutation","__PRIVATE_PatchMutation","updateMask","__PRIVATE_toDocumentMask","__PRIVATE_fieldMask","__PRIVATE_TransformMutation","transform","fieldTransforms","__PRIVATE_fieldTransform","__PRIVATE_ServerTimestampTransform","fieldPath","setToServerValue","__PRIVATE_ArrayUnionTransformOperation","appendMissingElements","elements","__PRIVATE_ArrayRemoveTransformOperation","removeAllFromArray","__PRIVATE_NumericIncrementTransformOperation","increment","__PRIVATE_operand","__PRIVATE_VerifyMutation","verify","__PRIVATE_precondition","__PRIVATE_isNone","currentDocument","updateTime","exists","__PRIVATE_toDocumentsTarget","__PRIVATE_toQueryTarget","structuredQuery","parent","from","allDescendants","__PRIVATE_popLast","__PRIVATE_lastSegment","where","__PRIVATE_protos","unaryFilter","__PRIVATE_toFieldPathReference","fieldFilter","compositeFilter","__PRIVATE_orderBys","order","__PRIVATE_toPropertyOrder","direction","val","__PRIVATE_toCursor","cursor","before","position","__PRIVATE_canonicalFields","fieldPaths","__PRIVATE_applyTransformOperationToLocalView","previousValue","__previous_value__","__PRIVATE_applyArrayUnionTransformOperation","__PRIVATE_applyArrayRemoveTransformOperation","__PRIVATE_baseValue","__PRIVATE_computeTransformOperationBaseValue","__PRIVATE_sum","asNumber","__PRIVATE_applyTransformOperationToRemoteDocument","__PRIVATE_transformResult","__PRIVATE_TransformOperation","__PRIVATE_coercedFieldValuesArray","__PRIVATE_toUnion","some","element","__PRIVATE_toRemove","__PRIVATE_isPrefixOf","__PRIVATE_l","r","transformResults","Precondition","Be","__PRIVATE_preconditionIsValidForDocument","__PRIVATE_maybeDoc","__PRIVATE_applyMutationToRemoteDocument","__PRIVATE_mutationResult","hasCommittedMutations","__PRIVATE_UnknownDocument","__PRIVATE_newData","__PRIVATE_patchDocument","__PRIVATE_requireDocument","__PRIVATE_baseDoc","__PRIVATE_serverTransformResults","__PRIVATE_transformObject","__PRIVATE_applyMutationToLocalView","__PRIVATE_getPostMutationVersion","Ge","__PRIVATE_extractMutationBaseValue","__PRIVATE_baseObject","__PRIVATE_existingValue","__PRIVATE_coercedValue","__PRIVATE_ObjectValueBuilder","__PRIVATE_build","__PRIVATE_mutationEquals","__PRIVATE_fieldTransformEquals","__PRIVATE_Mutation","__PRIVATE_builder","newValue","__PRIVATE_ObjectValue","empty","__PRIVATE_setOverlay","__PRIVATE_currentLevel","__PRIVATE_overlayMap","__PRIVATE_currentSegment","currentValue","__PRIVATE_mergedResult","__PRIVATE_applyOverlay","__PRIVATE_emptyPath","__PRIVATE_currentPath","__PRIVATE_currentOverlays","__PRIVATE_modified","__PRIVATE_resultAtPath","__PRIVATE_pathSegment","__PRIVATE_nested","__PRIVATE_extractFieldMask","__PRIVATE_nestedFields","__PRIVATE_nestedPath","__PRIVATE_FieldMask","__PRIVATE_objectValue","options","__PRIVATE_hasLocalMutations","__PRIVATE_MaybeDocument","__PRIVATE_explicitOrderBy","__PRIVATE_limitType","__PRIVATE_assertValidBound","Query","__PRIVATE_memoizedOrderBy","__PRIVATE_inequalityField","__PRIVATE_getInequalityFilterField","__PRIVATE_firstOrderByField","__PRIVATE_getFirstOrderByField","__PRIVATE_isKeyField","__PRIVATE_OrderBy","__PRIVATE_keyField","__PRIVATE_foundKeyOrdering","__PRIVATE_lastDirection","__PRIVATE_newFilters","concat","__PRIVATE_newOrderBy","bound","__PRIVATE_isInequality","__PRIVATE_operators","__PRIVATE_toTarget","__PRIVATE_memoizedTarget","__PRIVATE_Bound","__PRIVATE_canonifyQuery","__PRIVATE_stringifyQuery","__PRIVATE_stringifyFilter","__PRIVATE_stringifyOrderBy","__PRIVATE_stringifyTarget","__PRIVATE_queryMatches","__PRIVATE_docPath","__PRIVATE_hasCollectionId","__PRIVATE_isImmediateParentOf","__PRIVATE_sortsBeforeDocument","__PRIVATE_newQueryComparator","__PRIVATE_comparedOnKeyField","__PRIVATE_compareDocs","__PRIVATE_KeyFieldInFilter","__PRIVATE_KeyFieldFilter","__PRIVATE_ArrayContainsFilter","__PRIVATE_InFilter","__PRIVATE_ArrayContainsAnyFilter","__PRIVATE_matchesComparison","p","__PRIVATE_orderByComponent","component","v1","v2","batchId","baseMutations","mutations","__PRIVATE_docKey","__PRIVATE_batchResult","__PRIVATE_mutationResults","__PRIVATE_maybeDocs","__PRIVATE_mutatedDocuments","m","__PRIVATE_mutatedDocument","__PRIVATE_applyToLocalView","reduce","batch","__PRIVATE_commitVersion","__PRIVATE_docVersions","results","__PRIVATE_versionMap","__PRIVATE_MutationBatchResult","callback","__PRIVATE_isDone","__PRIVATE_nextCallback","__PRIVATE_catchCallback","__PRIVATE_nextFn","__PRIVATE_catchFn","__PRIVATE_callbackAttached","__PRIVATE_wrapFailure","__PRIVATE_wrapSuccess","PersistencePromise","resolve","reject","Promise","__PRIVATE_wrapUserFunction","all","__PRIVATE_resolvedCount","done","err","__PRIVATE_predicates","predicate","__PRIVATE_isTrue","collection","__PRIVATE_promises","s","__PRIVATE_waitFor","__PRIVATE_remoteDocumentCache","__PRIVATE_mutationQueue","__PRIVATE_indexManager","transaction","__PRIVATE_getAllMutationBatchesAffectingDocumentKey","__PRIVATE_batches","__PRIVATE_getDocumentInternal","__PRIVATE_inBatches","__PRIVATE_getEntry","__PRIVATE_localView","getEntries","__PRIVATE_getLocalViewOfDocuments","__PRIVATE_baseDocs","__PRIVATE_getAllMutationBatchesAffectingDocumentKeys","__PRIVATE_applyLocalMutationsToDocuments","__PRIVATE_sinceReadTime","__PRIVATE_isDocumentQuery","__PRIVATE_getDocumentsMatchingDocumentQuery","__PRIVATE_isCollectionGroupQuery","__PRIVATE_getDocumentsMatchingCollectionGroupQuery","__PRIVATE_getDocumentsMatchingCollectionQuery","__PRIVATE_getDocument","__PRIVATE_getCollectionParents","__PRIVATE_parents","__PRIVATE_collectionQuery","__PRIVATE_asCollectionQueryAtPath","__PRIVATE_mutationBatches","__PRIVATE_getDocumentsMatchingQuery","__PRIVATE_queryResults","__PRIVATE_getAllMutationBatchesAffectingQuery","__PRIVATE_matchingMutationBatches","__PRIVATE_addMissingBaseDocuments","__PRIVATE_mergedDocuments","__PRIVATE_mutatedDoc","__PRIVATE_existingDocuments","__PRIVATE_missingBaseDocEntriesForPatching","__PRIVATE_missingBaseDocs","__PRIVATE_addedKeys","__PRIVATE_removedKeys","__PRIVATE_viewSnapshot","__PRIVATE_LocalViewChanges","__PRIVATE_sequenceNumberSyncer","__PRIVATE_sequenceNumberHandler","__PRIVATE_setPreviousValue","__PRIVATE_writeNewSequenceNumber","__PRIVATE_writeSequenceNumber","__PRIVATE_externalPreviousValue","max","__PRIVATE_nextValue","__PRIVATE_ListenSequence","promise","__PRIVATE_queue","__PRIVATE_timerId","__PRIVATE_initialDelayMs","__PRIVATE_backoffFactor","__PRIVATE_maxDelayMs","reset","__PRIVATE_currentBaseMs","cancel","__PRIVATE_desiredDelayWithJitterMs","__PRIVATE_jitterDelayMs","__PRIVATE_delaySoFarMs","__PRIVATE_lastAttemptTime","__PRIVATE_remainingDelayMs","__PRIVATE_timerPromise","__PRIVATE_enqueueAfterDelay","__PRIVATE_skipDelay","__PRIVATE_MemoryCollectionParentIndex","collectionPath","__PRIVATE_collectionParentIndex","parentPath","__PRIVATE_existingParents","__PRIVATE_added","__PRIVATE_lastId","__PRIVATE_TargetIdGenerator","__PRIVATE_isIndexedDbTransactionError","__PRIVATE_getWindow","window","__PRIVATE_asyncQueue","__PRIVATE_targetTimeMs","__PRIVATE_removalCallback","__PRIVATE_Deferred","__PRIVATE_deferred","then","bind","catch","__PRIVATE_delayMs","__PRIVATE_delayedOp","__PRIVATE_DelayedOperation","__PRIVATE_timerHandle","setTimeout","__PRIVATE_handleDelayElapsed","reason","clearTimeout","__PRIVATE_enqueueAndForget","__PRIVATE_ExponentialBackoff","__PRIVATE_backoff","__PRIVATE_skipBackoff","addEventListener","__PRIVATE_visibilityHandler","Xs","__PRIVATE__isShuttingDown","enqueue","__PRIVATE_verifyNotFailed","__PRIVATE_enqueueInternal","removeEventListener","__PRIVATE_enqueueEvenAfterShutdown","__PRIVATE_retryableOps","__PRIVATE_retryNextOp","shift","__PRIVATE_backoffAndRun","__PRIVATE_newTail","__PRIVATE_tail","__PRIVATE_operationInProgress","stack","includes","__PRIVATE_timerIdsToSkip","__PRIVATE_createAndSchedule","__PRIVATE_removedOp","__PRIVATE_removeDelayedOperation","__PRIVATE_delayedOperations","__PRIVATE_currentTail","__PRIVATE_lastTimerId","__PRIVATE_drain","a","b","__PRIVATE_wrapInUserErrorIfRecoverable","__PRIVATE_cacheSizeCollectionThreshold","__PRIVATE_percentileToCollect","__PRIVATE_maximumSequenceNumbersToCollect","__PRIVATE_cacheSize","__PRIVATE_LruParams","__PRIVATE_DEFAULT_COLLECTION_PERCENTILE","__PRIVATE_DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT","__PRIVATE_DEFAULT_CACHE_SIZE_BYTES","__PRIVATE_COLLECTION_DISABLED","persistence","__PRIVATE_queryEngine","__PRIVATE_initialUser","__PRIVATE_ObjectMap","t","__PRIVATE_getMutationQueue","__PRIVATE_remoteDocuments","__PRIVATE_getRemoteDocumentCache","__PRIVATE_targetCache","__PRIVATE_getTargetCache","__PRIVATE_localDocuments","__PRIVATE_LocalDocumentsView","__PRIVATE_getIndexManager","__PRIVATE_setLocalDocumentsView","user","__PRIVATE_newMutationQueue","__PRIVATE_newLocalDocuments","runTransaction","txn","__PRIVATE_oldBatches","__PRIVATE_getAllMutationBatches","__PRIVATE_promisedOldBatches","__PRIVATE_newBatches","__PRIVATE_removedBatchIds","__PRIVATE_addedBatchIds","__PRIVATE_changedKeys","__PRIVATE_getDocuments","__PRIVATE_affectedDocuments","xi","Oi","Li","__PRIVATE_existingDocs","__PRIVATE_addMutationBatch","__PRIVATE_applyToLocalDocumentSet","Bi","__PRIVATE_affected","__PRIVATE_documentBuffer","__PRIVATE_newChangeBuffer","Wi","__PRIVATE_applyWriteToRemoteDocuments","apply","__PRIVATE_performConsistencyCheck","__PRIVATE_affectedKeys","__PRIVATE_lookupMutationBatch","__PRIVATE_removeMutationBatch","__PRIVATE_getHighestUnacknowledgedBatchId","__PRIVATE_getLastRemoteSnapshotVersion","__PRIVATE_remoteVersion","__PRIVATE_newTargetDataByTargetMap","__PRIVATE_targetDataByTarget","__PRIVATE_oldTargetData","__PRIVATE_removeMatchingKeys","__PRIVATE_addMatchingKeys","__PRIVATE_newTargetData","__PRIVATE_withResumeToken","__PRIVATE_withSequenceNumber","__PRIVATE_currentSequenceNumber","__PRIVATE_LocalStoreImpl","__PRIVATE_shouldPersistTargetData","__PRIVATE_updateTargetData","__PRIVATE_changedDocs","__PRIVATE_updatedKeys","__PRIVATE_existingDoc","__PRIVATE_removeEntry","__PRIVATE_addEntry","__PRIVATE_referenceDelegate","__PRIVATE_updateLimboDocument","__PRIVATE_updateRemoteVersion","lastRemoteSnapshotVersion","__PRIVATE_setTargetsMetadata","__PRIVATE_toMicroseconds","__PRIVATE_RESUME_TOKEN_MAX_AGE_MICROS","__PRIVATE_viewChanges","__PRIVATE_viewChange","__PRIVATE_addReference","__PRIVATE_removeReference","e_39","__PRIVATE_updatedTargetData","__PRIVATE_withLastLimboFreeSnapshotVersion","__PRIVATE_afterBatchId","__PRIVATE_getNextMutationBatchAfterBatchId","__PRIVATE_getTargetData","__PRIVATE_cached","__PRIVATE_allocateTargetId","__PRIVATE_addTargetData","__PRIVATE_cachedTargetData","__PRIVATE_targetIdByTarget","__PRIVATE_keepPersistedTargetData","mode","__PRIVATE_usePreviousResults","__PRIVATE_remoteKeys","__PRIVATE_getMatchingKeysForTargetId","yr","__PRIVATE_docKeys","__PRIVATE_promiseChain","__PRIVATE_remoteDoc","__PRIVATE_ackVersion","__PRIVATE_applyToRemoteDocument","__PRIVATE_garbageCollector","__PRIVATE_collect","async","__PRIVATE_ignoreIfPrimaryLeaseLoss","__PRIVATE_DocReference","__PRIVATE_compareByKey","__PRIVATE_compareByTargetId","__PRIVATE_refsByKey","ref","__PRIVATE_refsByTarget","__PRIVATE_removeRef","__PRIVATE_emptyKey","__PRIVATE_startRef","__PRIVATE_endRef","__PRIVATE_forEachInRange","__PRIVATE_firstRef","__PRIVATE_firstAfterOrEqual","__PRIVATE_targetOrBatchId","__PRIVATE_validateNoArgs","functionName","__PRIVATE_formatPlural","__PRIVATE_validateExactNumberOfArgs","__PRIVATE_numberOfArgs","__PRIVATE_validateAtLeastNumberOfArgs","__PRIVATE_minNumberOfArgs","__PRIVATE_validateBetweenNumberOfArgs","__PRIVATE_maxNumberOfArgs","__PRIVATE_validateArgType","__PRIVATE_argument","__PRIVATE_validateType","__PRIVATE_ordinal","__PRIVATE_validateOptionalArgType","__PRIVATE_validateNamedType","__PRIVATE_optionName","__PRIVATE_validateNamedOptionalType","__PRIVATE_validateNamedOptionalPropertyEquals","__PRIVATE_inputName","input","__PRIVATE_expected","__PRIVATE_expectedDescription","__PRIVATE_valueDescription","__PRIVATE_actualDescription","__PRIVATE_validateStringEnum","__PRIVATE_enums","__PRIVATE_isPlainObject","description","getPrototypeOf","substring","Array","__PRIVATE_customObjectName","__PRIVATE_validateDefined","__PRIVATE_validateOptionNames","__PRIVATE_optionNames","__PRIVATE_invalidClassError","__PRIVATE_validatePositiveNumber","num","__PRIVATE_assertUint8ArrayAvailable","__PRIVATE_assertBase64Available","__PRIVATE_byteString","__PRIVATE__byteString","arguments","Blob","fieldNames","__PRIVATE_minNumberOfElements","__PRIVATE__internalPath","__PRIVATE_InternalFieldPath","__PRIVATE_BaseFieldPath","__PRIVATE_RESERVED","__PRIVATE__methodName","__PRIVATE_SerializableFieldValue","context","__PRIVATE_dataSource","__PRIVATE_createError","__PRIVATE_DeleteFieldValueImpl","__PRIVATE_createSentinelChildContext","__PRIVATE_fieldValue","__PRIVATE_arrayElement","__PRIVATE_ParseContext","Wr","Gr","settings","__PRIVATE_targetDoc","methodName","Kr","ignoreUndefinedProperties","FieldTransform","__PRIVATE_ServerTimestampFieldValueImpl","__PRIVATE__elements","__PRIVATE_parseContext","__PRIVATE_parsedElements","__PRIVATE_parseData","arrayUnion","__PRIVATE__operand","__PRIVATE_numericIncrement","__PRIVATE_FieldValueDelegate","__PRIVATE_ArrayUnionFieldValueImpl","__PRIVATE_ArrayRemoveFieldValueImpl","__PRIVATE_NumericIncrementFieldValueImpl","__PRIVATE__delegate","FieldValue","__PRIVATE__toFieldTransform","isFinite","__PRIVATE__lat","__PRIVATE__long","__PRIVATE_newSerializer","__PRIVATE_JsonProtoSerializer","__PRIVATE_RESERVED_FIELD_REGEX","__PRIVATE__databaseId","__PRIVATE__key","__PRIVATE__converter","__PRIVATE_isWrite","__PRIVATE_validatePath","configuration","__PRIVATE_childPath","__PRIVATE_contextWith","__PRIVATE_validatePathSegment","__PRIVATE_hasConverter","ao","__PRIVATE_parseSetData","__PRIVATE_userDataReader","__PRIVATE_createContext","merge","mergeFields","__PRIVATE_validatePlainObject","__PRIVATE_updateData","__PRIVATE_parseObject","__PRIVATE_validatedFieldPaths","__PRIVATE_stringOrFieldPath","__PRIVATE_fieldPathFromDotSeparatedString","contains","__PRIVATE_fieldMaskContains","__PRIVATE_covers","__PRIVATE_ParsedSetData","__PRIVATE_parseUpdateData","__PRIVATE_fieldMaskPaths","__PRIVATE_childContext","__PRIVATE_childContextForFieldPath","__PRIVATE_parsedValue","mask","__PRIVATE_ParsedUpdateData","__PRIVATE_parseUpdateVarargs","moreFieldsAndValues","__PRIVATE_fieldPathFromArgument","__PRIVATE_parseQueryValue","__PRIVATE_allowArrays","__PRIVATE_looksLikeJsonObject","__PRIVATE_entryIndex","__PRIVATE_parsedEntry","__PRIVATE_childContextForArray","nullValue","fromDate","GeoPoint","__PRIVATE_DocumentKeyReference","__PRIVATE_thisDb","__PRIVATE_otherDb","__PRIVATE_childContextForField","search","__PRIVATE_hasPath","__PRIVATE_hasDocument","uid","__PRIVATE_isAuthenticated","__PRIVATE_otherUser","User","__PRIVATE_authHeaders","Authorization","__PRIVATE_changeListener","__PRIVATE_authProvider","__PRIVATE_tokenListener","__PRIVATE_tokenCounter","currentUser","__PRIVATE_getUser","__PRIVATE_receivedInitialUser","auth","getImmediate","optional","addAuthTokenListener","Io","__PRIVATE_initialTokenCounter","forceRefresh","getToken","__PRIVATE_tokenData","accessToken","__PRIVATE_OAuthToken","removeAuthTokenListener","__PRIVATE_currentUid","getUid","__PRIVATE_gapi","__PRIVATE_sessionIndex","__PRIVATE_FIRST_PARTY","do","headers","X-Goog-AuthUser","__PRIVATE_authHeader","__PRIVATE_getAuthHeaderValueForFirstParty","__PRIVATE_FirstPartyToken","__PRIVATE_connectionTimerId","__PRIVATE_idleTimerId","__PRIVATE_connection","__PRIVATE_credentialsProvider","listener","__PRIVATE_performBackoff","__PRIVATE_isStarted","close","__PRIVATE_isOpen","__PRIVATE_idleTimer","__PRIVATE_handleIdleCloseTimer","__PRIVATE_cancelIdleCheck","stream","send","__PRIVATE_finalState","__PRIVATE_closeCount","__PRIVATE_resetToMax","__PRIVATE_invalidateToken","__PRIVATE_tearDown","__PRIVATE_onClose","__PRIVATE_dispatchIfNotClosed","__PRIVATE_getCloseGuardedDispatcher","token","__PRIVATE_startStream","__PRIVATE_rpcError","__PRIVATE_handleStreamClose","__PRIVATE_startRpc","__PRIVATE_onOpen","onMessage","__PRIVATE_startCloseCount","credentials","__PRIVATE_PersistentStream","__PRIVATE_openStream","__PRIVATE_watchChangeProto","targetChangeType","__PRIVATE_causeProto","status","__PRIVATE_WatchTargetChange","documentChange","__PRIVATE_entityChange","__PRIVATE_DocumentWatchChange","documentDelete","__PRIVATE_docDelete","readTime","documentRemove","__PRIVATE_docRemove","ExistenceFilter","__PRIVATE_ExistenceFilterChange","__PRIVATE_fromWatchChange","snapshot","__PRIVATE_onWatchChange","request","addTarget","labels","goog-listen-tags","__PRIVATE_toListenRequestLabels","__PRIVATE_sendRequest","Jo","__PRIVATE_handshakeComplete_","lastStreamToken","__PRIVATE_writeMutations","__PRIVATE_responseProto","streamToken","commitTime","__PRIVATE_MutationResult","__PRIVATE_fromWriteResults","writeResults","__PRIVATE_onMutationResult","__PRIVATE_onHandshakeComplete","writes","__PRIVATE_terminated","__PRIVATE_rpcName","__PRIVATE_verifyNotTerminated","__PRIVATE_invokeRPC","__PRIVATE_invokeStreamingRPC","__PRIVATE_datastore","Set","__PRIVATE_ensureCommitNotCalled","__PRIVATE_datastoreImpl","params","response","found","missing","__PRIVATE_fromMaybeDocument","__PRIVATE_recordVersion","write","__PRIVATE_toMutations","__PRIVATE_writtenDocs","__PRIVATE_preconditionForUpdate","__PRIVATE_lastWriteError","__PRIVATE_unwritten","__PRIVATE_readVersions","__PRIVATE_committed","__PRIVATE_docVersion","__PRIVATE_existingVersion","__PRIVATE_none","__PRIVATE_onlineStateHandler","__PRIVATE_watchStreamFailures","__PRIVATE_setAndBroadcast","__PRIVATE_onlineStateTimer","__PRIVATE_logClientOfflineWarningIfNecessary","__PRIVATE_clearOnlineStateTimer","__PRIVATE_newState","__PRIVATE_shouldWarnClientIsOffline","details","__PRIVATE_localStore","__PRIVATE_connectivityMonitor","__PRIVATE_addCallback","__PRIVATE_canUseNetwork","__PRIVATE_restartNetwork","__PRIVATE_onlineStateTracker","__PRIVATE_OnlineStateTracker","__PRIVATE_watchStream","__PRIVATE_PersistentListenStream","Go","__PRIVATE_onWatchStreamOpen","Bo","__PRIVATE_onWatchStreamClose","zo","__PRIVATE_onWatchStreamChange","__PRIVATE_writeStream","__PRIVATE_PersistentWriteStream","__PRIVATE_onWriteStreamOpen","__PRIVATE_onWriteStreamClose","eh","__PRIVATE_onWriteHandshakeComplete","th","enableNetwork","__PRIVATE_offlineCauses","__PRIVATE_enableNetworkInternal","__PRIVATE_shouldStartWatchStream","__PRIVATE_startWatchStream","__PRIVATE_fillWritePipeline","__PRIVATE_disableNetworkInternal","stop","__PRIVATE_writePipeline","__PRIVATE_cleanUpWatchStreamState","__PRIVATE_shutdown","__PRIVATE_listenTargets","__PRIVATE_sendWatchRequest","__PRIVATE_sendUnwatchRequest","__PRIVATE_markIdle","__PRIVATE_syncEngine","__PRIVATE_watchChangeAggregator","__PRIVATE_watch","__PRIVATE_unwatch","__PRIVATE_WatchChangeAggregator","__PRIVATE_handleWatchStreamStart","__PRIVATE_handleWatchStreamFailure","__PRIVATE_handleTargetError","__PRIVATE_disableNetworkUntilRecovery","__PRIVATE_handleDocumentChange","__PRIVATE_handleExistenceFilter","__PRIVATE_handleTargetChange","__PRIVATE_raiseWatchSnapshot","__PRIVATE_enqueueRetryable","__PRIVATE_createRemoteEvent","__PRIVATE_requestTargetData","__PRIVATE_applyRemoteEvent","n_37","__PRIVATE_rejectListen","__PRIVATE_lastBatchIdRetrieved","__PRIVATE_canAddToWritePipeline","__PRIVATE_nextMutationBatch","__PRIVATE_addToWritePipeline","__PRIVATE_shouldStartWriteStream","__PRIVATE_startWriteStream","__PRIVATE_handshakeComplete","__PRIVATE_writeHandshake","t_51","__PRIVATE_success","__PRIVATE_executeWithRecovery","__PRIVATE_applySuccessfulWrite","__PRIVATE_handleWriteError","__PRIVATE_inhibitBackoff","__PRIVATE_rejectFailedWrite","Transaction","__PRIVATE_verifyOperationInProgress","__PRIVATE_handleCredentialChange","isPrimary","activeTargetIds","updateTimeMs","__PRIVATE_LocalClientState","__PRIVATE_localState","__PRIVATE_addQueryTarget","__PRIVATE_queryState","__PRIVATE_removeQueryTarget","onlineState","__PRIVATE__syncedDocuments","__PRIVATE_docComparator","__PRIVATE_documentSet","Oa","__PRIVATE_previousChanges","__PRIVATE_changeSet","__PRIVATE_DocumentChangeSet","__PRIVATE_oldDocumentSet","__PRIVATE_newMutatedKeys","__PRIVATE_newDocumentSet","__PRIVATE_needsRefill","__PRIVATE_lastDocInLimit","__PRIVATE_hasLimitToFirst","last","__PRIVATE_firstDocInLimit","__PRIVATE_hasLimitToLast","__PRIVATE_newMaybeDoc","__PRIVATE_oldDoc","__PRIVATE_oldDocHadPendingMutations","__PRIVATE_newDocHasPendingMutations","__PRIVATE_changeApplied","track","__PRIVATE_shouldWaitForSyncedDocument","xa","Ma","Ba","Mt","__PRIVATE_updateLimboDocuments","__PRIVATE_getChanges","__PRIVATE_c1","__PRIVATE_c2","__PRIVATE_applyTargetChange","__PRIVATE_limboChanges","__PRIVATE_newSyncState","__PRIVATE_limboDocuments","__PRIVATE_syncState","ja","__PRIVATE_applyChanges","__PRIVATE_oldLimboDocuments","__PRIVATE_shouldBeInLimbo","__PRIVATE_RemovedLimboDocument","__PRIVATE_AddedLimboDocument","__PRIVATE_queryResult","__PRIVATE_computeDocChanges","__PRIVATE_fromInitialDocuments","updateFunction","__PRIVATE_runWithBackOff","__PRIVATE_userPromise","__PRIVATE_tryRunUpdateFunction","commit","__PRIVATE_commitError","__PRIVATE_handleTransactionError","__PRIVATE_userPromiseError","__PRIVATE_retries","__PRIVATE_isRetryableTransactionError","view","__PRIVATE_remoteStore","__PRIVATE_sharedClientState","__PRIVATE_maxConcurrentLimboResolutions","q","__PRIVATE_ReferenceSet","__PRIVATE_forSyncEngine","wu","__PRIVATE_syncEngineListener","__PRIVATE_assertSubscribed","__PRIVATE_queryView","__PRIVATE_queryViewsByQuery","__PRIVATE_addLocalQueryTarget","__PRIVATE_computeInitialSnapshot","__PRIVATE_allocateTarget","__PRIVATE_initializeViewAndComputeSnapshot","__PRIVATE_isPrimaryClient","listen","__PRIVATE_executeQuery","__PRIVATE_View","__PRIVATE_viewDocChanges","__PRIVATE_synthesizedTargetChange","__PRIVATE_updateTrackedLimbos","__PRIVATE_QueryView","__PRIVATE_queriesByTarget","__PRIVATE_queries","__PRIVATE_removeLocalQueryTarget","__PRIVATE_isActiveQueryTarget","__PRIVATE_releaseTarget","__PRIVATE_clearQueryState","__PRIVATE_unlisten","__PRIVATE_removeAndCleanupTarget","__PRIVATE_userCallback","__PRIVATE_localWrite","__PRIVATE_addPendingMutation","__PRIVATE_addMutationCallback","__PRIVATE_emitNewSnapsAndNotifyLocalStore","__PRIVATE_TransactionRunner","run","__PRIVATE_limboResolution","__PRIVATE_activeLimboResolutionsByTarget","__PRIVATE_receivedDocument","source","__PRIVATE_newViewSnapshots","__PRIVATE_applyOnlineStateChange","__PRIVATE_onOnlineStateChange","__PRIVATE_updateQueryState","__PRIVATE_limboKey","event","__PRIVATE_activeLimboTargetsByKey","__PRIVATE_pumpEnqueuedLimboResolutions","__PRIVATE_mutationBatchResult","__PRIVATE_acknowledgeBatch","__PRIVATE_processUserCallback","__PRIVATE_triggerPendingWritesCallbacks","__PRIVATE_updateMutationState","__PRIVATE_rejectBatch","__PRIVATE_highestBatchId","__PRIVATE_callbacks","__PRIVATE_pendingWritesCallbacks","__PRIVATE_firestoreError","__PRIVATE_errorMessage","clear","__PRIVATE_newCallbacks","__PRIVATE_mutationUserCallbacks","__PRIVATE_toKey","__PRIVATE_onWatchError","__PRIVATE_limboDocumentRefs","__PRIVATE_removeReferencesForId","__PRIVATE_containsKey","__PRIVATE_removeLimboTarget","__PRIVATE_limboTargetId","__PRIVATE_limboChange","__PRIVATE_trackLimboChange","__PRIVATE_enqueuedLimboResolutions","__PRIVATE_limboTargetIdGenerator","__PRIVATE_LimboResolution","__PRIVATE_atPath","__PRIVATE_INVALID","__PRIVATE_newSnaps","__PRIVATE_docChangesInAllViews","__PRIVATE_queriesProcessed","__PRIVATE_fromSnapshot","__PRIVATE_notifyLocalViewChanges","__PRIVATE_fnName","__PRIVATE_handleUserChange","__PRIVATE_rejectOutstandingPendingWritesCallbacks","disableNetwork","__PRIVATE_keySet","__PRIVATE_unionWith","__PRIVATE_syncedDocuments","subscribe","__PRIVATE_firstListen","__PRIVATE_queryInfo","__PRIVATE_QueryListenersInfo","__PRIVATE_viewSnap","onError","listeners","__PRIVATE_onViewSnapshot","__PRIVATE_raiseSnapshotsInSyncEvent","__PRIVATE_lastListen","__PRIVATE_viewSnaps","__PRIVATE_raisedEvent","observer","__PRIVATE_snapshotsInSyncListeners","__PRIVATE_queryObserver","__PRIVATE_snap","includeMetadataChanges","__PRIVATE_raisedInitialEvent","__PRIVATE_shouldRaiseEvent","__PRIVATE_shouldRaiseInitialEvent","__PRIVATE_raiseInitialEvent","__PRIVATE_maybeOnline","__PRIVATE_waitForSyncWhenOnline","__PRIVATE_hasPendingWritesChanged","__PRIVATE_localDocumentsView","__PRIVATE_matchesAllDocuments","__PRIVATE_executeFullCollectionScan","__PRIVATE_previousResults","__PRIVATE_applyQuery","__PRIVATE_updatedResults","__PRIVATE_sortedPreviousResults","__PRIVATE_limboFreeSnapshotVersion","__PRIVATE_docAtLimitEdge","__PRIVATE_nextBatchId","__PRIVATE_MutationBatch","__PRIVATE_batchesByDocumentKey","__PRIVATE_addToCollectionParentIndex","__PRIVATE_findMutationBatch","__PRIVATE_rawIndex","__PRIVATE_indexOfBatchId","__PRIVATE_documentKey","POSITIVE_INFINITY","__PRIVATE_documentKeys","__PRIVATE_uniqueBatchIDs","__PRIVATE_findMutationBatches","prefix","__PRIVATE_immediateChildrenPathLength","__PRIVATE_startPath","__PRIVATE_rowKeyPath","__PRIVATE_batchIDs","__PRIVATE_indexOfExistingBatchId","__PRIVATE_references","__PRIVATE_markPotentiallyOrphaned","__PRIVATE_sizer","__PRIVATE_entry","__PRIVATE_previousSize","__PRIVATE_currentSize","oc","__PRIVATE_maybeDocument","iterator","__PRIVATE_MemoryRemoteDocumentCache","__PRIVATE_RemoteDocumentChangeBuffer","__PRIVATE_documentCache","__PRIVATE__readTime","__PRIVATE_assertNotApplied","__PRIVATE_bufferedEntry","__PRIVATE_getFromCache","__PRIVATE_getAllFromCache","__PRIVATE_changesApplied","__PRIVATE_forTargetCache","__PRIVATE_highestSequenceNumber","highestTargetId","__PRIVATE_targetIdGenerator","highestListenSequenceNumber","__PRIVATE_saveTargetData","targetCount","upperBound","__PRIVATE_removals","__PRIVATE_removeMatchingKeysForTargetId","__PRIVATE_addReferences","__PRIVATE_removeReferences","__PRIVATE_matchingKeys","__PRIVATE_referencesForId","__PRIVATE_referenceDelegateFactory","__PRIVATE__started","__PRIVATE_MemoryTargetCache","__PRIVATE_MemoryIndexManager","__PRIVATE_documentSize","Cc","__PRIVATE_mutationQueues","__PRIVATE_MemoryMutationQueue","__PRIVATE_transactionOperation","__PRIVATE_MemoryTransaction","__PRIVATE_listenSequence","__PRIVATE_onTransactionStarted","__PRIVATE_onTransactionCommitted","__PRIVATE_toPromise","__PRIVATE_raiseOnCommittedEvent","__PRIVATE_or","__PRIVATE_onCommittedListeners","__PRIVATE_MemoryEagerDelegate","Bc","__PRIVATE__orphanedDocuments","__PRIVATE_localViewReferences","__PRIVATE_orphanedDocuments","cache","__PRIVATE_removeTargetData","__PRIVATE_changeBuffer","__PRIVATE_isReferenced","__PRIVATE_mutationQueuesContainKey","__PRIVATE_sendFn","__PRIVATE_closeFn","__PRIVATE_wrappedOnOpen","__PRIVATE_wrappedOnClose","__PRIVATE_wrappedOnMessage","__PRIVATE_RPC_NAME_REST_MAPPING","BatchGetDocuments","Commit","__PRIVATE_X_GOOG_API_CLIENT_VALUE","info","__PRIVATE_baseUrl","__PRIVATE_header","url","__PRIVATE_makeUrl","__PRIVATE_xhr","XhrIo","listenOnce","EventType","COMPLETE","getLastErrorCode","ErrorCode","NO_ERROR","json","getResponseJson","TIMEOUT","HTTP_ERROR","getStatus","getResponseText","__PRIVATE_responseError","__PRIVATE_firestoreErrorCode","__PRIVATE_serverError","toLowerCase","__PRIVATE_jsonObj","__PRIVATE_requestString","Content-Type","__PRIVATE_modifyHeadersForRequest","__PRIVATE_urlParts","__PRIVATE_webchannelTransport","createWebChannelTransport","httpSessionIdParam","initMessageHeaders","messageUrlParams","sendRawJson","supportsCrossDomainXhr","internalChannelParams","forwardChannelRequestTimeoutMs","isMobileCordova","isReactNative","isElectron","isIE","isUWP","isBrowserExtension","httpHeadersOverwriteParam","channel","createWebChannel","__PRIVATE_opened","closed","__PRIVATE_streamBridge","__PRIVATE_StreamBridge","Qc","open","Wc","__PRIVATE_unguardedEventListen","param","WebChannel","OPEN","CLOSE","__PRIVATE_callOnClose","WARN","warn","MESSAGE","__PRIVATE_msgData","__PRIVATE_msgDataOrError","__PRIVATE_callOnMessage","__PRIVATE_callOnOpen","__PRIVATE_urlRpcName","__PRIVATE_onNetworkAvailable","__PRIVATE_onNetworkUnavailable","__PRIVATE_configureNetworkMonitoring","__PRIVATE_networkAvailableListener","__PRIVATE_networkUnavailableListener","__PRIVATE_MEMORY_ONLY_PERSISTENCE_ERROR_MESSAGE","__PRIVATE_cfg","__PRIVATE_createSharedClientState","__PRIVATE_createPersistence","__PRIVATE_gcScheduler","__PRIVATE_createGarbageCollectionScheduler","__PRIVATE_createLocalStore","__PRIVATE_createRemoteStore","__PRIVATE_createSyncEngine","__PRIVATE_eventManager","__PRIVATE_createEventManager","__PRIVATE_applyPrimaryState","__PRIVATE_EventManager","__PRIVATE_IndexFreeQueryEngine","__PRIVATE_persistenceSettings","__PRIVATE_durable","__PRIVATE_MemoryPersistence","__PRIVATE_factory","__PRIVATE_RemoteStore","__PRIVATE_BrowserConnectivityMonitor","__PRIVATE_isAvailable","__PRIVATE_NoopConnectivityMonitor","__PRIVATE_MemorySharedClientState","__PRIVATE_SyncEngineImpl","__PRIVATE_AutoId","__PRIVATE_newId","__PRIVATE_databaseInfo","__PRIVATE_componentProvider","__PRIVATE_initializationDone","__PRIVATE_persistenceResult","__PRIVATE_initialized","__PRIVATE_setChangeListener","__PRIVATE_initializeComponents","__PRIVATE_WebChannelConnection","__PRIVATE_DatastoreImpl","initialize","Ns","Il","hh","clientId","Tl","iu","ml","__PRIVATE_eventMgr","__PRIVATE_setDatabaseDeletedListener","terminate","__PRIVATE_canFallback","console","__PRIVATE_MemoryComponentProvider","El","DOMException","__PRIVATE_isShuttingDown","__PRIVATE_enqueueAndInitiateShutdown","__PRIVATE_removeChangeListener","__PRIVATE_registerPendingWritesCallback","__PRIVATE_QueryListener","__PRIVATE_clientTerminated","__PRIVATE_readDocument","__PRIVATE_addSnapshotsInSyncListener","__PRIVATE_removeSnapshotsInSyncListener","Vl","__PRIVATE_scheduleEvent","muted","eventHandler","__PRIVATE_isPartialObserver","__PRIVATE_methods","object","method","timestampsInSnapshots","__PRIVATE_serverTimestampBehavior","__PRIVATE_referenceFactory","__PRIVATE_convertTimestamp","__PRIVATE_convertServerTimestamp","__PRIVATE_convertReference","__PRIVATE_convertGeoPoint","__PRIVATE_convertArray","__PRIVATE_convertObject","__PRIVATE_convertValue","__PRIVATE_getPreviousValue","__PRIVATE_normalizedValue","toDate","__PRIVATE_resourcePath","CACHE_SIZE_UNLIMITED","cacheSizeBytes","__PRIVATE_MINIMUM_CACHE_SIZE_BYTES","experimentalForceLongPolling","__PRIVATE_databaseIdOrApp","__PRIVATE_AsyncQueue","__PRIVATE_ensureClientConfigured","__PRIVATE__firestoreClient","app","__PRIVATE__firebaseApp","Firestore","__PRIVATE_databaseIdFromApp","__PRIVATE__persistenceKey","__PRIVATE__credentials","__PRIVATE_FirebaseCredentialsProvider","external","__PRIVATE_EmptyCredentialsProvider","__PRIVATE__componentProvider","__PRIVATE__settings","__PRIVATE_FirestoreSettings","Gl","__PRIVATE__userDataReader","__PRIVATE_UserDataReader","__PRIVATE_settingsLiteral","__PRIVATE_newSettings","__PRIVATE_client","getAuthHeaderValueForFirstParty","__PRIVATE_FirstPartyCredentialsProvider","synchronizeTabs","experimentalForceOwningTab","experimentalTabSynchronization","__PRIVATE_configureClient","Yl","__PRIVATE__queue","__PRIVATE_enqueueAndForgetEvenAfterShutdown","clearPersistence","_removeServiceInstance","Xl","waitForPendingWrites","arg","ql","__PRIVATE_DatabaseInfo","__PRIVATE_makeDatabaseInfo","__PRIVATE_FirestoreClient","__PRIVATE_pathString","CollectionReference","DocumentReference","__PRIVATE_forPath","__PRIVATE_InternalQuery","WriteBatch","SILENT","INFO","VERBOSE","level","__PRIVATE_newLevel","setLogLevel","__PRIVATE_firestoreClient","__PRIVATE_asyncObserver","__PRIVATE_AsyncObserver","__PRIVATE_mute","__PRIVATE__firestore","__PRIVATE__transaction","documentRef","__PRIVATE_validateReference","__PRIVATE_lookup","DocumentSnapshot","__PRIVATE_validateSetOptions","__PRIVATE_convertedValue","__PRIVATE_applyFirestoreDataConverter","__PRIVATE_parsed","__PRIVATE__dataReader","__PRIVATE_fieldOrUpdateData","__PRIVATE_ExternalFieldPath","__PRIVATE_verifyNotCommitted","__PRIVATE__mutations","__PRIVATE__committed","firestore","converter","__PRIVATE_currArg","__PRIVATE_internalOptions","__PRIVATE_userObserver","complete","__PRIVATE__convertToDocSnapshot","__PRIVATE_addDocSnapshotListener","__PRIVATE_validateGetOptions","__PRIVATE_getDocumentFromLocalCache","Gu","__PRIVATE_errHandler","__PRIVATE_internalListener","__PRIVATE__document","__PRIVATE__fromCache","__PRIVATE__hasPendingWrites","__PRIVATE_validateSnapshotOptions","QueryDocumentSnapshot","fromFirestore","__PRIVATE_UserDataWriter","__PRIVATE__areTimestampsInSnapshotsEnabled","serverTimestamps","__PRIVATE_toProto","metadata","SnapshotMetadata","__PRIVATE_validateHasExplicitOrderByForLimitToLast","__PRIVATE__query","opStr","__PRIVATE_createFilter","__PRIVATE_addFilter","directionStr","__PRIVATE_createOrderBy","__PRIVATE_addOrderBy","__PRIVATE_withLimitToFirst","__PRIVATE_withLimitToLast","__PRIVATE_docOrField","__PRIVATE_boundFromDocOrFields","__PRIVATE_withStartAt","__PRIVATE_withEndAt","__PRIVATE_boundFromDocument","__PRIVATE_allFields","__PRIVATE_boundFromFields","QuerySnapshot","__PRIVATE_addQuerySnapshotListener","__PRIVATE_getDocumentsFromLocalCache","__PRIVATE_validateDisjunctiveFilterElements","__PRIVATE_referenceList","__PRIVATE_parseDocumentIdValue","create","__PRIVATE_validateNewFilter","__PRIVATE_validateNewOrderBy","components","__PRIVATE_rawValue","__PRIVATE_wrapped","__PRIVATE_documentIdValue","operator","__PRIVATE_arrayOps","__PRIVATE_disjunctiveOps","__PRIVATE_isArrayOp","__PRIVATE_isDisjunctiveOp","__PRIVATE_existingField","__PRIVATE_validateOrderByAndInequalityMatch","__PRIVATE_conflictingOp","__PRIVATE_findFilterOperator","__PRIVATE_inequality","__PRIVATE__originalQuery","__PRIVATE__snapshot","thisArg","__PRIVATE_convertToDocumentImpl","__PRIVATE__cachedChanges","__PRIVATE__cachedChangesIncludeMetadataChanges","oldIndex","newIndex","__PRIVATE_indexTracker","__PRIVATE_resultChangeType","__PRIVATE__path","toFirestore","__PRIVATE_docRef","__PRIVATE_typeDescription","__PRIVATE_validator","__PRIVATE_validateOptionalArrayElements","__PRIVATE_firestoreNamespace","__PRIVATE_registerFirestore","instance","__PRIVATE_firestoreFactory","registerComponent","Component","container","getProvider","setServiceProps","registerVersion"],"mappings":"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"}