UNPKG

@firebase/firestore

Version:

The Cloud Firestore component of the Firebase JS SDK.

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