@firebase/firestore
Version:
This is the [Cloud Firestore](https://firebase.google.com/docs/firestore/) component of the [Firebase JS SDK](https://www.npmjs.com/package/firebase).
1,325 lines (1,311 loc) • 921 kB
JavaScript
import firebase from '@firebase/app';
import { Logger, LogLevel as LogLevel$1 } from '@firebase/logger';
import { getUA, isReactNative } from '@firebase/util';
import { Component } from '@firebase/component';
import { XhrIo, EventType, ErrorCode, createWebChannelTransport, WebChannel } from '@firebase/webchannel-wrapper';
/**
* @license
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** The semver (www.semver.org) version of the SDK. */
const SDK_VERSION = firebase.SDK_VERSION;
/**
* @license
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const logClient = new Logger('@firebase/firestore');
var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
LogLevel[LogLevel["ERROR"] = 1] = "ERROR";
LogLevel[LogLevel["SILENT"] = 2] = "SILENT";
})(LogLevel || (LogLevel = {}));
// Helper methods are needed because variables can't be exported as read/write
function getLogLevel() {
if (logClient.logLevel === LogLevel$1.DEBUG) {
return LogLevel.DEBUG;
}
else if (logClient.logLevel === LogLevel$1.SILENT) {
return LogLevel.SILENT;
}
else {
return LogLevel.ERROR;
}
}
function setLogLevel(newLevel) {
/**
* Map the new log level to the associated Firebase Log Level
*/
switch (newLevel) {
case LogLevel.DEBUG:
logClient.logLevel = LogLevel$1.DEBUG;
break;
case LogLevel.ERROR:
logClient.logLevel = LogLevel$1.ERROR;
break;
case LogLevel.SILENT:
logClient.logLevel = LogLevel$1.SILENT;
break;
default:
logClient.error(`Firestore (${SDK_VERSION}): Invalid value passed to \`setLogLevel\``);
}
}
function debug(tag, msg, ...obj) {
if (logClient.logLevel <= LogLevel$1.DEBUG) {
const args = obj.map(argToString);
logClient.debug(`Firestore (${SDK_VERSION}) [${tag}]: ${msg}`, ...args);
}
}
function error(msg, ...obj) {
if (logClient.logLevel <= LogLevel$1.ERROR) {
const args = obj.map(argToString);
logClient.error(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
}
}
/**
* Converts an additional log parameter to a string representation.
*/
function argToString(obj) {
if (typeof obj === 'string') {
return obj;
}
else {
const platform = PlatformSupport.getPlatform();
try {
return platform.formatJSON(obj);
}
catch (e) {
// Converting to JSON failed, just log the object directly
return obj;
}
}
}
/**
* @license
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Unconditionally fails, throwing an Error with the given message.
*
* Returns any so it can be used in expressions:
* @example
* let futureVar = fail('not implemented yet');
*/
function fail(failure) {
// Log the failure in addition to throw an exception, just in case the
// exception is swallowed.
const message = `FIRESTORE (${SDK_VERSION}) INTERNAL ASSERTION FAILED: ` + failure;
error(message);
// NOTE: We don't use FirestoreError here because these are internal failures
// that cannot be handled by the user. (Also it would create a circular
// dependency between the error and assert modules which doesn't work.)
throw new Error(message);
}
/**
* Fails if the given assertion condition is false, throwing an Error with the
* given message if it did.
*/
function assert(assertion, message) {
if (!assertion) {
fail(message);
}
}
/**
* @license
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Provides singleton helpers where setup code can inject a platform at runtime.
* setPlatform needs to be set before Firestore is used and must be set exactly
* once.
*/
class PlatformSupport {
static setPlatform(platform) {
if (PlatformSupport.platform) {
fail('Platform already defined');
}
PlatformSupport.platform = platform;
}
static getPlatform() {
if (!PlatformSupport.platform) {
fail('Platform not set');
}
return PlatformSupport.platform;
}
}
/**
* Returns the representation of an empty "proto" byte string for the
* platform.
*/
function emptyByteString() {
return PlatformSupport.getPlatform().emptyByteString;
}
/**
* @license
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const Code = {
// Causes are copied from:
// https://github.com/grpc/grpc/blob/bceec94ea4fc5f0085d81235d8e1c06798dc341a/include/grpc%2B%2B/impl/codegen/status_code_enum.h
/** Not an error; returned on success. */
OK: 'ok',
/** The operation was cancelled (typically by the caller). */
CANCELLED: 'cancelled',
/** Unknown error or an error from a different error domain. */
UNKNOWN: 'unknown',
/**
* Client specified an invalid argument. Note that this differs from
* FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are
* problematic regardless of the state of the system (e.g., a malformed file
* name).
*/
INVALID_ARGUMENT: 'invalid-argument',
/**
* Deadline expired before operation could complete. For operations that
* change the state of the system, this error may be returned even if the
* operation has completed successfully. For example, a successful response
* from a server could have been delayed long enough for the deadline to
* expire.
*/
DEADLINE_EXCEEDED: 'deadline-exceeded',
/** Some requested entity (e.g., file or directory) was not found. */
NOT_FOUND: 'not-found',
/**
* Some entity that we attempted to create (e.g., file or directory) already
* exists.
*/
ALREADY_EXISTS: 'already-exists',
/**
* The caller does not have permission to execute the specified operation.
* PERMISSION_DENIED must not be used for rejections caused by exhausting
* some resource (use RESOURCE_EXHAUSTED instead for those errors).
* PERMISSION_DENIED must not be used if the caller can not be identified
* (use UNAUTHENTICATED instead for those errors).
*/
PERMISSION_DENIED: 'permission-denied',
/**
* The request does not have valid authentication credentials for the
* operation.
*/
UNAUTHENTICATED: 'unauthenticated',
/**
* Some resource has been exhausted, perhaps a per-user quota, or perhaps the
* entire file system is out of space.
*/
RESOURCE_EXHAUSTED: 'resource-exhausted',
/**
* Operation was rejected because the system is not in a state required for
* the operation's execution. For example, directory to be deleted may be
* non-empty, an rmdir operation is applied to a non-directory, etc.
*
* A litmus test that may help a service implementor in deciding
* between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
* (a) Use UNAVAILABLE if the client can retry just the failing call.
* (b) Use ABORTED if the client should retry at a higher-level
* (e.g., restarting a read-modify-write sequence).
* (c) Use FAILED_PRECONDITION if the client should not retry until
* the system state has been explicitly fixed. E.g., if an "rmdir"
* fails because the directory is non-empty, FAILED_PRECONDITION
* should be returned since the client should not retry unless
* they have first fixed up the directory by deleting files from it.
* (d) Use FAILED_PRECONDITION if the client performs conditional
* REST Get/Update/Delete on a resource and the resource on the
* server does not match the condition. E.g., conflicting
* read-modify-write on the same resource.
*/
FAILED_PRECONDITION: 'failed-precondition',
/**
* The operation was aborted, typically due to a concurrency issue like
* sequencer check failures, transaction aborts, etc.
*
* See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
* and UNAVAILABLE.
*/
ABORTED: 'aborted',
/**
* Operation was attempted past the valid range. E.g., seeking or reading
* past end of file.
*
* Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed
* if the system state changes. For example, a 32-bit file system will
* generate INVALID_ARGUMENT if asked to read at an offset that is not in the
* range [0,2^32-1], but it will generate OUT_OF_RANGE if asked to read from
* an offset past the current file size.
*
* There is a fair bit of overlap between FAILED_PRECONDITION and
* OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error)
* when it applies so that callers who are iterating through a space can
* easily look for an OUT_OF_RANGE error to detect when they are done.
*/
OUT_OF_RANGE: 'out-of-range',
/** Operation is not implemented or not supported/enabled in this service. */
UNIMPLEMENTED: 'unimplemented',
/**
* Internal errors. Means some invariants expected by underlying System has
* been broken. If you see one of these errors, Something is very broken.
*/
INTERNAL: 'internal',
/**
* The service is currently unavailable. This is a most likely a transient
* condition and may be corrected by retrying with a backoff.
*
* See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
* and UNAVAILABLE.
*/
UNAVAILABLE: 'unavailable',
/** Unrecoverable data loss or corruption. */
DATA_LOSS: 'data-loss'
};
/**
* An error class used for Firestore-generated errors. Ideally we should be
* using FirebaseError, but integrating with it is overly arduous at the moment,
* so we define our own compatible error class (with a `name` of 'FirebaseError'
* and compatible `code` and `message` fields.)
*/
class FirestoreError extends Error {
constructor(code, message) {
super(message);
this.code = code;
this.message = message;
this.name = 'FirebaseError';
// HACK: We write a toString property directly because Error is not a real
// class and so inheritance does not work correctly. We could alternatively
// do the same "back-door inheritance" trick that FirebaseError does.
this.toString = () => `${this.name}: [code=${this.code}]: ${this.message}`;
}
}
/**
* @license
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Helper function to prevent instantiation through the constructor.
*
* This method creates a new constructor that throws when it's invoked.
* The prototype of that constructor is then set to the prototype of the hidden
* "class" to expose all the prototype methods and allow for instanceof
* checks.
*
* To also make all the static methods available, all properties of the
* original constructor are copied to the new constructor.
*/
function makeConstructorPrivate(cls, optionalMessage) {
function PublicConstructor() {
let error = 'This constructor is private.';
if (optionalMessage) {
error += ' ';
error += optionalMessage;
}
throw new FirestoreError(Code.INVALID_ARGUMENT, error);
}
// Make sure instanceof checks work and all methods are exposed on the public
// constructor
PublicConstructor.prototype = cls.prototype;
// Copy any static methods/members
for (const staticProperty in cls) {
if (cls.hasOwnProperty(staticProperty)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
PublicConstructor[staticProperty] = cls[staticProperty];
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return PublicConstructor;
}
/**
* @license
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function contains(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
/** Returns the given value if it's defined or the defaultValue otherwise. */
function defaulted(value, defaultValue) {
return value !== undefined ? value : defaultValue;
}
function forEachNumber(obj, fn) {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const num = Number(key);
if (!isNaN(num)) {
fn(num, obj[key]);
}
}
}
}
function values(obj) {
const vs = [];
forEach(obj, (_, v) => vs.push(v));
return vs;
}
function forEach(obj, fn) {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn(key, obj[key]);
}
}
}
function isEmpty(obj) {
assert(obj != null && typeof obj === 'object', 'isEmpty() expects object parameter.');
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
return false;
}
}
return true;
}
function shallowCopy(obj) {
assert(obj && typeof obj === 'object', 'shallowCopy() expects object parameter.');
const result = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
result[key] = obj[key];
}
}
return result;
}
/**
* @license
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Validates that no arguments were passed in the invocation of functionName.
*
* Forward the magic "arguments" variable as second parameter on which the
* parameter validation is performed:
* validateNoArgs('myFunction', arguments);
*/
function validateNoArgs(functionName, args) {
if (args.length !== 0) {
throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() does not support arguments, ` +
'but was called with ' +
formatPlural(args.length, 'argument') +
'.');
}
}
/**
* Validates the invocation of functionName has the exact number of arguments.
*
* Forward the magic "arguments" variable as second parameter on which the
* parameter validation is performed:
* validateExactNumberOfArgs('myFunction', arguments, 2);
*/
function validateExactNumberOfArgs(functionName, args, numberOfArgs) {
if (args.length !== numberOfArgs) {
throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() requires ` +
formatPlural(numberOfArgs, 'argument') +
', but was called with ' +
formatPlural(args.length, 'argument') +
'.');
}
}
/**
* Validates the invocation of functionName has at least the provided number of
* arguments (but can have many more).
*
* Forward the magic "arguments" variable as second parameter on which the
* parameter validation is performed:
* validateAtLeastNumberOfArgs('myFunction', arguments, 2);
*/
function validateAtLeastNumberOfArgs(functionName, args, minNumberOfArgs) {
if (args.length < minNumberOfArgs) {
throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() requires at least ` +
formatPlural(minNumberOfArgs, 'argument') +
', but was called with ' +
formatPlural(args.length, 'argument') +
'.');
}
}
/**
* Validates the invocation of functionName has number of arguments between
* the values provided.
*
* Forward the magic "arguments" variable as second parameter on which the
* parameter validation is performed:
* validateBetweenNumberOfArgs('myFunction', arguments, 2, 3);
*/
function validateBetweenNumberOfArgs(functionName, args, minNumberOfArgs, maxNumberOfArgs) {
if (args.length < minNumberOfArgs || args.length > maxNumberOfArgs) {
throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() requires between ${minNumberOfArgs} and ` +
`${maxNumberOfArgs} arguments, but was called with ` +
formatPlural(args.length, 'argument') +
'.');
}
}
/**
* Validates the provided argument is an array and has as least the expected
* number of elements.
*/
function validateNamedArrayAtLeastNumberOfElements(functionName, value, name, minNumberOfElements) {
if (!(value instanceof Array) || value.length < minNumberOfElements) {
throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() requires its ${name} argument to be an ` +
'array with at least ' +
`${formatPlural(minNumberOfElements, 'element')}.`);
}
}
/**
* Validates the provided positional argument has the native JavaScript type
* using typeof checks.
*/
function validateArgType(functionName, type, position, argument) {
validateType(functionName, type, `${ordinal(position)} argument`, argument);
}
/**
* Validates the provided argument has the native JavaScript type using
* typeof checks or is undefined.
*/
function validateOptionalArgType(functionName, type, position, argument) {
if (argument !== undefined) {
validateArgType(functionName, type, position, argument);
}
}
/**
* Validates the provided named option has the native JavaScript type using
* typeof checks.
*/
function validateNamedType(functionName, type, optionName, argument) {
validateType(functionName, type, `${optionName} option`, argument);
}
/**
* Validates the provided named option has the native JavaScript type using
* typeof checks or is undefined.
*/
function validateNamedOptionalType(functionName, type, optionName, argument) {
if (argument !== undefined) {
validateNamedType(functionName, type, optionName, argument);
}
}
function validateArrayElements(functionName, optionName, typeDescription, argument, validator) {
if (!(argument instanceof Array)) {
throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() requires its ${optionName} ` +
`option to be an array, but it was: ${valueDescription(argument)}`);
}
for (let i = 0; i < argument.length; ++i) {
if (!validator(argument[i])) {
throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() requires all ${optionName} ` +
`elements to be ${typeDescription}, but the value at index ${i} ` +
`was: ${valueDescription(argument[i])}`);
}
}
}
function validateOptionalArrayElements(functionName, optionName, typeDescription, argument, validator) {
if (argument !== undefined) {
validateArrayElements(functionName, optionName, typeDescription, argument, validator);
}
}
/**
* Validates that the provided named option equals one of the expected values.
*/
function validateNamedPropertyEquals(functionName, inputName, optionName, input, expected) {
const expectedDescription = [];
for (const val of expected) {
if (val === input) {
return;
}
expectedDescription.push(valueDescription(val));
}
const actualDescription = valueDescription(input);
throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid value ${actualDescription} provided to function ${functionName}() for option ` +
`"${optionName}". Acceptable values: ${expectedDescription.join(', ')}`);
}
/**
* Validates that the provided named option equals one of the expected values or
* is undefined.
*/
function validateNamedOptionalPropertyEquals(functionName, inputName, optionName, input, expected) {
if (input !== undefined) {
validateNamedPropertyEquals(functionName, inputName, optionName, input, expected);
}
}
/**
* Validates that the provided argument is a valid enum.
*
* @param functionName Function making the validation call.
* @param enums Array containing all possible values for the enum.
* @param position Position of the argument in `functionName`.
* @param argument Arugment to validate.
*/
function validateStringEnum(functionName, enums, position, argument) {
if (!enums.some(element => element === argument)) {
throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid value ${valueDescription(argument)} provided to function ` +
`${functionName}() for its ${ordinal(position)} argument. Acceptable ` +
`values: ${enums.join(', ')}`);
}
}
/** Helper to validate the type of a provided input. */
function validateType(functionName, type, inputName, input) {
let valid = false;
if (type === 'object') {
valid = isPlainObject(input);
}
else if (type === 'non-empty string') {
valid = typeof input === 'string' && input !== '';
}
else {
valid = typeof input === type;
}
if (!valid) {
const description = valueDescription(input);
throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() requires its ${inputName} ` +
`to be of type ${type}, but it was: ${description}`);
}
}
/**
* Returns true if it's a non-null object without a custom prototype
* (i.e. excludes Array, Date, etc.).
*/
function isPlainObject(input) {
return (typeof input === 'object' &&
input !== null &&
(Object.getPrototypeOf(input) === Object.prototype ||
Object.getPrototypeOf(input) === null));
}
/** Returns a string describing the type / value of the provided input. */
function valueDescription(input) {
if (input === undefined) {
return 'undefined';
}
else if (input === null) {
return 'null';
}
else if (typeof input === 'string') {
if (input.length > 20) {
input = `${input.substring(0, 20)}...`;
}
return JSON.stringify(input);
}
else if (typeof input === 'number' || typeof input === 'boolean') {
return '' + input;
}
else if (typeof input === 'object') {
if (input instanceof Array) {
return 'an array';
}
else {
const customObjectName = tryGetCustomObjectType(input);
if (customObjectName) {
return `a custom ${customObjectName} object`;
}
else {
return 'an object';
}
}
}
else if (typeof input === 'function') {
return 'a function';
}
else {
return fail('Unknown wrong type: ' + typeof input);
}
}
/** Hacky method to try to get the constructor name for an object. */
function tryGetCustomObjectType(input) {
if (input.constructor) {
const funcNameRegex = /function\s+([^\s(]+)\s*\(/;
const results = funcNameRegex.exec(input.constructor.toString());
if (results && results.length > 1) {
return results[1];
}
}
return null;
}
/** Validates the provided argument is defined. */
function validateDefined(functionName, position, argument) {
if (argument === undefined) {
throw new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() requires a valid ${ordinal(position)} ` +
`argument, but it was undefined.`);
}
}
/**
* Validates the provided positional argument is an object, and its keys and
* values match the expected keys and types provided in optionTypes.
*/
function validateOptionNames(functionName, options, optionNames) {
forEach(options, (key, _) => {
if (optionNames.indexOf(key) < 0) {
throw new FirestoreError(Code.INVALID_ARGUMENT, `Unknown option '${key}' passed to function ${functionName}(). ` +
'Available options: ' +
optionNames.join(', '));
}
});
}
/**
* Helper method to throw an error that the provided argument did not pass
* an instanceof check.
*/
function invalidClassError(functionName, type, position, argument) {
const description = valueDescription(argument);
return new FirestoreError(Code.INVALID_ARGUMENT, `Function ${functionName}() requires its ${ordinal(position)} ` +
`argument to be a ${type}, but it was: ${description}`);
}
function validatePositiveNumber(functionName, position, n) {
if (n <= 0) {
throw new FirestoreError(Code.INVALID_ARGUMENT, `Function "${functionName}()" requires its ${ordinal(position)} argument to be a positive number, but it was: ${n}.`);
}
}
/** Converts a number to its english word representation */
function ordinal(num) {
switch (num) {
case 1:
return 'first';
case 2:
return 'second';
case 3:
return 'third';
default:
return num + 'th';
}
}
/**
* Formats the given word as plural conditionally given the preceding number.
*/
function formatPlural(num, str) {
return `${num} ${str}` + (num === 1 ? '' : 's');
}
/**
* @license
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class AutoId {
static newId() {
// Alphanumeric characters
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let autoId = '';
for (let i = 0; i < 20; i++) {
autoId += chars.charAt(Math.floor(Math.random() * chars.length));
}
assert(autoId.length === 20, 'Invalid auto ID: ' + autoId);
return autoId;
}
}
function primitiveComparator(left, right) {
if (left < right) {
return -1;
}
if (left > right) {
return 1;
}
return 0;
}
/** Helper to compare nullable (or undefined-able) objects using isEqual(). */
function equals(left, right) {
if (left !== null && left !== undefined) {
return !!(right && left.isEqual(right));
}
else {
// HACK: Explicitly cast since TypeScript's type narrowing apparently isn't
// smart enough.
return left === right;
}
}
/** Helper to compare arrays using isEqual(). */
function arrayEquals(left, right) {
if (left.length !== right.length) {
return false;
}
for (let i = 0; i < left.length; i++) {
if (!left[i].isEqual(right[i])) {
return false;
}
}
return true;
}
/**
* Returns the immediate lexicographically-following string. This is useful to
* construct an inclusive range for indexeddb iterators.
*/
function immediateSuccessor(s) {
// Return the input string, with an additional NUL byte appended.
return s + '\0';
}
/**
* @license
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** Helper function to assert Uint8Array is available at runtime. */
function assertUint8ArrayAvailable() {
if (typeof Uint8Array === 'undefined') {
throw new FirestoreError(Code.UNIMPLEMENTED, 'Uint8Arrays are not available in this environment.');
}
}
/** Helper function to assert Base64 functions are available at runtime. */
function assertBase64Available() {
if (!PlatformSupport.getPlatform().base64Available) {
throw new FirestoreError(Code.UNIMPLEMENTED, 'Blobs are unavailable in Firestore in this environment.');
}
}
/**
* Immutable class holding a blob (binary data).
* This class is directly exposed in the public API.
*
* Note that while you can't hide the constructor in JavaScript code, we are
* using the hack above to make sure no-one outside this module can call it.
*/
class Blob {
constructor(binaryString) {
assertBase64Available();
this._binaryString = binaryString;
}
static fromBase64String(base64) {
validateExactNumberOfArgs('Blob.fromBase64String', arguments, 1);
validateArgType('Blob.fromBase64String', 'string', 1, base64);
assertBase64Available();
try {
const binaryString = PlatformSupport.getPlatform().atob(base64);
return new Blob(binaryString);
}
catch (e) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Failed to construct Blob from Base64 string: ' + e);
}
}
static fromUint8Array(array) {
validateExactNumberOfArgs('Blob.fromUint8Array', arguments, 1);
assertUint8ArrayAvailable();
if (!(array instanceof Uint8Array)) {
throw invalidClassError('Blob.fromUint8Array', 'Uint8Array', 1, array);
}
// We can't call array.map directly because it expects the return type to
// be a Uint8Array, whereas we can convert it to a regular array by invoking
// map on the Array prototype.
const binaryString = Array.prototype.map
.call(array, (char) => {
return String.fromCharCode(char);
})
.join('');
return new Blob(binaryString);
}
toBase64() {
validateExactNumberOfArgs('Blob.toBase64', arguments, 0);
assertBase64Available();
return PlatformSupport.getPlatform().btoa(this._binaryString);
}
toUint8Array() {
validateExactNumberOfArgs('Blob.toUint8Array', arguments, 0);
assertUint8ArrayAvailable();
const buffer = new Uint8Array(this._binaryString.length);
for (let i = 0; i < this._binaryString.length; i++) {
buffer[i] = this._binaryString.charCodeAt(i);
}
return buffer;
}
toString() {
return 'Blob(base64: ' + this.toBase64() + ')';
}
isEqual(other) {
return this._binaryString === other._binaryString;
}
/**
* Actually private to JS consumers of our API, so this function is prefixed
* with an underscore.
*/
_compareTo(other) {
return primitiveComparator(this._binaryString, other._binaryString);
}
}
// Public instance that disallows construction at runtime. This constructor is
// used when exporting Blob on firebase.firestore.Blob and will be called Blob
// publicly. Internally we still use Blob which has a type checked private
// constructor. Note that Blob and PublicBlob can be used interchangeably in
// instanceof checks.
// For our internal TypeScript code PublicBlob doesn't exist as a type, and so
// we need to use Blob as type and export it too.
const PublicBlob = makeConstructorPrivate(Blob, 'Use Blob.fromUint8Array() or Blob.fromBase64String() instead.');
/**
* @license
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class DatabaseInfo {
/**
* Constructs a DatabaseInfo using the provided host, databaseId and
* persistenceKey.
*
* @param databaseId The database to use.
* @param persistenceKey A unique identifier for this Firestore's local
* storage (used in conjunction with the databaseId).
* @param host The Firestore backend host to connect to.
* @param ssl Whether to use SSL when connecting.
* @param forceLongPolling Whether to use the forceLongPolling option
* when using WebChannel as the network transport.
*/
constructor(databaseId, persistenceKey, host, ssl, forceLongPolling) {
this.databaseId = databaseId;
this.persistenceKey = persistenceKey;
this.host = host;
this.ssl = ssl;
this.forceLongPolling = forceLongPolling;
}
}
/** The default database name for a project. */
const DEFAULT_DATABASE_NAME = '(default)';
/** Represents the database ID a Firestore client is associated with. */
class DatabaseId {
constructor(projectId, database) {
this.projectId = projectId;
this.database = database ? database : DEFAULT_DATABASE_NAME;
}
get isDefaultDatabase() {
return this.database === DEFAULT_DATABASE_NAME;
}
isEqual(other) {
return (other instanceof DatabaseId &&
other.projectId === this.projectId &&
other.database === this.database);
}
compareTo(other) {
return (primitiveComparator(this.projectId, other.projectId) ||
primitiveComparator(this.database, other.database));
}
}
/**
* @license
* Copyright 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* `ListenSequence` is a monotonic sequence. It is initialized with a minimum value to
* exceed. All subsequent calls to next will return increasing values. If provided with a
* `SequenceNumberSyncer`, it will additionally bump its next value when told of a new value, as
* well as write out sequence numbers that it produces via `next()`.
*/
class ListenSequence {
constructor(previousValue, sequenceNumberSyncer) {
this.previousValue = previousValue;
if (sequenceNumberSyncer) {
sequenceNumberSyncer.sequenceNumberHandler = sequenceNumber => this.setPreviousValue(sequenceNumber);
this.writeNewSequenceNumber = sequenceNumber => sequenceNumberSyncer.writeSequenceNumber(sequenceNumber);
}
}
setPreviousValue(externalPreviousValue) {
this.previousValue = Math.max(externalPreviousValue, this.previousValue);
return this.previousValue;
}
next() {
const nextValue = ++this.previousValue;
if (this.writeNewSequenceNumber) {
this.writeNewSequenceNumber(nextValue);
}
return nextValue;
}
}
ListenSequence.INVALID = -1;
/**
* @license
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const DOCUMENT_KEY_NAME = '__name__';
/**
* Path represents an ordered sequence of string segments.
*/
class BasePath {
constructor(segments, offset, length) {
if (offset === undefined) {
offset = 0;
}
else if (offset > segments.length) {
fail('offset ' + offset + ' out of range ' + segments.length);
}
if (length === undefined) {
length = segments.length - offset;
}
else if (length > segments.length - offset) {
fail('length ' + length + ' out of range ' + (segments.length - offset));
}
this.segments = segments;
this.offset = offset;
this.len = length;
}
get length() {
return this.len;
}
isEqual(other) {
return BasePath.comparator(this, other) === 0;
}
child(nameOrPath) {
const segments = this.segments.slice(this.offset, this.limit());
if (nameOrPath instanceof BasePath) {
nameOrPath.forEach(segment => {
segments.push(segment);
});
}
else {
segments.push(nameOrPath);
}
return this.construct(segments);
}
/** The index of one past the last segment of the path. */
limit() {
return this.offset + this.length;
}
popFirst(size) {
size = size === undefined ? 1 : size;
assert(this.length >= size, "Can't call popFirst() with less segments");
return this.construct(this.segments, this.offset + size, this.length - size);
}
popLast() {
assert(!this.isEmpty(), "Can't call popLast() on empty path");
return this.construct(this.segments, this.offset, this.length - 1);
}
firstSegment() {
assert(!this.isEmpty(), "Can't call firstSegment() on empty path");
return this.segments[this.offset];
}
lastSegment() {
return this.get(this.length - 1);
}
get(index) {
assert(index < this.length, 'Index out of range');
return this.segments[this.offset + index];
}
isEmpty() {
return this.length === 0;
}
isPrefixOf(other) {
if (other.length < this.length) {
return false;
}
for (let i = 0; i < this.length; i++) {
if (this.get(i) !== other.get(i)) {
return false;
}
}
return true;
}
isImmediateParentOf(potentialChild) {
if (this.length + 1 !== potentialChild.length) {
return false;
}
for (let i = 0; i < this.length; i++) {
if (this.get(i) !== potentialChild.get(i)) {
return false;
}
}
return true;
}
forEach(fn) {
for (let i = this.offset, end = this.limit(); i < end; i++) {
fn(this.segments[i]);
}
}
toArray() {
return this.segments.slice(this.offset, this.limit());
}
static comparator(p1, p2) {
const len = Math.min(p1.length, p2.length);
for (let i = 0; i < len; i++) {
const left = p1.get(i);
const right = p2.get(i);
if (left < right) {
return -1;
}
if (left > right) {
return 1;
}
}
if (p1.length < p2.length) {
return -1;
}
if (p1.length > p2.length) {
return 1;
}
return 0;
}
}
/**
* A slash-separated path for navigating resources (documents and collections)
* within Firestore.
*/
class ResourcePath extends BasePath {
construct(segments, offset, length) {
return new ResourcePath(segments, offset, length);
}
canonicalString() {
// NOTE: The client is ignorant of any path segments containing escape
// sequences (e.g. __id123__) and just passes them through raw (they exist
// for legacy reasons and should not be used frequently).
return this.toArray().join('/');
}
toString() {
return this.canonicalString();
}
/**
* Creates a resource path from the given slash-delimited string.
*/
static fromString(path) {
// NOTE: The client is ignorant of any path segments containing escape
// sequences (e.g. __id123__) and just passes them through raw (they exist
// for legacy reasons and should not be used frequently).
if (path.indexOf('//') >= 0) {
throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid path (${path}). Paths must not contain // in them.`);
}
// We may still have an empty segment at the beginning or end if they had a
// leading or trailing slash (which we allow).
const segments = path.split('/').filter(segment => segment.length > 0);
return new ResourcePath(segments);
}
}
ResourcePath.EMPTY_PATH = new ResourcePath([]);
const identifierRegExp = /^[_a-zA-Z][_a-zA-Z0-9]*$/;
/** A dot-separated path for navigating sub-objects within a document. */
class FieldPath extends BasePath {
construct(segments, offset, length) {
return new FieldPath(segments, offset, length);
}
/**
* Returns true if the string could be used as a segment in a field path
* without escaping.
*/
static isValidIdentifier(segment) {
return identifierRegExp.test(segment);
}
canonicalString() {
return this.toArray()
.map(str => {
str = str.replace('\\', '\\\\').replace('`', '\\`');
if (!FieldPath.isValidIdentifier(str)) {
str = '`' + str + '`';
}
return str;
})
.join('.');
}
toString() {
return this.canonicalString();
}
/**
* Returns true if this field references the key of a document.
*/
isKeyField() {
return this.length === 1 && this.get(0) === DOCUMENT_KEY_NAME;
}
/**
* The field designating the key of a document.
*/
static keyField() {
return new FieldPath([DOCUMENT_KEY_NAME]);
}
/**
* Parses a field string from the given server-formatted string.
*
* - Splitting the empty string is not allowed (for now at least).
* - Empty segments within the string (e.g. if there are two consecutive
* separators) are not allowed.
*
* TODO(b/37244157): we should make this more strict. Right now, it allows
* non-identifier path components, even if they aren't escaped.
*/
static fromServerFormat(path) {
const segments = [];
let current = '';
let i = 0;
const addCurrentSegment = () => {
if (current.length === 0) {
throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid field path (${path}). Paths must not be empty, begin ` +
`with '.', end with '.', or contain '..'`);
}
segments.push(current);
current = '';
};
let inBackticks = false;
while (i < path.length) {
const c = path[i];
if (c === '\\') {
if (i + 1 === path.length) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Path has trailing escape character: ' + path);
}
const next = path[i + 1];
if (!(next === '\\' || next === '.' || next === '`')) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Path has invalid escape sequence: ' + path);
}
current += next;
i += 2;
}
else if (c === '`') {
inBackticks = !inBackticks;
i++;
}
else if (c === '.' && !inBackticks) {
addCurrentSegment();
i++;
}
else {
current += c;
i++;
}
}
addCurrentSegment();
if (inBackticks) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Unterminated ` in path: ' + path);
}
return new FieldPath(segments);
}
}
FieldPath.EMPTY_PATH = new FieldPath([]);
/**
* @license
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limit