initial commit

This commit is contained in:
2025-09-01 22:12:29 +02:00
parent b1873f9c1d
commit 02a54f61c0
5598 changed files with 903558 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
/**
* Copyright 2023 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import { FieldPath } from './path';
import { google } from '../protos/firestore_v1_proto_api';
import IAggregation = google.firestore.v1.StructuredAggregationQuery.IAggregation;
/**
* Concrete implementation of the Aggregate type.
*/
export declare class Aggregate {
readonly alias: string;
readonly aggregateType: AggregateType;
readonly fieldPath?: (string | FieldPath) | undefined;
constructor(alias: string, aggregateType: AggregateType, fieldPath?: (string | FieldPath) | undefined);
/**
* Converts this object to the proto representation of an Aggregate.
* @internal
*/
toProto(): IAggregation;
}
/**
* Represents an aggregation that can be performed by Firestore.
*/
export declare class AggregateField<T> implements firestore.AggregateField<T> {
readonly aggregateType: AggregateType;
/** A type string to uniquely identify instances of this class. */
readonly type = "AggregateField";
/**
* The field on which the aggregation is performed.
* @internal
**/
readonly _field?: string | FieldPath;
/**
* Create a new AggregateField<T>
* @param aggregateType Specifies the type of aggregation operation to perform.
* @param field Optionally specifies the field that is aggregated.
* @internal
*/
private constructor();
/**
* Compares this object with the given object for equality.
*
* This object is considered "equal" to the other object if and only if
* `other` performs the same kind of aggregation on the same field (if any).
*
* @param other The object to compare to this object for equality.
* @return `true` if this object is "equal" to the given object, as
* defined above, or `false` otherwise.
*/
isEqual(other: AggregateField<T>): boolean;
/**
* Create an AggregateField object that can be used to compute the count of
* documents in the result set of a query.
*/
static count(): AggregateField<number>;
/**
* Create an AggregateField object that can be used to compute the average of
* a specified field over a range of documents in the result set of a query.
* @param field Specifies the field to average across the result set.
*/
static average(field: string | FieldPath): AggregateField<number | null>;
/**
* Create an AggregateField object that can be used to compute the sum of
* a specified field over a range of documents in the result set of a query.
* @param field Specifies the field to sum across the result set.
*/
static sum(field: string | FieldPath): AggregateField<number>;
}
/**
* A type whose property values are all `AggregateField` objects.
*/
export interface AggregateSpec {
[field: string]: AggregateFieldType;
}
/**
* The union of all `AggregateField` types that are supported by Firestore.
*/
export type AggregateFieldType = ReturnType<typeof AggregateField.count> | ReturnType<typeof AggregateField.sum> | ReturnType<typeof AggregateField.average>;
/**
* Union type representing the aggregate type to be performed.
*/
export type AggregateType = 'count' | 'avg' | 'sum';

View File

@@ -0,0 +1,122 @@
"use strict";
/**
* Copyright 2023 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AggregateField = exports.Aggregate = void 0;
const path_1 = require("./path");
const assert = require("assert");
/**
* Concrete implementation of the Aggregate type.
*/
class Aggregate {
constructor(alias, aggregateType, fieldPath) {
this.alias = alias;
this.aggregateType = aggregateType;
this.fieldPath = fieldPath;
}
/**
* Converts this object to the proto representation of an Aggregate.
* @internal
*/
toProto() {
const proto = {};
if (this.aggregateType === 'count') {
proto.count = {};
}
else if (this.aggregateType === 'sum') {
assert(this.fieldPath !== undefined, 'Missing field path for sum aggregation.');
proto.sum = {
field: {
fieldPath: path_1.FieldPath.fromArgument(this.fieldPath).formattedName,
},
};
}
else if (this.aggregateType === 'avg') {
assert(this.fieldPath !== undefined, 'Missing field path for average aggregation.');
proto.avg = {
field: {
fieldPath: path_1.FieldPath.fromArgument(this.fieldPath).formattedName,
},
};
}
else {
throw new Error(`Aggregate type ${this.aggregateType} unimplemented.`);
}
proto.alias = this.alias;
return proto;
}
}
exports.Aggregate = Aggregate;
/**
* Represents an aggregation that can be performed by Firestore.
*/
class AggregateField {
/**
* Create a new AggregateField<T>
* @param aggregateType Specifies the type of aggregation operation to perform.
* @param field Optionally specifies the field that is aggregated.
* @internal
*/
constructor(aggregateType, field) {
this.aggregateType = aggregateType;
/** A type string to uniquely identify instances of this class. */
this.type = 'AggregateField';
this._field = field;
}
/**
* Compares this object with the given object for equality.
*
* This object is considered "equal" to the other object if and only if
* `other` performs the same kind of aggregation on the same field (if any).
*
* @param other The object to compare to this object for equality.
* @return `true` if this object is "equal" to the given object, as
* defined above, or `false` otherwise.
*/
isEqual(other) {
return (other instanceof AggregateField &&
this.aggregateType === other.aggregateType &&
((this._field === undefined && other._field === undefined) ||
(this._field !== undefined &&
other._field !== undefined &&
path_1.FieldPath.fromArgument(this._field).isEqual(path_1.FieldPath.fromArgument(other._field)))));
}
/**
* Create an AggregateField object that can be used to compute the count of
* documents in the result set of a query.
*/
static count() {
return new AggregateField('count');
}
/**
* Create an AggregateField object that can be used to compute the average of
* a specified field over a range of documents in the result set of a query.
* @param field Specifies the field to average across the result set.
*/
static average(field) {
return new AggregateField('avg', field);
}
/**
* Create an AggregateField object that can be used to compute the sum of
* a specified field over a range of documents in the result set of a query.
* @param field Specifies the field to sum across the result set.
*/
static sum(field) {
return new AggregateField('sum', field);
}
}
exports.AggregateField = AggregateField;
//# sourceMappingURL=aggregate.js.map

View File

@@ -0,0 +1,177 @@
/*!
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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 default initial backoff time in milliseconds after an error.
* Set to 1s according to https://cloud.google.com/apis/design/errors.
*/
export declare const DEFAULT_BACKOFF_INITIAL_DELAY_MS = 1000;
/*!
* The default maximum backoff time in milliseconds.
*/
export declare const DEFAULT_BACKOFF_MAX_DELAY_MS: number;
/*!
* The default factor to increase the backup by after each failed attempt.
*/
export declare const DEFAULT_BACKOFF_FACTOR = 1.5;
/*!
* The maximum number of retries that will be attempted by backoff
* before stopping all retry attempts.
*/
export declare const MAX_RETRY_ATTEMPTS = 10;
/*!
* The timeout handler used by `ExponentialBackoff` and `BulkWriter`.
*/
export declare let delayExecution: (f: () => void, ms: number) => NodeJS.Timeout;
/**
* Allows overriding of the timeout handler used by the exponential backoff
* implementation. If not invoked, we default to `setTimeout()`.
*
* Used only in testing.
*
* @private
* @internal
* @param {function} handler A handler than matches the API of `setTimeout()`.
*/
export declare function setTimeoutHandler(handler: (f: () => void, ms: number) => void): void;
/**
* Configuration object to adjust the delays of the exponential backoff
* algorithm.
*
* @private
* @internal
*/
export interface ExponentialBackoffSetting {
/** Optional override for the initial retry delay. */
initialDelayMs?: number;
/** Optional override for the exponential backoff factor. */
backoffFactor?: number;
/** Optional override for the maximum retry delay. */
maxDelayMs?: number;
/**
* Optional override to control the itter factor by which to randomize
* attempts (0 means no randomization, 1.0 means +/-50% randomization). It is
* suggested not to exceed this range.
*/
jitterFactor?: number;
}
/**
* A helper for running delayed tasks following an exponential backoff curve
* between attempts.
*
* Each delay is made up of a "base" delay which follows the exponential
* backoff curve, and a "jitter" (+/- 50% by default) that is calculated and
* added to the base delay. This prevents clients from accidentally
* synchronizing their delays causing spikes of load to the backend.
*
* @private
* @internal
*/
export declare class ExponentialBackoff {
/**
* The initial delay (used as the base delay on the first retry attempt).
* Note that jitter will still be applied, so the actual delay could be as
* little as 0.5*initialDelayMs (based on a jitter factor of 1.0).
*
* @private
* @internal
*/
private readonly initialDelayMs;
/**
* The multiplier to use to determine the extended base delay after each
* attempt.
*
* @private
* @internal
*/
private readonly backoffFactor;
/**
* The maximum base delay after which no further backoff is performed.
* Note that jitter will still be applied, so the actual delay could be as
* much as 1.5*maxDelayMs (based on a jitter factor of 1.0).
*
* @private
* @internal
*/
private readonly maxDelayMs;
/**
* The jitter factor that controls the random distribution of the backoff
* points.
*
* @private
* @internal
*/
private readonly jitterFactor;
/**
* The number of retries that has been attempted.
*
* @private
* @internal
*/
private _retryCount;
/**
* The backoff delay of the current attempt.
*
* @private
* @internal
*/
private currentBaseMs;
/**
* Whether we are currently waiting for backoff to complete.
*
* @private
* @internal
*/
private awaitingBackoffCompletion;
constructor(options?: ExponentialBackoffSetting);
/**
* Resets the backoff delay and retry count.
*
* The very next backoffAndWait() will have no delay. If it is called again
* (i.e. due to an error), initialDelayMs (plus jitter) will be used, and
* subsequent ones will increase according to the backoffFactor.
*
* @private
* @internal
*/
reset(): void;
/**
* Resets the backoff delay to the maximum delay (e.g. for use after a
* RESOURCE_EXHAUSTED error).
*
* @private
* @internal
*/
resetToMax(): void;
/**
* Returns a promise that resolves after currentDelayMs, and increases the
* delay for any subsequent attempts.
*
* @return A Promise that resolves when the current delay elapsed.
* @private
* @internal
*/
backoffAndWait(): Promise<void>;
get retryCount(): number;
/**
* Returns a randomized "jitter" delay based on the current base and jitter
* factor.
*
* @returns {number} The jitter to apply based on the current delay.
* @private
* @internal
*/
private jitterDelayMs;
}

View File

@@ -0,0 +1,225 @@
"use strict";
/*!
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExponentialBackoff = exports.delayExecution = exports.MAX_RETRY_ATTEMPTS = exports.DEFAULT_BACKOFF_FACTOR = exports.DEFAULT_BACKOFF_MAX_DELAY_MS = exports.DEFAULT_BACKOFF_INITIAL_DELAY_MS = void 0;
exports.setTimeoutHandler = setTimeoutHandler;
const logger_1 = require("./logger");
/*
* @module firestore/backoff
* @private
* @internal
*
* Contains backoff logic to facilitate RPC error handling. This class derives
* its implementation from the Firestore Mobile Web Client.
*
* @see https://github.com/firebase/firebase-js-sdk/blob/master/packages/firestore/src/remote/backoff.ts
*/
/*!
* The default initial backoff time in milliseconds after an error.
* Set to 1s according to https://cloud.google.com/apis/design/errors.
*/
exports.DEFAULT_BACKOFF_INITIAL_DELAY_MS = 1000;
/*!
* The default maximum backoff time in milliseconds.
*/
exports.DEFAULT_BACKOFF_MAX_DELAY_MS = 60 * 1000;
/*!
* The default factor to increase the backup by after each failed attempt.
*/
exports.DEFAULT_BACKOFF_FACTOR = 1.5;
/*!
* The default jitter to distribute the backoff attempts by (0 means no
* randomization, 1.0 means +/-50% randomization).
*/
const DEFAULT_JITTER_FACTOR = 1.0;
/*!
* The maximum number of retries that will be attempted by backoff
* before stopping all retry attempts.
*/
exports.MAX_RETRY_ATTEMPTS = 10;
/*!
* The timeout handler used by `ExponentialBackoff` and `BulkWriter`.
*/
exports.delayExecution = setTimeout;
/**
* Allows overriding of the timeout handler used by the exponential backoff
* implementation. If not invoked, we default to `setTimeout()`.
*
* Used only in testing.
*
* @private
* @internal
* @param {function} handler A handler than matches the API of `setTimeout()`.
*/
function setTimeoutHandler(handler) {
exports.delayExecution = (f, ms) => {
handler(f, ms);
const timeout = {
hasRef: () => {
throw new Error('For tests only. Not Implemented');
},
ref: () => {
throw new Error('For tests only. Not Implemented');
},
refresh: () => {
throw new Error('For tests only. Not Implemented');
},
unref: () => {
throw new Error('For tests only. Not Implemented');
},
[Symbol.toPrimitive]: () => {
throw new Error('For tests only. Not Implemented');
},
};
// `NodeJS.Timeout` type signature change:
// https://github.com/DefinitelyTyped/DefinitelyTyped/pull/66176/files#diff-e838d0ace9cd5f6516bacfbd3ad00d02cd37bd60f9993ce6223f52d889a1fdbaR122-R126
//
// Adding `[Symbol.dispose](): void;` cannot be done on older versions of
// NodeJS. So we simply cast to `NodeJS.Timeout`.
return timeout;
};
}
/**
* A helper for running delayed tasks following an exponential backoff curve
* between attempts.
*
* Each delay is made up of a "base" delay which follows the exponential
* backoff curve, and a "jitter" (+/- 50% by default) that is calculated and
* added to the base delay. This prevents clients from accidentally
* synchronizing their delays causing spikes of load to the backend.
*
* @private
* @internal
*/
class ExponentialBackoff {
constructor(options = {}) {
/**
* The number of retries that has been attempted.
*
* @private
* @internal
*/
this._retryCount = 0;
/**
* The backoff delay of the current attempt.
*
* @private
* @internal
*/
this.currentBaseMs = 0;
/**
* Whether we are currently waiting for backoff to complete.
*
* @private
* @internal
*/
this.awaitingBackoffCompletion = false;
this.initialDelayMs =
options.initialDelayMs !== undefined
? options.initialDelayMs
: exports.DEFAULT_BACKOFF_INITIAL_DELAY_MS;
this.backoffFactor =
options.backoffFactor !== undefined
? options.backoffFactor
: exports.DEFAULT_BACKOFF_FACTOR;
this.maxDelayMs =
options.maxDelayMs !== undefined
? options.maxDelayMs
: exports.DEFAULT_BACKOFF_MAX_DELAY_MS;
this.jitterFactor =
options.jitterFactor !== undefined
? options.jitterFactor
: DEFAULT_JITTER_FACTOR;
}
/**
* Resets the backoff delay and retry count.
*
* The very next backoffAndWait() will have no delay. If it is called again
* (i.e. due to an error), initialDelayMs (plus jitter) will be used, and
* subsequent ones will increase according to the backoffFactor.
*
* @private
* @internal
*/
reset() {
this._retryCount = 0;
this.currentBaseMs = 0;
}
/**
* Resets the backoff delay to the maximum delay (e.g. for use after a
* RESOURCE_EXHAUSTED error).
*
* @private
* @internal
*/
resetToMax() {
this.currentBaseMs = this.maxDelayMs;
}
/**
* Returns a promise that resolves after currentDelayMs, and increases the
* delay for any subsequent attempts.
*
* @return A Promise that resolves when the current delay elapsed.
* @private
* @internal
*/
backoffAndWait() {
if (this.awaitingBackoffCompletion) {
return Promise.reject(new Error('A backoff operation is already in progress.'));
}
if (this.retryCount > exports.MAX_RETRY_ATTEMPTS) {
return Promise.reject(new Error('Exceeded maximum number of retries allowed.'));
}
// First schedule using the current base (which may be 0 and should be
// honored as such).
const delayWithJitterMs = this.currentBaseMs + this.jitterDelayMs();
if (this.currentBaseMs > 0) {
(0, logger_1.logger)('ExponentialBackoff.backoffAndWait', null, `Backing off for ${delayWithJitterMs} ms ` +
`(base delay: ${this.currentBaseMs} ms)`);
}
// Apply backoff factor to determine next delay and ensure it is within
// bounds.
this.currentBaseMs *= this.backoffFactor;
this.currentBaseMs = Math.max(this.currentBaseMs, this.initialDelayMs);
this.currentBaseMs = Math.min(this.currentBaseMs, this.maxDelayMs);
this._retryCount += 1;
return new Promise(resolve => {
this.awaitingBackoffCompletion = true;
(0, exports.delayExecution)(() => {
this.awaitingBackoffCompletion = false;
resolve();
}, delayWithJitterMs);
});
}
// Visible for testing.
get retryCount() {
return this._retryCount;
}
/**
* Returns a randomized "jitter" delay based on the current base and jitter
* factor.
*
* @returns {number} The jitter to apply based on the current delay.
* @private
* @internal
*/
jitterDelayMs() {
return (Math.random() - 0.5) * this.jitterFactor * this.currentBaseMs;
}
}
exports.ExponentialBackoff = ExponentialBackoff;
//# sourceMappingURL=backoff.js.map

View File

@@ -0,0 +1,529 @@
/*!
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import type { GoogleError } from 'google-gax';
import { FieldPath, Firestore } from '.';
import { RateLimiter } from './rate-limiter';
import { Timestamp } from './timestamp';
import { WriteBatch, WriteResult } from './write-batch';
import GrpcStatus = FirebaseFirestore.GrpcStatus;
/*!
* The maximum number of writes can be can in a single batch that is being retried.
*/
export declare const RETRY_MAX_BATCH_SIZE = 10;
/*!
* The starting maximum number of operations per second as allowed by the
* 500/50/5 rule.
*
* https://firebase.google.com/docs/firestore/best-practices#ramping_up_traffic.
*/
export declare const DEFAULT_INITIAL_OPS_PER_SECOND_LIMIT = 500;
/*!
* The maximum number of operations per second as allowed by the 500/50/5 rule.
* By default the rate limiter will not exceed this value.
*
* https://firebase.google.com/docs/firestore/best-practices#ramping_up_traffic.
*/
export declare const DEFAULT_MAXIMUM_OPS_PER_SECOND_LIMIT = 10000;
/*!
* The default jitter to apply to the exponential backoff used in retries. For
* example, a factor of 0.3 means a 30% jitter is applied.
*/
export declare const DEFAULT_JITTER_FACTOR = 0.3;
/**
* Represents a single write for BulkWriter, encapsulating operation dispatch
* and error handling.
* @private
* @internal
*/
declare class BulkWriterOperation {
readonly ref: firestore.DocumentReference<unknown>;
private readonly type;
private readonly sendFn;
private readonly errorFn;
private readonly successFn;
private deferred;
private failedAttempts;
private lastStatus?;
private _backoffDuration;
/** Whether flush() was called when this was the last enqueued operation. */
private _flushed;
/**
* @param ref The document reference being written to.
* @param type The type of operation that created this write.
* @param sendFn A callback to invoke when the operation should be sent.
* @param errorFn The user provided global error callback.
* @param successFn The user provided global success callback.
*/
constructor(ref: firestore.DocumentReference<unknown>, type: 'create' | 'set' | 'update' | 'delete', sendFn: (op: BulkWriterOperation) => void, errorFn: (error: BulkWriterError) => boolean, successFn: (ref: firestore.DocumentReference<unknown>, result: WriteResult) => void);
get promise(): Promise<WriteResult>;
get backoffDuration(): number;
markFlushed(): void;
get flushed(): boolean;
onError(error: GoogleError): void;
private updateBackoffDuration;
onSuccess(result: WriteResult): void;
}
/**
* Used to represent a batch on the BatchQueue.
*
* @private
* @internal
*/
declare class BulkCommitBatch extends WriteBatch {
readonly docPaths: Set<string>;
readonly pendingOps: Array<BulkWriterOperation>;
private _maxBatchSize;
constructor(firestore: Firestore, maxBatchSize: number);
get maxBatchSize(): number;
setMaxBatchSize(size: number): void;
has(documentRef: firestore.DocumentReference<unknown>): boolean;
bulkCommit(options?: {
requestTag?: string;
}): Promise<void>;
/**
* Helper to update data structures associated with the operation and returns
* the result.
*/
processLastOperation(op: BulkWriterOperation): void;
}
/**
* The error thrown when a BulkWriter operation fails.
*
* @class BulkWriterError
*/
export declare class BulkWriterError extends Error {
/** The status code of the error. */
readonly code: GrpcStatus;
/** The error message of the error. */
readonly message: string;
/** The document reference the operation was performed on. */
readonly documentRef: firestore.DocumentReference<any, any>;
/** The type of operation performed. */
readonly operationType: 'create' | 'set' | 'update' | 'delete';
/** How many times this operation has been attempted unsuccessfully. */
readonly failedAttempts: number;
/**
* @private
* @internal
*/
constructor(
/** The status code of the error. */
code: GrpcStatus,
/** The error message of the error. */
message: string,
/** The document reference the operation was performed on. */
documentRef: firestore.DocumentReference<any, any>,
/** The type of operation performed. */
operationType: 'create' | 'set' | 'update' | 'delete',
/** How many times this operation has been attempted unsuccessfully. */
failedAttempts: number);
}
/**
* A Firestore BulkWriter that can be used to perform a large number of writes
* in parallel.
*
* @class BulkWriter
*/
export declare class BulkWriter {
private readonly firestore;
/**
* The maximum number of writes that can be in a single batch.
* Visible for testing.
* @private
* @internal
*/
private _maxBatchSize;
/**
* The batch that is currently used to schedule operations. Once this batch
* reaches maximum capacity, a new batch is created.
* @private
* @internal
*/
private _bulkCommitBatch;
/**
* A pointer to the tail of all active BulkWriter operations. This pointer
* is advanced every time a new write is enqueued.
* @private
* @internal
*/
private _lastOp;
/**
* When this BulkWriter instance has started to close, a flush promise is
* saved. Afterwards, no new operations can be enqueued, except for retry
* operations scheduled by the error handler.
* @private
* @internal
*/
private _closePromise;
/**
* Rate limiter used to throttle requests as per the 500/50/5 rule.
* Visible for testing.
* @private
* @internal
*/
readonly _rateLimiter: RateLimiter;
/**
* The number of pending operations enqueued on this BulkWriter instance.
* An operation is considered pending if BulkWriter has sent it via RPC and
* is awaiting the result.
* @private
* @internal
*/
private _pendingOpsCount;
/**
* An array containing buffered BulkWriter operations after the maximum number
* of pending operations has been enqueued.
* @private
* @internal
*/
private _bufferedOperations;
/**
* Whether a custom error handler has been set. BulkWriter only swallows
* errors if an error handler is set. Otherwise, an UnhandledPromiseRejection
* is thrown by Node if an operation promise is rejected without being
* handled.
* @private
* @internal
*/
private _errorHandlerSet;
/**
* @private
* @internal
*/
_getBufferedOperationsCount(): number;
/**
* @private
* @internal
*/
_setMaxBatchSize(size: number): void;
/**
* The maximum number of pending operations that can be enqueued onto this
* BulkWriter instance. Once the this number of writes have been enqueued,
* subsequent writes are buffered.
* @private
* @internal
*/
private _maxPendingOpCount;
/**
* @private
* @internal
*/
_setMaxPendingOpCount(newMax: number): void;
/**
* The user-provided callback to be run every time a BulkWriter operation
* successfully completes.
* @private
* @internal
*/
private _successFn;
/**
* The user-provided callback to be run every time a BulkWriter operation
* fails.
* @private
* @internal
*/
private _errorFn;
/** @private */
constructor(firestore: Firestore, options?: firestore.BulkWriterOptions);
/**
* Create a document with the provided data. This single operation will fail
* if a document exists at its location.
*
* @param {DocumentReference} documentRef A reference to the document to be
* created.
* @param {T} data The object to serialize as the document.
* @throws {Error} If the provided input is not a valid Firestore document.
* @returns {Promise<WriteResult>} A promise that resolves with the result of
* the write. If the write fails, the promise is rejected with a
* [BulkWriterError]{@link BulkWriterError}.
*
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
* let documentRef = firestore.collection('col').doc();
*
* bulkWriter
* .create(documentRef, {foo: 'bar'})
* .then(result => {
* console.log('Successfully executed write at: ', result);
* })
* .catch(err => {
* console.log('Write failed with: ', err);
* });
* });
* ```
*/
create<AppModelType, DbModelType extends firestore.DocumentData>(documentRef: firestore.DocumentReference<AppModelType, DbModelType>, data: firestore.WithFieldValue<AppModelType>): Promise<WriteResult>;
/**
* Delete a document from the database.
*
* @param {DocumentReference} documentRef A reference to the document to be
* deleted.
* @param {Precondition=} precondition A precondition to enforce for this
* delete.
* @param {Timestamp=} precondition.lastUpdateTime If set, enforces that the
* document was last updated at lastUpdateTime. Fails the batch if the
* document doesn't exist or was last updated at a different time.
* @returns {Promise<WriteResult>} A promise that resolves with the result of
* the delete. If the delete fails, the promise is rejected with a
* [BulkWriterError]{@link BulkWriterError}.
*
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
* let documentRef = firestore.doc('col/doc');
*
* bulkWriter
* .delete(documentRef)
* .then(result => {
* console.log('Successfully deleted document');
* })
* .catch(err => {
* console.log('Delete failed with: ', err);
* });
* });
* ```
*/
delete<AppModelType, DbModelType extends firestore.DocumentData>(documentRef: firestore.DocumentReference<AppModelType, DbModelType>, precondition?: firestore.Precondition): Promise<WriteResult>;
set<AppModelType, DbModelType extends firestore.DocumentData>(documentRef: firestore.DocumentReference<AppModelType, DbModelType>, data: Partial<AppModelType>, options: firestore.SetOptions): Promise<WriteResult>;
set<AppModelType, DbModelType extends firestore.DocumentData>(documentRef: firestore.DocumentReference<AppModelType, DbModelType>, data: AppModelType): Promise<WriteResult>;
/**
* Update fields of the document referred to by the provided
* [DocumentReference]{@link DocumentReference}. If the document doesn't yet
* exist, the update fails and the entire batch will be rejected.
*
* The update() method accepts either an object with field paths encoded as
* keys and field values encoded as values, or a variable number of arguments
* that alternate between field paths and field values. Nested fields can be
* updated by providing dot-separated field path strings or by providing
* FieldPath objects.
*
*
* A Precondition restricting this update can be specified as the last
* argument.
*
* @param {DocumentReference} documentRef A reference to the document to be
* updated.
* @param {UpdateData|string|FieldPath} dataOrField An object containing the
* fields and values with which to update the document or the path of the
* first field to update.
* @param {...(Precondition|*|string|FieldPath)} preconditionOrValues - An
* alternating list of field paths and values to update or a Precondition to
* restrict this update
* @throws {Error} If the provided input is not valid Firestore data.
* @returns {Promise<WriteResult>} A promise that resolves with the result of
* the write. If the write fails, the promise is rejected with a
* [BulkWriterError]{@link BulkWriterError}.
*
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
* let documentRef = firestore.doc('col/doc');
*
* bulkWriter
* .update(documentRef, {foo: 'bar'})
* .then(result => {
* console.log('Successfully executed write at: ', result);
* })
* .catch(err => {
* console.log('Write failed with: ', err);
* });
* });
* ```
*/
update<AppModelType, DbModelType extends firestore.DocumentData>(documentRef: firestore.DocumentReference<AppModelType, DbModelType>, dataOrField: firestore.UpdateData<DbModelType> | string | FieldPath, ...preconditionOrValues: Array<{
lastUpdateTime?: Timestamp;
} | unknown | string | FieldPath>): Promise<WriteResult>;
/**
* Callback function set by {@link BulkWriter#onWriteResult} that is run
* every time a {@link BulkWriter} operation successfully completes.
*
* @callback BulkWriter~successCallback
* @param {DocumentReference} documentRef The document reference the
* operation was performed on
* @param {WriteResult} result The server write time of the operation.
*/
/**
* Attaches a listener that is run every time a BulkWriter operation
* successfully completes.
*
* @param {BulkWriter~successCallback} successCallback A callback to be
* called every time a BulkWriter operation successfully completes.
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
*
* bulkWriter
* .onWriteResult((documentRef, result) => {
* console.log(
* 'Successfully executed write on document: ',
* documentRef,
* ' at: ',
* result
* );
* });
* ```
*/
onWriteResult(successCallback: (documentRef: firestore.DocumentReference<any, any>, result: WriteResult) => void): void;
/**
* Callback function set by {@link BulkWriter#onWriteError} that is run when
* a write fails in order to determine whether {@link BulkWriter} should
* retry the operation.
*
* @callback BulkWriter~shouldRetryCallback
* @param {BulkWriterError} error The error object with information about the
* operation and error.
* @returns {boolean} Whether or not to retry the failed operation. Returning
* `true` retries the operation. Returning `false` will stop the retry loop.
*/
/**
* Attaches an error handler listener that is run every time a BulkWriter
* operation fails.
*
* BulkWriter has a default error handler that retries UNAVAILABLE and
* ABORTED errors up to a maximum of 10 failed attempts. When an error
* handler is specified, the default error handler will be overwritten.
*
* @param shouldRetryCallback {BulkWriter~shouldRetryCallback} A callback to
* be called every time a BulkWriter operation fails. Returning `true` will
* retry the operation. Returning `false` will stop the retry loop.
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
*
* bulkWriter
* .onWriteError((error) => {
* if (
* error.code === GrpcStatus.UNAVAILABLE &&
* error.failedAttempts < MAX_RETRY_ATTEMPTS
* ) {
* return true;
* } else {
* console.log('Failed write at document: ', error.documentRef);
* return false;
* }
* });
* ```
*/
onWriteError(shouldRetryCallback: (error: BulkWriterError) => boolean): void;
/**
* Commits all writes that have been enqueued up to this point in parallel.
*
* Returns a Promise that resolves when all currently queued operations have
* been committed. The Promise will never be rejected since the results for
* each individual operation are conveyed via their individual Promises.
*
* The Promise resolves immediately if there are no pending writes. Otherwise,
* the Promise waits for all previously issued writes, but it does not wait
* for writes that were added after the method is called. If you want to wait
* for additional writes, call `flush()` again.
*
* @return {Promise<void>} A promise that resolves when all enqueued writes
* up to this point have been committed.
*
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
*
* bulkWriter.create(documentRef, {foo: 'bar'});
* bulkWriter.update(documentRef2, {foo: 'bar'});
* bulkWriter.delete(documentRef3);
* await flush().then(() => {
* console.log('Executed all writes');
* });
* ```
*/
flush(): Promise<void>;
/**
* Commits all enqueued writes and marks the BulkWriter instance as closed.
*
* After calling `close()`, calling any method will throw an error. Any
* retries scheduled as part of an `onWriteError()` handler will be run
* before the `close()` promise resolves.
*
* Returns a Promise that resolves when there are no more pending writes. The
* Promise will never be rejected. Calling this method will send all requests.
* The promise resolves immediately if there are no pending writes.
*
* @return {Promise<void>} A promise that resolves when all enqueued writes
* up to this point have been committed.
*
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
*
* bulkWriter.create(documentRef, {foo: 'bar'});
* bulkWriter.update(documentRef2, {foo: 'bar'});
* bulkWriter.delete(documentRef3);
* await close().then(() => {
* console.log('Executed all writes');
* });
* ```
*/
close(): Promise<void>;
/**
* Throws an error if the BulkWriter instance has been closed.
* @private
* @internal
*/
_verifyNotClosed(): void;
/**
* Sends the current batch and resets `this._bulkCommitBatch`.
*
* @param flush If provided, keeps re-sending operations until no more
* operations are enqueued. This allows retries to resolve as part of a
* `flush()` or `close()` call.
* @private
* @internal
*/
private _scheduleCurrentBatch;
/**
* Sends the provided batch once the rate limiter does not require any delay.
* @private
* @internal
*/
private _sendBatch;
/**
* Adds a 30% jitter to the provided backoff.
*
* @private
* @internal
*/
private static _applyJitter;
/**
* Schedules and runs the provided operation on the next available batch.
* @private
* @internal
*/
private _enqueue;
/**
* Manages the pending operation counter and schedules the next BulkWriter
* operation if we're under the maximum limit.
* @private
* @internal
*/
private _processBufferedOps;
/**
* Schedules the provided operations on current BulkCommitBatch.
* Sends the BulkCommitBatch if it reaches maximum capacity.
*
* @private
* @internal
*/
_sendFn(enqueueOnBatchCallback: (bulkCommitBatch: BulkCommitBatch) => void, op: BulkWriterOperation): void;
}
export {};

View File

@@ -0,0 +1,915 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BulkWriter = exports.BulkWriterError = exports.DEFAULT_JITTER_FACTOR = exports.DEFAULT_MAXIMUM_OPS_PER_SECOND_LIMIT = exports.DEFAULT_INITIAL_OPS_PER_SECOND_LIMIT = exports.RETRY_MAX_BATCH_SIZE = void 0;
const assert = require("assert");
const backoff_1 = require("./backoff");
const rate_limiter_1 = require("./rate-limiter");
const timestamp_1 = require("./timestamp");
const util_1 = require("./util");
const write_batch_1 = require("./write-batch");
const validate_1 = require("./validate");
const logger_1 = require("./logger");
const trace_util_1 = require("./telemetry/trace-util");
/*!
* The maximum number of writes that can be in a single batch.
*/
const MAX_BATCH_SIZE = 20;
/*!
* The maximum number of writes can be can in a single batch that is being retried.
*/
exports.RETRY_MAX_BATCH_SIZE = 10;
/*!
* The starting maximum number of operations per second as allowed by the
* 500/50/5 rule.
*
* https://firebase.google.com/docs/firestore/best-practices#ramping_up_traffic.
*/
exports.DEFAULT_INITIAL_OPS_PER_SECOND_LIMIT = 500;
/*!
* The maximum number of operations per second as allowed by the 500/50/5 rule.
* By default the rate limiter will not exceed this value.
*
* https://firebase.google.com/docs/firestore/best-practices#ramping_up_traffic.
*/
exports.DEFAULT_MAXIMUM_OPS_PER_SECOND_LIMIT = 10000;
/*!
* The default jitter to apply to the exponential backoff used in retries. For
* example, a factor of 0.3 means a 30% jitter is applied.
*/
exports.DEFAULT_JITTER_FACTOR = 0.3;
/*!
* The rate by which to increase the capacity as specified by the 500/50/5 rule.
*/
const RATE_LIMITER_MULTIPLIER = 1.5;
/*!
* How often the operations per second capacity should increase in milliseconds
* as specified by the 500/50/5 rule.
*/
const RATE_LIMITER_MULTIPLIER_MILLIS = 5 * 60 * 1000;
/*!
* The default maximum number of pending operations that can be enqueued onto a
* BulkWriter instance. An operation is considered pending if BulkWriter has
* sent it via RPC and is awaiting the result. BulkWriter buffers additional
* writes after this many pending operations in order to avoiding going OOM.
*/
const DEFAULT_MAXIMUM_PENDING_OPERATIONS_COUNT = 500;
/**
* Represents a single write for BulkWriter, encapsulating operation dispatch
* and error handling.
* @private
* @internal
*/
class BulkWriterOperation {
/**
* @param ref The document reference being written to.
* @param type The type of operation that created this write.
* @param sendFn A callback to invoke when the operation should be sent.
* @param errorFn The user provided global error callback.
* @param successFn The user provided global success callback.
*/
constructor(ref, type, sendFn, errorFn, successFn) {
this.ref = ref;
this.type = type;
this.sendFn = sendFn;
this.errorFn = errorFn;
this.successFn = successFn;
this.deferred = new util_1.Deferred();
this.failedAttempts = 0;
this._backoffDuration = 0;
/** Whether flush() was called when this was the last enqueued operation. */
this._flushed = false;
}
get promise() {
return this.deferred.promise;
}
get backoffDuration() {
return this._backoffDuration;
}
markFlushed() {
this._flushed = true;
}
get flushed() {
return this._flushed;
}
onError(error) {
++this.failedAttempts;
try {
const bulkWriterError = new BulkWriterError(error.code, error.message, this.ref, this.type, this.failedAttempts);
const shouldRetry = this.errorFn(bulkWriterError);
(0, logger_1.logger)('BulkWriter.errorFn', null, 'Ran error callback on error code:', error.code, ', shouldRetry:', shouldRetry, ' for document:', this.ref.path);
if (shouldRetry) {
this.lastStatus = error.code;
this.updateBackoffDuration();
this.sendFn(this);
}
else {
this.deferred.reject(bulkWriterError);
}
}
catch (userCallbackError) {
this.deferred.reject(userCallbackError);
}
}
updateBackoffDuration() {
if (this.lastStatus === 8 /* StatusCode.RESOURCE_EXHAUSTED */) {
this._backoffDuration = backoff_1.DEFAULT_BACKOFF_MAX_DELAY_MS;
}
else if (this._backoffDuration === 0) {
this._backoffDuration = backoff_1.DEFAULT_BACKOFF_INITIAL_DELAY_MS;
}
else {
this._backoffDuration *= backoff_1.DEFAULT_BACKOFF_FACTOR;
}
}
onSuccess(result) {
try {
this.successFn(this.ref, result);
this.deferred.resolve(result);
}
catch (userCallbackError) {
this.deferred.reject(userCallbackError);
}
}
}
/**
* Used to represent a batch on the BatchQueue.
*
* @private
* @internal
*/
class BulkCommitBatch extends write_batch_1.WriteBatch {
constructor(firestore, maxBatchSize) {
super(firestore);
// The set of document reference paths present in the WriteBatch.
this.docPaths = new Set();
// An array of pending write operations. Only contains writes that have not
// been resolved.
this.pendingOps = [];
this._maxBatchSize = maxBatchSize;
}
get maxBatchSize() {
return this._maxBatchSize;
}
setMaxBatchSize(size) {
assert(this.pendingOps.length <= size, 'New batch size cannot be less than the number of enqueued writes');
this._maxBatchSize = size;
}
has(documentRef) {
return this.docPaths.has(documentRef.path);
}
async bulkCommit(options = {}) {
return this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_BULK_WRITER_COMMIT, async () => {
var _a;
const tag = (_a = options === null || options === void 0 ? void 0 : options.requestTag) !== null && _a !== void 0 ? _a : (0, util_1.requestTag)();
// Capture the error stack to preserve stack tracing across async calls.
const stack = Error().stack;
let response;
try {
(0, logger_1.logger)('BulkCommitBatch.bulkCommit', tag, `Sending next batch with ${this._opCount} writes`);
const retryCodes = (0, util_1.getRetryCodes)('batchWrite');
response = await this._commit({ retryCodes, methodName: 'batchWrite', requestTag: tag });
}
catch (err) {
// Map the failure to each individual write's result.
const ops = Array.from({ length: this.pendingOps.length });
response = {
writeResults: ops.map(() => {
return {};
}),
status: ops.map(() => err),
};
}
for (let i = 0; i < (response.writeResults || []).length; ++i) {
// Since delete operations currently do not have write times, use a
// sentinel Timestamp value.
// TODO(b/158502664): Use actual delete timestamp.
const DELETE_TIMESTAMP_SENTINEL = timestamp_1.Timestamp.fromMillis(0);
const status = (response.status || [])[i];
if (status.code === 0 /* StatusCode.OK */) {
const updateTime = timestamp_1.Timestamp.fromProto(response.writeResults[i].updateTime || DELETE_TIMESTAMP_SENTINEL);
this.pendingOps[i].onSuccess(new write_batch_1.WriteResult(updateTime));
}
else {
const error = new (require('google-gax/build/src/fallback').GoogleError)(status.message || undefined);
error.code = status.code;
this.pendingOps[i].onError((0, util_1.wrapError)(error, stack));
}
}
}, {
[trace_util_1.ATTRIBUTE_KEY_DOC_COUNT]: this._opCount,
});
}
/**
* Helper to update data structures associated with the operation and returns
* the result.
*/
processLastOperation(op) {
assert(!this.docPaths.has(op.ref.path), 'Batch should not contain writes to the same document');
this.docPaths.add(op.ref.path);
this.pendingOps.push(op);
}
}
/**
* Used to represent a buffered BulkWriterOperation.
*
* @private
* @internal
*/
class BufferedOperation {
constructor(operation, sendFn) {
this.operation = operation;
this.sendFn = sendFn;
}
}
/**
* The error thrown when a BulkWriter operation fails.
*
* @class BulkWriterError
*/
class BulkWriterError extends Error {
/**
* @private
* @internal
*/
constructor(
/** The status code of the error. */
code,
/** The error message of the error. */
message,
/** The document reference the operation was performed on. */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
documentRef,
/** The type of operation performed. */
operationType,
/** How many times this operation has been attempted unsuccessfully. */
failedAttempts) {
super(message);
this.code = code;
this.message = message;
this.documentRef = documentRef;
this.operationType = operationType;
this.failedAttempts = failedAttempts;
}
}
exports.BulkWriterError = BulkWriterError;
/**
* A Firestore BulkWriter that can be used to perform a large number of writes
* in parallel.
*
* @class BulkWriter
*/
class BulkWriter {
// Visible for testing.
/**
* @private
* @internal
*/
_getBufferedOperationsCount() {
return this._bufferedOperations.length;
}
// Visible for testing.
/**
* @private
* @internal
*/
_setMaxBatchSize(size) {
assert(this._bulkCommitBatch.pendingOps.length === 0, 'BulkCommitBatch should be empty');
this._maxBatchSize = size;
this._bulkCommitBatch = new BulkCommitBatch(this.firestore, size);
}
// Visible for testing.
/**
* @private
* @internal
*/
_setMaxPendingOpCount(newMax) {
this._maxPendingOpCount = newMax;
}
/** @private */
constructor(firestore, options) {
var _a, _b;
this.firestore = firestore;
/**
* The maximum number of writes that can be in a single batch.
* Visible for testing.
* @private
* @internal
*/
this._maxBatchSize = MAX_BATCH_SIZE;
/**
* The batch that is currently used to schedule operations. Once this batch
* reaches maximum capacity, a new batch is created.
* @private
* @internal
*/
this._bulkCommitBatch = new BulkCommitBatch(this.firestore, this._maxBatchSize);
/**
* A pointer to the tail of all active BulkWriter operations. This pointer
* is advanced every time a new write is enqueued.
* @private
* @internal
*/
this._lastOp = Promise.resolve();
/**
* The number of pending operations enqueued on this BulkWriter instance.
* An operation is considered pending if BulkWriter has sent it via RPC and
* is awaiting the result.
* @private
* @internal
*/
this._pendingOpsCount = 0;
/**
* An array containing buffered BulkWriter operations after the maximum number
* of pending operations has been enqueued.
* @private
* @internal
*/
this._bufferedOperations = [];
/**
* Whether a custom error handler has been set. BulkWriter only swallows
* errors if an error handler is set. Otherwise, an UnhandledPromiseRejection
* is thrown by Node if an operation promise is rejected without being
* handled.
* @private
* @internal
*/
this._errorHandlerSet = false;
/**
* The maximum number of pending operations that can be enqueued onto this
* BulkWriter instance. Once the this number of writes have been enqueued,
* subsequent writes are buffered.
* @private
* @internal
*/
this._maxPendingOpCount = DEFAULT_MAXIMUM_PENDING_OPERATIONS_COUNT;
/**
* The user-provided callback to be run every time a BulkWriter operation
* successfully completes.
* @private
* @internal
*/
this._successFn = () => { };
/**
* The user-provided callback to be run every time a BulkWriter operation
* fails.
* @private
* @internal
*/
this._errorFn = error => {
const isRetryableDeleteError = error.operationType === 'delete' &&
error.code === 13 /* StatusCode.INTERNAL */;
const retryCodes = (0, util_1.getRetryCodes)('batchWrite');
return ((retryCodes.includes(error.code) || isRetryableDeleteError) &&
error.failedAttempts < backoff_1.MAX_RETRY_ATTEMPTS);
};
this.firestore._incrementBulkWritersCount();
validateBulkWriterOptions(options);
if ((options === null || options === void 0 ? void 0 : options.throttling) === false) {
this._rateLimiter = new rate_limiter_1.RateLimiter(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY);
}
else {
let startingRate = exports.DEFAULT_INITIAL_OPS_PER_SECOND_LIMIT;
let maxRate = exports.DEFAULT_MAXIMUM_OPS_PER_SECOND_LIMIT;
if (typeof (options === null || options === void 0 ? void 0 : options.throttling) !== 'boolean') {
if (((_a = options === null || options === void 0 ? void 0 : options.throttling) === null || _a === void 0 ? void 0 : _a.maxOpsPerSecond) !== undefined) {
maxRate = options.throttling.maxOpsPerSecond;
}
if (((_b = options === null || options === void 0 ? void 0 : options.throttling) === null || _b === void 0 ? void 0 : _b.initialOpsPerSecond) !== undefined) {
startingRate = options.throttling.initialOpsPerSecond;
}
// The initial validation step ensures that the maxOpsPerSecond is
// greater than initialOpsPerSecond. If this inequality is true, that
// means initialOpsPerSecond was not set and maxOpsPerSecond is less
// than the default starting rate.
if (maxRate < startingRate) {
startingRate = maxRate;
}
// Ensure that the batch size is not larger than the number of allowed
// operations per second.
if (startingRate < this._maxBatchSize) {
this._maxBatchSize = startingRate;
}
}
this._rateLimiter = new rate_limiter_1.RateLimiter(startingRate, RATE_LIMITER_MULTIPLIER, RATE_LIMITER_MULTIPLIER_MILLIS, maxRate);
}
}
/**
* Create a document with the provided data. This single operation will fail
* if a document exists at its location.
*
* @param {DocumentReference} documentRef A reference to the document to be
* created.
* @param {T} data The object to serialize as the document.
* @throws {Error} If the provided input is not a valid Firestore document.
* @returns {Promise<WriteResult>} A promise that resolves with the result of
* the write. If the write fails, the promise is rejected with a
* [BulkWriterError]{@link BulkWriterError}.
*
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
* let documentRef = firestore.collection('col').doc();
*
* bulkWriter
* .create(documentRef, {foo: 'bar'})
* .then(result => {
* console.log('Successfully executed write at: ', result);
* })
* .catch(err => {
* console.log('Write failed with: ', err);
* });
* });
* ```
*/
create(documentRef, data) {
this._verifyNotClosed();
return this._enqueue(documentRef, 'create', bulkCommitBatch => bulkCommitBatch.create(documentRef, data));
}
/**
* Delete a document from the database.
*
* @param {DocumentReference} documentRef A reference to the document to be
* deleted.
* @param {Precondition=} precondition A precondition to enforce for this
* delete.
* @param {Timestamp=} precondition.lastUpdateTime If set, enforces that the
* document was last updated at lastUpdateTime. Fails the batch if the
* document doesn't exist or was last updated at a different time.
* @returns {Promise<WriteResult>} A promise that resolves with the result of
* the delete. If the delete fails, the promise is rejected with a
* [BulkWriterError]{@link BulkWriterError}.
*
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
* let documentRef = firestore.doc('col/doc');
*
* bulkWriter
* .delete(documentRef)
* .then(result => {
* console.log('Successfully deleted document');
* })
* .catch(err => {
* console.log('Delete failed with: ', err);
* });
* });
* ```
*/
delete(documentRef, precondition) {
this._verifyNotClosed();
return this._enqueue(documentRef, 'delete', bulkCommitBatch => bulkCommitBatch.delete(documentRef, precondition));
}
/**
* Write to the document referred to by the provided
* [DocumentReference]{@link DocumentReference}. If the document does not
* exist yet, it will be created. If you pass [SetOptions]{@link SetOptions}.,
* the provided data can be merged into the existing document.
*
* @param {DocumentReference} documentRef A reference to the document to be
* set.
* @param {T} data The object to serialize as the document.
* @param {SetOptions=} options An object to configure the set behavior.
* @throws {Error} If the provided input is not a valid Firestore document.
* @param {boolean=} options.merge - If true, set() merges the values
* specified in its data argument. Fields omitted from this set() call remain
* untouched. If your input sets any field to an empty map, all nested fields
* are overwritten.
* @param {Array.<string|FieldPath>=} options.mergeFields - If provided, set()
* only replaces the specified field paths. Any field path that is not
* specified is ignored and remains untouched. If your input sets any field to
* an empty map, all nested fields are overwritten.
* @returns {Promise<WriteResult>} A promise that resolves with the result of
* the write. If the write fails, the promise is rejected with a
* [BulkWriterError]{@link BulkWriterError}.
*
*
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
* let documentRef = firestore.collection('col').doc();
*
* bulkWriter
* .set(documentRef, {foo: 'bar'})
* .then(result => {
* console.log('Successfully executed write at: ', result);
* })
* .catch(err => {
* console.log('Write failed with: ', err);
* });
* });
* ```
*/
set(documentRef, data, options) {
this._verifyNotClosed();
return this._enqueue(documentRef, 'set', bulkCommitBatch => {
if (options) {
return bulkCommitBatch.set(documentRef, data, options);
}
else {
return bulkCommitBatch.set(documentRef, data);
}
});
}
/**
* Update fields of the document referred to by the provided
* [DocumentReference]{@link DocumentReference}. If the document doesn't yet
* exist, the update fails and the entire batch will be rejected.
*
* The update() method accepts either an object with field paths encoded as
* keys and field values encoded as values, or a variable number of arguments
* that alternate between field paths and field values. Nested fields can be
* updated by providing dot-separated field path strings or by providing
* FieldPath objects.
*
*
* A Precondition restricting this update can be specified as the last
* argument.
*
* @param {DocumentReference} documentRef A reference to the document to be
* updated.
* @param {UpdateData|string|FieldPath} dataOrField An object containing the
* fields and values with which to update the document or the path of the
* first field to update.
* @param {...(Precondition|*|string|FieldPath)} preconditionOrValues - An
* alternating list of field paths and values to update or a Precondition to
* restrict this update
* @throws {Error} If the provided input is not valid Firestore data.
* @returns {Promise<WriteResult>} A promise that resolves with the result of
* the write. If the write fails, the promise is rejected with a
* [BulkWriterError]{@link BulkWriterError}.
*
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
* let documentRef = firestore.doc('col/doc');
*
* bulkWriter
* .update(documentRef, {foo: 'bar'})
* .then(result => {
* console.log('Successfully executed write at: ', result);
* })
* .catch(err => {
* console.log('Write failed with: ', err);
* });
* });
* ```
*/
update(documentRef, dataOrField, ...preconditionOrValues) {
this._verifyNotClosed();
return this._enqueue(documentRef, 'update', bulkCommitBatch => bulkCommitBatch.update(documentRef, dataOrField, ...preconditionOrValues));
}
/**
* Callback function set by {@link BulkWriter#onWriteResult} that is run
* every time a {@link BulkWriter} operation successfully completes.
*
* @callback BulkWriter~successCallback
* @param {DocumentReference} documentRef The document reference the
* operation was performed on
* @param {WriteResult} result The server write time of the operation.
*/
/**
* Attaches a listener that is run every time a BulkWriter operation
* successfully completes.
*
* @param {BulkWriter~successCallback} successCallback A callback to be
* called every time a BulkWriter operation successfully completes.
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
*
* bulkWriter
* .onWriteResult((documentRef, result) => {
* console.log(
* 'Successfully executed write on document: ',
* documentRef,
* ' at: ',
* result
* );
* });
* ```
*/
onWriteResult(successCallback) {
this._successFn = successCallback;
}
/**
* Callback function set by {@link BulkWriter#onWriteError} that is run when
* a write fails in order to determine whether {@link BulkWriter} should
* retry the operation.
*
* @callback BulkWriter~shouldRetryCallback
* @param {BulkWriterError} error The error object with information about the
* operation and error.
* @returns {boolean} Whether or not to retry the failed operation. Returning
* `true` retries the operation. Returning `false` will stop the retry loop.
*/
/**
* Attaches an error handler listener that is run every time a BulkWriter
* operation fails.
*
* BulkWriter has a default error handler that retries UNAVAILABLE and
* ABORTED errors up to a maximum of 10 failed attempts. When an error
* handler is specified, the default error handler will be overwritten.
*
* @param shouldRetryCallback {BulkWriter~shouldRetryCallback} A callback to
* be called every time a BulkWriter operation fails. Returning `true` will
* retry the operation. Returning `false` will stop the retry loop.
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
*
* bulkWriter
* .onWriteError((error) => {
* if (
* error.code === GrpcStatus.UNAVAILABLE &&
* error.failedAttempts < MAX_RETRY_ATTEMPTS
* ) {
* return true;
* } else {
* console.log('Failed write at document: ', error.documentRef);
* return false;
* }
* });
* ```
*/
onWriteError(shouldRetryCallback) {
this._errorHandlerSet = true;
this._errorFn = shouldRetryCallback;
}
/**
* Commits all writes that have been enqueued up to this point in parallel.
*
* Returns a Promise that resolves when all currently queued operations have
* been committed. The Promise will never be rejected since the results for
* each individual operation are conveyed via their individual Promises.
*
* The Promise resolves immediately if there are no pending writes. Otherwise,
* the Promise waits for all previously issued writes, but it does not wait
* for writes that were added after the method is called. If you want to wait
* for additional writes, call `flush()` again.
*
* @return {Promise<void>} A promise that resolves when all enqueued writes
* up to this point have been committed.
*
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
*
* bulkWriter.create(documentRef, {foo: 'bar'});
* bulkWriter.update(documentRef2, {foo: 'bar'});
* bulkWriter.delete(documentRef3);
* await flush().then(() => {
* console.log('Executed all writes');
* });
* ```
*/
flush() {
this._verifyNotClosed();
this._scheduleCurrentBatch(/* flush= */ true);
// Mark the most recent operation as flushed to ensure that the batch
// containing it will be sent once it's popped from the buffer.
if (this._bufferedOperations.length > 0) {
this._bufferedOperations[this._bufferedOperations.length - 1].operation.markFlushed();
}
return this._lastOp;
}
/**
* Commits all enqueued writes and marks the BulkWriter instance as closed.
*
* After calling `close()`, calling any method will throw an error. Any
* retries scheduled as part of an `onWriteError()` handler will be run
* before the `close()` promise resolves.
*
* Returns a Promise that resolves when there are no more pending writes. The
* Promise will never be rejected. Calling this method will send all requests.
* The promise resolves immediately if there are no pending writes.
*
* @return {Promise<void>} A promise that resolves when all enqueued writes
* up to this point have been committed.
*
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
*
* bulkWriter.create(documentRef, {foo: 'bar'});
* bulkWriter.update(documentRef2, {foo: 'bar'});
* bulkWriter.delete(documentRef3);
* await close().then(() => {
* console.log('Executed all writes');
* });
* ```
*/
close() {
if (!this._closePromise) {
this._closePromise = this.flush();
this.firestore._decrementBulkWritersCount();
}
return this._closePromise;
}
/**
* Throws an error if the BulkWriter instance has been closed.
* @private
* @internal
*/
_verifyNotClosed() {
if (this._closePromise) {
throw new Error('BulkWriter has already been closed.');
}
}
/**
* Sends the current batch and resets `this._bulkCommitBatch`.
*
* @param flush If provided, keeps re-sending operations until no more
* operations are enqueued. This allows retries to resolve as part of a
* `flush()` or `close()` call.
* @private
* @internal
*/
_scheduleCurrentBatch(flush = false) {
if (this._bulkCommitBatch._opCount === 0)
return;
const pendingBatch = this._bulkCommitBatch;
this._bulkCommitBatch = new BulkCommitBatch(this.firestore, this._maxBatchSize);
// Use the write with the longest backoff duration when determining backoff.
const highestBackoffDuration = pendingBatch.pendingOps.reduce((prev, cur) => (prev.backoffDuration > cur.backoffDuration ? prev : cur)).backoffDuration;
const backoffMsWithJitter = BulkWriter._applyJitter(highestBackoffDuration);
const delayedExecution = new util_1.Deferred();
if (backoffMsWithJitter > 0) {
(0, backoff_1.delayExecution)(() => delayedExecution.resolve(), backoffMsWithJitter);
}
else {
delayedExecution.resolve();
}
delayedExecution.promise.then(() => this._sendBatch(pendingBatch, flush));
}
/**
* Sends the provided batch once the rate limiter does not require any delay.
* @private
* @internal
*/
async _sendBatch(batch, flush = false) {
const tag = (0, util_1.requestTag)();
// Send the batch if it is does not require any delay, or schedule another
// attempt after the appropriate timeout.
const underRateLimit = this._rateLimiter.tryMakeRequest(batch._opCount);
if (underRateLimit) {
await batch.bulkCommit({ requestTag: tag });
if (flush)
this._scheduleCurrentBatch(flush);
}
else {
const delayMs = this._rateLimiter.getNextRequestDelayMs(batch._opCount);
(0, logger_1.logger)('BulkWriter._sendBatch', tag, `Backing off for ${delayMs} seconds`);
(0, backoff_1.delayExecution)(() => this._sendBatch(batch, flush), delayMs);
}
}
/**
* Adds a 30% jitter to the provided backoff.
*
* @private
* @internal
*/
static _applyJitter(backoffMs) {
if (backoffMs === 0)
return 0;
// Random value in [-0.3, 0.3].
const jitter = exports.DEFAULT_JITTER_FACTOR * (Math.random() * 2 - 1);
return Math.min(backoff_1.DEFAULT_BACKOFF_MAX_DELAY_MS, backoffMs + jitter * backoffMs);
}
/**
* Schedules and runs the provided operation on the next available batch.
* @private
* @internal
*/
_enqueue(ref, type, enqueueOnBatchCallback) {
const bulkWriterOp = new BulkWriterOperation(ref, type, this._sendFn.bind(this, enqueueOnBatchCallback), this._errorFn.bind(this), this._successFn.bind(this));
// Swallow the error if the developer has set an error listener. This
// prevents UnhandledPromiseRejections from being thrown if a floating
// BulkWriter operation promise fails when an error handler is specified.
//
// This is done here in order to chain the caught promise onto `lastOp`,
// which ensures that flush() resolves after the operation promise.
const userPromise = bulkWriterOp.promise.catch(err => {
if (!this._errorHandlerSet) {
throw err;
}
else {
return bulkWriterOp.promise;
}
});
// Advance the `_lastOp` pointer. This ensures that `_lastOp` only resolves
// when both the previous and the current write resolve.
this._lastOp = this._lastOp.then(() => (0, util_1.silencePromise)(userPromise));
// Schedule the operation if the BulkWriter has fewer than the maximum
// number of allowed pending operations, or add the operation to the
// buffer.
if (this._pendingOpsCount < this._maxPendingOpCount) {
this._pendingOpsCount++;
this._sendFn(enqueueOnBatchCallback, bulkWriterOp);
}
else {
this._bufferedOperations.push(new BufferedOperation(bulkWriterOp, () => {
this._pendingOpsCount++;
this._sendFn(enqueueOnBatchCallback, bulkWriterOp);
}));
}
// Chain the BulkWriter operation promise with the buffer processing logic
// in order to ensure that it runs and that subsequent operations are
// enqueued before the next batch is scheduled in `_sendBatch()`.
return userPromise
.then(res => {
this._pendingOpsCount--;
this._processBufferedOps();
return res;
})
.catch(err => {
this._pendingOpsCount--;
this._processBufferedOps();
throw err;
});
}
/**
* Manages the pending operation counter and schedules the next BulkWriter
* operation if we're under the maximum limit.
* @private
* @internal
*/
_processBufferedOps() {
if (this._pendingOpsCount < this._maxPendingOpCount &&
this._bufferedOperations.length > 0) {
const nextOp = this._bufferedOperations.shift();
nextOp.sendFn();
}
}
/**
* Schedules the provided operations on current BulkCommitBatch.
* Sends the BulkCommitBatch if it reaches maximum capacity.
*
* @private
* @internal
*/
_sendFn(enqueueOnBatchCallback, op) {
// A backoff duration greater than 0 implies that this batch is a retry.
// Retried writes are sent with a batch size of 10 in order to guarantee
// that the batch is under the 10MiB limit.
if (op.backoffDuration > 0) {
if (this._bulkCommitBatch.pendingOps.length >= exports.RETRY_MAX_BATCH_SIZE) {
this._scheduleCurrentBatch(/* flush= */ false);
}
this._bulkCommitBatch.setMaxBatchSize(exports.RETRY_MAX_BATCH_SIZE);
}
if (this._bulkCommitBatch.has(op.ref)) {
// Create a new batch since the backend doesn't support batches with two
// writes to the same document.
this._scheduleCurrentBatch();
}
enqueueOnBatchCallback(this._bulkCommitBatch);
this._bulkCommitBatch.processLastOperation(op);
if (this._bulkCommitBatch._opCount === this._bulkCommitBatch.maxBatchSize) {
this._scheduleCurrentBatch();
}
else if (op.flushed) {
// If flush() was called before this operation was enqueued into a batch,
// we still need to schedule it.
this._scheduleCurrentBatch(/* flush= */ true);
}
}
}
exports.BulkWriter = BulkWriter;
/**
* Validates the use of 'value' as BulkWriterOptions.
*
* @private
* @internal
* @param value The BulkWriterOptions object to validate.
* @throws if the input is not a valid BulkWriterOptions object.
*/
function validateBulkWriterOptions(value) {
if ((0, validate_1.validateOptional)(value, { optional: true })) {
return;
}
const argName = 'options';
if (!(0, util_1.isObject)(value)) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(argName, 'bulkWriter() options argument')} Input is not an object.`);
}
const options = value;
if (options.throttling === undefined ||
typeof options.throttling === 'boolean') {
return;
}
if (options.throttling.initialOpsPerSecond !== undefined) {
(0, validate_1.validateInteger)('initialOpsPerSecond', options.throttling.initialOpsPerSecond, {
minValue: 1,
});
}
if (options.throttling.maxOpsPerSecond !== undefined) {
(0, validate_1.validateInteger)('maxOpsPerSecond', options.throttling.maxOpsPerSecond, {
minValue: 1,
});
if (options.throttling.initialOpsPerSecond !== undefined &&
options.throttling.initialOpsPerSecond >
options.throttling.maxOpsPerSecond) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(argName, 'bulkWriter() options argument')} "maxOpsPerSecond" cannot be less than "initialOpsPerSecond".`);
}
}
}
//# sourceMappingURL=bulk-writer.js.map

View File

@@ -0,0 +1,24 @@
import { DocumentSnapshot } from './document';
import { QuerySnapshot } from './reference/query-snapshot';
/**
* Builds a Firestore data bundle with results from the given document and query snapshots.
*/
export declare class BundleBuilder {
readonly bundleId: string;
private documents;
private namedQueries;
private latestReadTime;
constructor(bundleId: string);
add(documentSnapshot: DocumentSnapshot): BundleBuilder;
add(queryName: string, querySnapshot: QuerySnapshot): BundleBuilder;
private addBundledDocument;
private addNamedQuery;
/**
* Converts a IBundleElement to a Buffer whose content is the length prefixed JSON representation
* of the element.
* @private
* @internal
*/
private elementToLengthPrefixedBuffer;
build(): Buffer;
}

View File

@@ -0,0 +1,207 @@
"use strict";
// 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
//
// https://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.
Object.defineProperty(exports, "__esModule", { value: true });
exports.BundleBuilder = void 0;
const document_1 = require("./document");
const query_snapshot_1 = require("./reference/query-snapshot");
const timestamp_1 = require("./timestamp");
const validate_1 = require("./validate");
const BUNDLE_VERSION = 1;
/**
* Builds a Firestore data bundle with results from the given document and query snapshots.
*/
class BundleBuilder {
constructor(bundleId) {
this.bundleId = bundleId;
// Resulting documents for the bundle, keyed by full document path.
this.documents = new Map();
// Named queries saved in the bundle, keyed by query name.
this.namedQueries = new Map();
// The latest read time among all bundled documents and queries.
this.latestReadTime = new timestamp_1.Timestamp(0, 0);
}
/**
* Adds a Firestore document snapshot or query snapshot to the bundle.
* Both the documents data and the query read time will be included in the bundle.
*
* @param {DocumentSnapshot | string} documentOrName A document snapshot to add or a name of a query.
* @param {Query=} querySnapshot A query snapshot to add to the bundle, if provided.
* @returns {BundleBuilder} This instance.
*
* @example
* ```
* const bundle = firestore.bundle('data-bundle');
* const docSnapshot = await firestore.doc('abc/123').get();
* const querySnapshot = await firestore.collection('coll').get();
*
* const bundleBuffer = bundle.add(docSnapshot) // Add a document
* .add('coll-query', querySnapshot) // Add a named query.
* .build()
* // Save `bundleBuffer` to CDN or stream it to clients.
* ```
*/
add(documentOrName, querySnapshot) {
// eslint-disable-next-line prefer-rest-params
(0, validate_1.validateMinNumberOfArguments)('BundleBuilder.add', arguments, 1);
// eslint-disable-next-line prefer-rest-params
(0, validate_1.validateMaxNumberOfArguments)('BundleBuilder.add', arguments, 2);
if (arguments.length === 1) {
validateDocumentSnapshot('documentOrName', documentOrName);
this.addBundledDocument(documentOrName);
}
else {
(0, validate_1.validateString)('documentOrName', documentOrName);
validateQuerySnapshot('querySnapshot', querySnapshot);
this.addNamedQuery(documentOrName, querySnapshot);
}
return this;
}
addBundledDocument(snap, queryName) {
const originalDocument = this.documents.get(snap.ref.path);
const originalQueries = originalDocument === null || originalDocument === void 0 ? void 0 : originalDocument.metadata.queries;
// Update with document built from `snap` because it is newer.
if (!originalDocument ||
timestamp_1.Timestamp.fromProto(originalDocument.metadata.readTime) < snap.readTime) {
const docProto = snap.toDocumentProto();
this.documents.set(snap.ref.path, {
document: snap.exists ? docProto : undefined,
metadata: {
name: docProto.name,
readTime: snap.readTime.toProto().timestampValue,
exists: snap.exists,
},
});
}
// Update `queries` to include both original and `queryName`.
const newDocument = this.documents.get(snap.ref.path);
newDocument.metadata.queries = originalQueries || [];
if (queryName) {
newDocument.metadata.queries.push(queryName);
}
if (snap.readTime > this.latestReadTime) {
this.latestReadTime = snap.readTime;
}
}
addNamedQuery(name, querySnap) {
if (this.namedQueries.has(name)) {
throw new Error(`Query name conflict: ${name} has already been added.`);
}
this.namedQueries.set(name, {
name,
bundledQuery: querySnap.query._toBundledQuery(),
readTime: querySnap.readTime.toProto().timestampValue,
});
for (const snap of querySnap.docs) {
this.addBundledDocument(snap, name);
}
if (querySnap.readTime > this.latestReadTime) {
this.latestReadTime = querySnap.readTime;
}
}
/**
* Converts a IBundleElement to a Buffer whose content is the length prefixed JSON representation
* of the element.
* @private
* @internal
*/
elementToLengthPrefixedBuffer(bundleElement) {
// Convert to a valid proto message object then take its JSON representation.
// This take cares of stuff like converting internal byte array fields
// to Base64 encodings.
// We lazy-load the Proto file to reduce cold-start times.
const message = require('../protos/firestore_v1_proto_api')
.firestore.BundleElement.fromObject(bundleElement)
.toJSON();
const buffer = Buffer.from(JSON.stringify(message), 'utf-8');
const lengthBuffer = Buffer.from(buffer.length.toString());
return Buffer.concat([lengthBuffer, buffer]);
}
build() {
let bundleBuffer = Buffer.alloc(0);
for (const namedQuery of this.namedQueries.values()) {
bundleBuffer = Buffer.concat([
bundleBuffer,
this.elementToLengthPrefixedBuffer({ namedQuery }),
]);
}
for (const bundledDocument of this.documents.values()) {
const documentMetadata = bundledDocument.metadata;
bundleBuffer = Buffer.concat([
bundleBuffer,
this.elementToLengthPrefixedBuffer({ documentMetadata }),
]);
// Write to the bundle if document exists.
const document = bundledDocument.document;
if (document) {
bundleBuffer = Buffer.concat([
bundleBuffer,
this.elementToLengthPrefixedBuffer({ document }),
]);
}
}
const metadata = {
id: this.bundleId,
createTime: this.latestReadTime.toProto().timestampValue,
version: BUNDLE_VERSION,
totalDocuments: this.documents.size,
totalBytes: bundleBuffer.length,
};
// Prepends the metadata element to the bundleBuffer: `bundleBuffer` is the second argument to `Buffer.concat`.
bundleBuffer = Buffer.concat([
this.elementToLengthPrefixedBuffer({ metadata }),
bundleBuffer,
]);
return bundleBuffer;
}
}
exports.BundleBuilder = BundleBuilder;
/**
* Convenient class to hold both the metadata and the actual content of a document to be bundled.
* @private
* @internal
*/
class BundledDocument {
constructor(metadata, document) {
this.metadata = metadata;
this.document = document;
}
}
/**
* Validates that 'value' is DocumentSnapshot.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param value The input to validate.
*/
function validateDocumentSnapshot(arg, value) {
if (!(value instanceof document_1.DocumentSnapshot)) {
throw new Error((0, validate_1.invalidArgumentMessage)(arg, 'DocumentSnapshot'));
}
}
/**
* Validates that 'value' is QuerySnapshot.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param value The input to validate.
*/
function validateQuerySnapshot(arg, value) {
if (!(value instanceof query_snapshot_1.QuerySnapshot)) {
throw new Error((0, validate_1.invalidArgumentMessage)(arg, 'QuerySnapshot'));
}
}
//# sourceMappingURL=bundle.js.map

View File

@@ -0,0 +1,90 @@
import * as firestore from '@google-cloud/firestore';
import { QueryPartition } from './query-partition';
import { Query } from './reference/query';
import { Firestore } from './index';
/**
* A `CollectionGroup` refers to all documents that are contained in a
* collection or subcollection with a specific collection ID.
*
* @class CollectionGroup
*/
export declare class CollectionGroup<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> extends Query<AppModelType, DbModelType> implements firestore.CollectionGroup<AppModelType, DbModelType> {
/** @private */
constructor(firestore: Firestore, collectionId: string, converter: firestore.FirestoreDataConverter<AppModelType, DbModelType> | undefined);
/**
* Partitions a query by returning partition cursors that can be used to run
* the query in parallel. The returned cursors are split points that can be
* used as starting and end points for individual query invocations.
*
* @example
* ```
* const query = firestore.collectionGroup('collectionId');
* for await (const partition of query.getPartitions(42)) {
* const partitionedQuery = partition.toQuery();
* const querySnapshot = await partitionedQuery.get();
* console.log(`Partition contained ${querySnapshot.length} documents`);
* }
*
* ```
* @param {number} desiredPartitionCount The desired maximum number of
* partition points. The number must be strictly positive. The actual number
* of partitions returned may be fewer.
* @return {AsyncIterable<QueryPartition>} An AsyncIterable of
* `QueryPartition`s.
*/
getPartitions(desiredPartitionCount: number): AsyncIterable<QueryPartition<AppModelType, DbModelType>>;
/**
* Applies a custom data converter to this `CollectionGroup`, allowing you
* to use your own custom model objects with Firestore. When you call get()
* on the returned `CollectionGroup`, the provided converter will convert
* between Firestore data of type `NewDbModelType` and your custom type
* `NewAppModelType`.
*
* Using the converter allows you to specify generic type arguments when
* storing and retrieving objects from Firestore.
*
* Passing in `null` as the converter parameter removes the current
* converter.
*
* @example
* ```
* class Post {
* constructor(readonly title: string, readonly author: string) {}
*
* toString(): string {
* return this.title + ', by ' + this.author;
* }
* }
*
* const postConverter = {
* toFirestore(post: Post): FirebaseFirestore.DocumentData {
* return {title: post.title, author: post.author};
* },
* fromFirestore(
* snapshot: FirebaseFirestore.QueryDocumentSnapshot
* ): Post {
* const data = snapshot.data();
* return new Post(data.title, data.author);
* }
* };
*
* const querySnapshot = await Firestore()
* .collectionGroup('posts')
* .withConverter(postConverter)
* .get();
* for (const doc of querySnapshot.docs) {
* const post = doc.data();
* post.title; // string
* post.toString(); // Should be defined
* post.someNonExistentProperty; // TS error
* }
*
* ```
* @param {FirestoreDataConverter | null} converter Converts objects to and
* from Firestore. Passing in `null` removes the current converter.
* @return {CollectionGroup} A `CollectionGroup` that uses the provided
* converter.
*/
withConverter(converter: null): CollectionGroup;
withConverter<NewAppModelType, NewDbModelType extends firestore.DocumentData = firestore.DocumentData>(converter: firestore.FirestoreDataConverter<NewAppModelType, NewDbModelType>): CollectionGroup<NewAppModelType, NewDbModelType>;
}

View File

@@ -0,0 +1,99 @@
"use strict";
/*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CollectionGroup = void 0;
const query_partition_1 = require("./query-partition");
const util_1 = require("./util");
const logger_1 = require("./logger");
const query_1 = require("./reference/query");
const query_options_1 = require("./reference/query-options");
const path_1 = require("./path");
const validate_1 = require("./validate");
const types_1 = require("./types");
const order_1 = require("./order");
const trace_util_1 = require("./telemetry/trace-util");
/**
* A `CollectionGroup` refers to all documents that are contained in a
* collection or subcollection with a specific collection ID.
*
* @class CollectionGroup
*/
class CollectionGroup extends query_1.Query {
/** @private */
constructor(firestore, collectionId, converter) {
super(firestore, query_options_1.QueryOptions.forCollectionGroupQuery(collectionId, converter));
}
/**
* Partitions a query by returning partition cursors that can be used to run
* the query in parallel. The returned cursors are split points that can be
* used as starting and end points for individual query invocations.
*
* @example
* ```
* const query = firestore.collectionGroup('collectionId');
* for await (const partition of query.getPartitions(42)) {
* const partitionedQuery = partition.toQuery();
* const querySnapshot = await partitionedQuery.get();
* console.log(`Partition contained ${querySnapshot.length} documents`);
* }
*
* ```
* @param {number} desiredPartitionCount The desired maximum number of
* partition points. The number must be strictly positive. The actual number
* of partitions returned may be fewer.
* @return {AsyncIterable<QueryPartition>} An AsyncIterable of
* `QueryPartition`s.
*/
async *getPartitions(desiredPartitionCount) {
const partitions = [];
await this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_PARTITION_QUERY, async () => {
var _a;
(0, validate_1.validateInteger)('desiredPartitionCount', desiredPartitionCount, {
minValue: 1,
});
const tag = (0, util_1.requestTag)();
await this.firestore.initializeIfNeeded(tag);
if (desiredPartitionCount > 1) {
// Partition queries require explicit ordering by __name__.
const queryWithDefaultOrder = this.orderBy(path_1.FieldPath.documentId());
const request = queryWithDefaultOrder.toProto();
// Since we are always returning an extra partition (with an empty endBefore
// cursor), we reduce the desired partition count by one.
request.partitionCount = desiredPartitionCount - 1;
const stream = await this.firestore.requestStream('partitionQueryStream',
/* bidirectional= */ false, request, tag);
stream.resume();
for await (const currentCursor of stream) {
partitions.push((_a = currentCursor.values) !== null && _a !== void 0 ? _a : []);
}
}
(0, logger_1.logger)('Firestore.getPartitions', tag, 'Received %d partitions', partitions.length);
// Sort the partitions as they may not be ordered if responses are paged.
partitions.sort((l, r) => (0, order_1.compareArrays)(l, r));
});
for (let i = 0; i < partitions.length; ++i) {
yield new query_partition_1.QueryPartition(this._firestore, this._queryOptions.collectionId, this._queryOptions.converter, i > 0 ? partitions[i - 1] : undefined, partitions[i]);
}
// Return the extra partition with the empty cursor.
yield new query_partition_1.QueryPartition(this._firestore, this._queryOptions.collectionId, this._queryOptions.converter, partitions.pop(), undefined);
}
withConverter(converter) {
return new CollectionGroup(this.firestore, this._queryOptions.collectionId, converter !== null && converter !== void 0 ? converter : (0, types_1.defaultConverter)());
}
}
exports.CollectionGroup = CollectionGroup;
//# sourceMappingURL=collection-group.js.map

View File

@@ -0,0 +1,81 @@
/*!
* Copyright 2019 Google Inc. All Rights Reserved.
*
* 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.
*/
import { google } from '../protos/firestore_v1_proto_api';
import { ApiMapValue, ProtobufJsValue } from './types';
import api = google.firestore.v1;
/*!
* @module firestore/convert
* @private
* @internal
*
* This module contains utility functions to convert
* `firestore.v1.Documents` from Proto3 JSON to their equivalent
* representation in Protobuf JS. Protobuf JS is the only encoding supported by
* this client, and dependencies that use Proto3 JSON (such as the Google Cloud
* Functions SDK) are supported through this conversion and its usage in
* {@see Firestore#snapshot_}.
*/
/**
* Converts an ISO 8601 or google.protobuf.Timestamp proto into Protobuf JS.
*
* @private
* @internal
* @param timestampValue The value to convert.
* @param argumentName The argument name to use in the error message if the
* conversion fails. If omitted, 'timestampValue' is used.
* @return The value as expected by Protobuf JS or undefined if no input was
* provided.
*/
export declare function timestampFromJson(timestampValue?: string | google.protobuf.ITimestamp, argumentName?: string): google.protobuf.ITimestamp | undefined;
/**
* Detects 'valueType' from a Proto3 JSON `firestore.v1.Value` proto.
*
* @private
* @internal
* @param proto The `firestore.v1.Value` proto.
* @return The string value for 'valueType'.
*/
export declare function detectValueType(proto: ProtobufJsValue): string;
/**
* Detects the value kind from a Proto3 JSON `google.protobuf.Value` proto.
*
* @private
* @internal
* @param proto The `firestore.v1.Value` proto.
* @return The string value for 'valueType'.
*/
export declare function detectGoogleProtobufValueType(proto: google.protobuf.IValue): string;
/**
* Converts a `firestore.v1.Value` in Proto3 JSON encoding into the
* Protobuf JS format expected by this client.
*
* @private
* @internal
* @param fieldValue The `firestore.v1.Value` in Proto3 JSON format.
* @return The `firestore.v1.Value` in Protobuf JS format.
*/
export declare function valueFromJson(fieldValue: api.IValue): api.IValue;
/**
* Converts a map of IValues in Proto3 JSON encoding into the Protobuf JS format
* expected by this client. This conversion creates a copy of the underlying
* fields.
*
* @private
* @internal
* @param document An object with IValues in Proto3 JSON format.
* @return The object in Protobuf JS format.
*/
export declare function fieldsFromJson(document: ApiMapValue): ApiMapValue;

View File

@@ -0,0 +1,267 @@
"use strict";
/*!
* Copyright 2019 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.timestampFromJson = timestampFromJson;
exports.detectValueType = detectValueType;
exports.detectGoogleProtobufValueType = detectGoogleProtobufValueType;
exports.valueFromJson = valueFromJson;
exports.fieldsFromJson = fieldsFromJson;
const validate_1 = require("./validate");
const map_type_1 = require("./map-type");
/*!
* @module firestore/convert
* @private
* @internal
*
* This module contains utility functions to convert
* `firestore.v1.Documents` from Proto3 JSON to their equivalent
* representation in Protobuf JS. Protobuf JS is the only encoding supported by
* this client, and dependencies that use Proto3 JSON (such as the Google Cloud
* Functions SDK) are supported through this conversion and its usage in
* {@see Firestore#snapshot_}.
*/
/**
* Converts an ISO 8601 or google.protobuf.Timestamp proto into Protobuf JS.
*
* @private
* @internal
* @param timestampValue The value to convert.
* @param argumentName The argument name to use in the error message if the
* conversion fails. If omitted, 'timestampValue' is used.
* @return The value as expected by Protobuf JS or undefined if no input was
* provided.
*/
function timestampFromJson(timestampValue, argumentName) {
let timestampProto = {};
if (typeof timestampValue === 'string') {
const date = new Date(timestampValue);
const seconds = Math.floor(date.getTime() / 1000);
let nanos = 0;
if (timestampValue.length > 20) {
const nanoString = timestampValue.substring(20, timestampValue.length - 1);
const trailingZeroes = 9 - nanoString.length;
nanos = Number(nanoString) * Math.pow(10, trailingZeroes);
}
if (isNaN(seconds) || isNaN(nanos)) {
argumentName = argumentName || 'timestampValue';
throw new Error(`Specify a valid ISO 8601 timestamp for "${argumentName}".`);
}
timestampProto = {
seconds: seconds || undefined,
nanos: nanos || undefined,
};
}
else if (timestampValue !== undefined) {
(0, validate_1.validateObject)('timestampValue', timestampValue);
timestampProto = {
seconds: timestampValue.seconds || undefined,
nanos: timestampValue.nanos || undefined,
};
}
return timestampProto;
}
/**
* Converts a Proto3 JSON 'bytesValue' field into Protobuf JS.
*
* @private
* @internal
* @param bytesValue The value to convert.
* @return The value as expected by Protobuf JS.
*/
function bytesFromJson(bytesValue) {
if (typeof bytesValue === 'string') {
return Buffer.from(bytesValue, 'base64');
}
else {
return bytesValue;
}
}
/**
* Detects 'valueType' from a Proto3 JSON `firestore.v1.Value` proto.
*
* @private
* @internal
* @param proto The `firestore.v1.Value` proto.
* @return The string value for 'valueType'.
*/
function detectValueType(proto) {
var _a;
let valueType;
if (proto.valueType) {
valueType = proto.valueType;
}
else {
const detectedValues = [];
if (proto.stringValue !== undefined) {
detectedValues.push('stringValue');
}
if (proto.booleanValue !== undefined) {
detectedValues.push('booleanValue');
}
if (proto.integerValue !== undefined) {
detectedValues.push('integerValue');
}
if (proto.doubleValue !== undefined) {
detectedValues.push('doubleValue');
}
if (proto.timestampValue !== undefined) {
detectedValues.push('timestampValue');
}
if (proto.referenceValue !== undefined) {
detectedValues.push('referenceValue');
}
if (proto.arrayValue !== undefined) {
detectedValues.push('arrayValue');
}
if (proto.nullValue !== undefined) {
detectedValues.push('nullValue');
}
if (proto.mapValue !== undefined) {
detectedValues.push('mapValue');
}
if (proto.geoPointValue !== undefined) {
detectedValues.push('geoPointValue');
}
if (proto.bytesValue !== undefined) {
detectedValues.push('bytesValue');
}
if (detectedValues.length !== 1) {
throw new Error(`Unable to infer type value from '${JSON.stringify(proto)}'.`);
}
valueType = detectedValues[0];
}
// Special handling of mapValues used to represent other data types
if (valueType === 'mapValue') {
const fields = (_a = proto.mapValue) === null || _a === void 0 ? void 0 : _a.fields;
if (fields) {
const props = Object.keys(fields);
if (props.indexOf(map_type_1.RESERVED_MAP_KEY) !== -1 &&
detectValueType(fields[map_type_1.RESERVED_MAP_KEY]) === 'stringValue' &&
fields[map_type_1.RESERVED_MAP_KEY].stringValue === map_type_1.RESERVED_MAP_KEY_VECTOR_VALUE) {
valueType = 'vectorValue';
}
}
}
return valueType;
}
/**
* Detects the value kind from a Proto3 JSON `google.protobuf.Value` proto.
*
* @private
* @internal
* @param proto The `firestore.v1.Value` proto.
* @return The string value for 'valueType'.
*/
function detectGoogleProtobufValueType(proto) {
const detectedValues = [];
if (proto.nullValue !== undefined) {
detectedValues.push('nullValue');
}
if (proto.numberValue !== undefined) {
detectedValues.push('numberValue');
}
if (proto.stringValue !== undefined) {
detectedValues.push('stringValue');
}
if (proto.boolValue !== undefined) {
detectedValues.push('boolValue');
}
if (proto.structValue !== undefined) {
detectedValues.push('structValue');
}
if (proto.listValue !== undefined) {
detectedValues.push('listValue');
}
if (detectedValues.length !== 1) {
throw new Error(`Unable to infer type value from '${JSON.stringify(proto)}'.`);
}
return detectedValues[0];
}
/**
* Converts a `firestore.v1.Value` in Proto3 JSON encoding into the
* Protobuf JS format expected by this client.
*
* @private
* @internal
* @param fieldValue The `firestore.v1.Value` in Proto3 JSON format.
* @return The `firestore.v1.Value` in Protobuf JS format.
*/
function valueFromJson(fieldValue) {
const valueType = detectValueType(fieldValue);
switch (valueType) {
case 'timestampValue':
return {
timestampValue: timestampFromJson(fieldValue.timestampValue),
};
case 'bytesValue':
return {
bytesValue: bytesFromJson(fieldValue.bytesValue),
};
case 'doubleValue':
return {
doubleValue: Number(fieldValue.doubleValue),
};
case 'arrayValue': {
const arrayValue = [];
if (Array.isArray(fieldValue.arrayValue.values)) {
for (const value of fieldValue.arrayValue.values) {
arrayValue.push(valueFromJson(value));
}
}
return {
arrayValue: {
values: arrayValue,
},
};
}
case 'mapValue':
case 'vectorValue': {
const mapValue = {};
const fields = fieldValue.mapValue.fields;
if (fields) {
for (const prop of Object.keys(fields)) {
mapValue[prop] = valueFromJson(fieldValue.mapValue.fields[prop]);
}
}
return {
mapValue: {
fields: mapValue,
},
};
}
default:
return fieldValue;
}
}
/**
* Converts a map of IValues in Proto3 JSON encoding into the Protobuf JS format
* expected by this client. This conversion creates a copy of the underlying
* fields.
*
* @private
* @internal
* @param document An object with IValues in Proto3 JSON format.
* @return The object in Protobuf JS format.
*/
function fieldsFromJson(document) {
const result = {};
for (const prop of Object.keys(document)) {
result[prop] = valueFromJson(document[prop]);
}
return result;
}
//# sourceMappingURL=convert.js.map

View File

@@ -0,0 +1,155 @@
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import { QueryDocumentSnapshot } from './document';
export type DocumentChangeType = 'added' | 'removed' | 'modified';
/**
* A DocumentChange represents a change to the documents matching a query.
* It contains the document affected and the type of change that occurred.
*
* @class DocumentChange
*/
export declare class DocumentChange<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> implements firestore.DocumentChange<AppModelType, DbModelType> {
private readonly _type;
private readonly _document;
private readonly _oldIndex;
private readonly _newIndex;
/**
* @private
*
* @param {string} type 'added' | 'removed' | 'modified'.
* @param {QueryDocumentSnapshot} document The document.
* @param {number} oldIndex The index in the documents array prior to this
* change.
* @param {number} newIndex The index in the documents array after this
* change.
*/
constructor(type: DocumentChangeType, document: QueryDocumentSnapshot<AppModelType, DbModelType>, oldIndex: number, newIndex: number);
/**
* The type of change ('added', 'modified', or 'removed').
*
* @type {string}
* @name DocumentChange#type
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
* let docsArray = [];
*
* let unsubscribe = query.onSnapshot(querySnapshot => {
* for (let change of querySnapshot.docChanges) {
* console.log(`Type of change is ${change.type}`);
* }
* });
*
* // Remove this listener.
* unsubscribe();
* ```
*/
get type(): DocumentChangeType;
/**
* The document affected by this change.
*
* @type {QueryDocumentSnapshot}
* @name DocumentChange#doc
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* let unsubscribe = query.onSnapshot(querySnapshot => {
* for (let change of querySnapshot.docChanges) {
* console.log(change.doc.data());
* }
* });
*
* // Remove this listener.
* unsubscribe();
* ```
*/
get doc(): QueryDocumentSnapshot<AppModelType, DbModelType>;
/**
* The index of the changed document in the result set immediately prior to
* this DocumentChange (i.e. supposing that all prior DocumentChange objects
* have been applied). Is -1 for 'added' events.
*
* @type {number}
* @name DocumentChange#oldIndex
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
* let docsArray = [];
*
* let unsubscribe = query.onSnapshot(querySnapshot => {
* for (let change of querySnapshot.docChanges) {
* if (change.oldIndex !== -1) {
* docsArray.splice(change.oldIndex, 1);
* }
* if (change.newIndex !== -1) {
* docsArray.splice(change.newIndex, 0, change.doc);
* }
* }
* });
*
* // Remove this listener.
* unsubscribe();
* ```
*/
get oldIndex(): number;
/**
* The index of the changed document in the result set immediately after
* this DocumentChange (i.e. supposing that all prior DocumentChange
* objects and the current DocumentChange object have been applied).
* Is -1 for 'removed' events.
*
* @type {number}
* @name DocumentChange#newIndex
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
* let docsArray = [];
*
* let unsubscribe = query.onSnapshot(querySnapshot => {
* for (let change of querySnapshot.docChanges) {
* if (change.oldIndex !== -1) {
* docsArray.splice(change.oldIndex, 1);
* }
* if (change.newIndex !== -1) {
* docsArray.splice(change.newIndex, 0, change.doc);
* }
* }
* });
*
* // Remove this listener.
* unsubscribe();
* ```
*/
get newIndex(): number;
/**
* Returns true if the data in this `DocumentChange` is equal to the provided
* value.
*
* @param {*} other The value to compare against.
* @return true if this `DocumentChange` is equal to the provided value.
*/
isEqual(other: firestore.DocumentChange<AppModelType, DbModelType>): boolean;
}

View File

@@ -0,0 +1,175 @@
"use strict";
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DocumentChange = void 0;
/**
* A DocumentChange represents a change to the documents matching a query.
* It contains the document affected and the type of change that occurred.
*
* @class DocumentChange
*/
class DocumentChange {
/**
* @private
*
* @param {string} type 'added' | 'removed' | 'modified'.
* @param {QueryDocumentSnapshot} document The document.
* @param {number} oldIndex The index in the documents array prior to this
* change.
* @param {number} newIndex The index in the documents array after this
* change.
*/
constructor(type, document, oldIndex, newIndex) {
this._type = type;
this._document = document;
this._oldIndex = oldIndex;
this._newIndex = newIndex;
}
/**
* The type of change ('added', 'modified', or 'removed').
*
* @type {string}
* @name DocumentChange#type
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
* let docsArray = [];
*
* let unsubscribe = query.onSnapshot(querySnapshot => {
* for (let change of querySnapshot.docChanges) {
* console.log(`Type of change is ${change.type}`);
* }
* });
*
* // Remove this listener.
* unsubscribe();
* ```
*/
get type() {
return this._type;
}
/**
* The document affected by this change.
*
* @type {QueryDocumentSnapshot}
* @name DocumentChange#doc
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* let unsubscribe = query.onSnapshot(querySnapshot => {
* for (let change of querySnapshot.docChanges) {
* console.log(change.doc.data());
* }
* });
*
* // Remove this listener.
* unsubscribe();
* ```
*/
get doc() {
return this._document;
}
/**
* The index of the changed document in the result set immediately prior to
* this DocumentChange (i.e. supposing that all prior DocumentChange objects
* have been applied). Is -1 for 'added' events.
*
* @type {number}
* @name DocumentChange#oldIndex
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
* let docsArray = [];
*
* let unsubscribe = query.onSnapshot(querySnapshot => {
* for (let change of querySnapshot.docChanges) {
* if (change.oldIndex !== -1) {
* docsArray.splice(change.oldIndex, 1);
* }
* if (change.newIndex !== -1) {
* docsArray.splice(change.newIndex, 0, change.doc);
* }
* }
* });
*
* // Remove this listener.
* unsubscribe();
* ```
*/
get oldIndex() {
return this._oldIndex;
}
/**
* The index of the changed document in the result set immediately after
* this DocumentChange (i.e. supposing that all prior DocumentChange
* objects and the current DocumentChange object have been applied).
* Is -1 for 'removed' events.
*
* @type {number}
* @name DocumentChange#newIndex
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
* let docsArray = [];
*
* let unsubscribe = query.onSnapshot(querySnapshot => {
* for (let change of querySnapshot.docChanges) {
* if (change.oldIndex !== -1) {
* docsArray.splice(change.oldIndex, 1);
* }
* if (change.newIndex !== -1) {
* docsArray.splice(change.newIndex, 0, change.doc);
* }
* }
* });
*
* // Remove this listener.
* unsubscribe();
* ```
*/
get newIndex() {
return this._newIndex;
}
/**
* Returns true if the data in this `DocumentChange` is equal to the provided
* value.
*
* @param {*} other The value to compare against.
* @return true if this `DocumentChange` is equal to the provided value.
*/
isEqual(other) {
if (this === other) {
return true;
}
return (other instanceof DocumentChange &&
this._type === other._type &&
this._oldIndex === other._oldIndex &&
this._newIndex === other._newIndex &&
this._document.isEqual(other._document));
}
}
exports.DocumentChange = DocumentChange;
//# sourceMappingURL=document-change.js.map

View File

@@ -0,0 +1,74 @@
/*!
* Copyright 2021 Google LLC. All Rights Reserved.
*
* 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.
*/
import { DocumentSnapshot } from './document';
import { DocumentReference } from './reference/document-reference';
import { FieldPath } from './path';
import { google } from '../protos/firestore_v1_proto_api';
import { Firestore } from './index';
import { Timestamp } from './timestamp';
import { DocumentData } from '@google-cloud/firestore';
import api = google.firestore.v1;
interface BatchGetResponse<AppModelType, DbModelType extends DocumentData> {
result: Array<DocumentSnapshot<AppModelType, DbModelType>>;
/**
* The transaction that was started as part of this request. Will only be if
* `DocumentReader.transactionIdOrNewTransaction` was `api.ITransactionOptions`.
*/
transaction?: Uint8Array;
}
/**
* A wrapper around BatchGetDocumentsRequest that retries request upon stream
* failure and returns ordered results.
*
* @private
* @internal
*/
export declare class DocumentReader<AppModelType, DbModelType extends DocumentData> {
private readonly firestore;
private readonly allDocuments;
private readonly fieldMask?;
private readonly transactionOrReadTime?;
private readonly outstandingDocuments;
private readonly retrievedDocuments;
private retrievedTransactionId?;
/**
* Creates a new DocumentReader that fetches the provided documents (via
* `get()`).
*
* @param firestore The Firestore instance to use.
* @param allDocuments The documents to get.
* @param fieldMask An optional field mask to apply to this read
* @param transactionOrReadTime An optional transaction ID to use for this
* read or options for beginning a new transaction with this read
*/
constructor(firestore: Firestore, allDocuments: ReadonlyArray<DocumentReference<AppModelType, DbModelType>>, fieldMask?: FieldPath[] | undefined, transactionOrReadTime?: (Uint8Array | api.ITransactionOptions | Timestamp) | undefined);
/**
* Invokes the BatchGetDocuments RPC and returns the results as an array of
* documents.
*
* @param requestTag A unique client-assigned identifier for this request.
*/
get(requestTag: string): Promise<Array<DocumentSnapshot<AppModelType, DbModelType>>>;
/**
* Invokes the BatchGetDocuments RPC and returns the results with transaction
* metadata.
*
* @param requestTag A unique client-assigned identifier for this request.
*/
_get(requestTag: string): Promise<BatchGetResponse<AppModelType, DbModelType>>;
private fetchDocuments;
}
export {};

View File

@@ -0,0 +1,167 @@
"use strict";
/*!
* Copyright 2021 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DocumentReader = void 0;
const document_1 = require("./document");
const util_1 = require("./util");
const logger_1 = require("./logger");
const timestamp_1 = require("./timestamp");
/**
* A wrapper around BatchGetDocumentsRequest that retries request upon stream
* failure and returns ordered results.
*
* @private
* @internal
*/
class DocumentReader {
/**
* Creates a new DocumentReader that fetches the provided documents (via
* `get()`).
*
* @param firestore The Firestore instance to use.
* @param allDocuments The documents to get.
* @param fieldMask An optional field mask to apply to this read
* @param transactionOrReadTime An optional transaction ID to use for this
* read or options for beginning a new transaction with this read
*/
constructor(firestore, allDocuments, fieldMask, transactionOrReadTime) {
this.firestore = firestore;
this.allDocuments = allDocuments;
this.fieldMask = fieldMask;
this.transactionOrReadTime = transactionOrReadTime;
this.outstandingDocuments = new Set();
this.retrievedDocuments = new Map();
for (const docRef of this.allDocuments) {
this.outstandingDocuments.add(docRef.formattedName);
}
}
/**
* Invokes the BatchGetDocuments RPC and returns the results as an array of
* documents.
*
* @param requestTag A unique client-assigned identifier for this request.
*/
async get(requestTag) {
const { result } = await this._get(requestTag);
return result;
}
/**
* Invokes the BatchGetDocuments RPC and returns the results with transaction
* metadata.
*
* @param requestTag A unique client-assigned identifier for this request.
*/
async _get(requestTag) {
await this.fetchDocuments(requestTag);
// BatchGetDocuments doesn't preserve document order. We use the request
// order to sort the resulting documents.
const orderedDocuments = [];
for (const docRef of this.allDocuments) {
const document = this.retrievedDocuments.get(docRef.formattedName);
if (document !== undefined) {
// Recreate the DocumentSnapshot with the DocumentReference
// containing the original converter.
const finalDoc = new document_1.DocumentSnapshotBuilder(docRef);
finalDoc.fieldsProto = document._fieldsProto;
finalDoc.readTime = document.readTime;
finalDoc.createTime = document.createTime;
finalDoc.updateTime = document.updateTime;
orderedDocuments.push(finalDoc.build());
}
else {
throw new Error(`Did not receive document for "${docRef.path}".`);
}
}
return {
result: orderedDocuments,
transaction: this.retrievedTransactionId,
};
}
async fetchDocuments(requestTag) {
var _a;
if (!this.outstandingDocuments.size) {
return;
}
const request = {
database: this.firestore.formattedName,
documents: Array.from(this.outstandingDocuments),
};
if (this.transactionOrReadTime instanceof Uint8Array) {
request.transaction = this.transactionOrReadTime;
}
else if (this.transactionOrReadTime instanceof timestamp_1.Timestamp) {
request.readTime = this.transactionOrReadTime.toProto().timestampValue;
}
else if (this.transactionOrReadTime) {
request.newTransaction = this.transactionOrReadTime;
}
if (this.fieldMask) {
const fieldPaths = this.fieldMask.map(fieldPath => fieldPath.formattedName);
request.mask = { fieldPaths };
}
let resultCount = 0;
try {
const stream = await this.firestore.requestStream('batchGetDocuments',
/* bidirectional= */ false, request, requestTag);
stream.resume();
for await (const response of stream) {
// Proto comes with zero-length buffer by default
if ((_a = response.transaction) === null || _a === void 0 ? void 0 : _a.length) {
this.retrievedTransactionId = response.transaction;
}
let snapshot;
if (response.found) {
(0, logger_1.logger)('DocumentReader.fetchDocuments', requestTag, 'Received document: %s', response.found.name);
snapshot = this.firestore.snapshot_(response.found, response.readTime);
}
else if (response.missing) {
(0, logger_1.logger)('DocumentReader.fetchDocuments', requestTag, 'Document missing: %s', response.missing);
snapshot = this.firestore.snapshot_(response.missing, response.readTime);
}
if (snapshot) {
const path = snapshot.ref.formattedName;
this.outstandingDocuments.delete(path);
this.retrievedDocuments.set(path, snapshot);
++resultCount;
}
}
}
catch (error) {
const shouldRetry =
// Transactional reads are retried via the transaction runner.
!request.transaction &&
!request.newTransaction &&
// Only retry if we made progress.
resultCount > 0 &&
// Don't retry permanent errors.
error.code !== undefined &&
!(0, util_1.isPermanentRpcError)(error, 'batchGetDocuments');
(0, logger_1.logger)('DocumentReader.fetchDocuments', requestTag, 'BatchGetDocuments failed with error: %s. Retrying: %s', error, shouldRetry);
if (shouldRetry) {
return this.fetchDocuments(requestTag);
}
else {
throw error;
}
}
finally {
(0, logger_1.logger)('DocumentReader.fetchDocuments', requestTag, 'Received %d results', resultCount);
}
}
}
exports.DocumentReader = DocumentReader;
//# sourceMappingURL=document-reader.js.map

View File

@@ -0,0 +1,594 @@
/*!
* Copyright 2019 Google Inc. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import { google } from '../protos/firestore_v1_proto_api';
import { FieldTransform } from './field-value';
import { FieldPath } from './path';
import { DocumentReference } from './reference/document-reference';
import { Serializer } from './serializer';
import { Timestamp } from './timestamp';
import { ApiMapValue, UpdateMap } from './types';
import api = google.firestore.v1;
/**
* Returns a builder for DocumentSnapshot and QueryDocumentSnapshot instances.
* Invoke `.build()' to assemble the final snapshot.
*
* @private
* @internal
*/
export declare class DocumentSnapshotBuilder<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> {
readonly ref: DocumentReference<AppModelType, DbModelType>;
/** The fields of the Firestore `Document` Protobuf backing this document. */
fieldsProto?: ApiMapValue;
/** The time when this document was read. */
readTime?: Timestamp;
/** The time when this document was created. */
createTime?: Timestamp;
/** The time when this document was last updated. */
updateTime?: Timestamp;
constructor(ref: DocumentReference<AppModelType, DbModelType>);
/**
* Builds the DocumentSnapshot.
*
* @private
* @internal
* @returns Returns either a QueryDocumentSnapshot (if `fieldsProto` was
* provided) or a DocumentSnapshot.
*/
build(): QueryDocumentSnapshot<AppModelType, DbModelType> | DocumentSnapshot<AppModelType, DbModelType>;
}
/**
* A DocumentSnapshot is an immutable representation for a document in a
* Firestore database. The data can be extracted with
* [data()]{@link DocumentSnapshot#data} or
* [get(fieldPath)]{@link DocumentSnapshot#get} to get a
* specific field.
*
* <p>For a DocumentSnapshot that points to a non-existing document, any data
* access will return 'undefined'. You can use the
* [exists]{@link DocumentSnapshot#exists} property to explicitly verify a
* document's existence.
*
* @class DocumentSnapshot
*/
export declare class DocumentSnapshot<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> implements firestore.DocumentSnapshot<AppModelType, DbModelType> {
/**
* @internal
* @private
**/
readonly _fieldsProto?: ApiMapValue | undefined;
private _ref;
private _serializer;
private _readTime;
private _createTime;
private _updateTime;
/**
* @private
* @internal
*
* @param ref The reference to the document.
* @param _fieldsProto The fields of the Firestore `Document` Protobuf backing
* this document (or undefined if the document does not exist).
* @param readTime The time when this snapshot was read (or undefined if
* the document exists only locally).
* @param createTime The time when the document was created (or undefined if
* the document does not exist).
* @param updateTime The time when the document was last updated (or undefined
* if the document does not exist).
*/
constructor(ref: DocumentReference<AppModelType, DbModelType>,
/**
* @internal
* @private
**/
_fieldsProto?: ApiMapValue | undefined, readTime?: Timestamp, createTime?: Timestamp, updateTime?: Timestamp);
/**
* Creates a DocumentSnapshot from an object.
*
* @private
* @internal
* @param ref The reference to the document.
* @param obj The object to store in the DocumentSnapshot.
* @return The created DocumentSnapshot.
*/
static fromObject<AppModelType, DbModelType extends firestore.DocumentData>(ref: DocumentReference<AppModelType, DbModelType>, obj: firestore.DocumentData): DocumentSnapshot<AppModelType, DbModelType>;
/**
* Creates a DocumentSnapshot from an UpdateMap.
*
* This methods expands the top-level field paths in a JavaScript map and
* turns { foo.bar : foobar } into { foo { bar : foobar }}
*
* @private
* @internal
* @param ref The reference to the document.
* @param data The field/value map to expand.
* @return The created DocumentSnapshot.
*/
static fromUpdateMap<AppModelType, DbModelType extends firestore.DocumentData>(ref: firestore.DocumentReference<AppModelType, DbModelType>, data: UpdateMap): DocumentSnapshot<AppModelType, DbModelType>;
/**
* True if the document exists.
*
* @type {boolean}
* @name DocumentSnapshot#exists
* @readonly
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then((documentSnapshot) => {
* if (documentSnapshot.exists) {
* console.log(`Data: ${JSON.stringify(documentSnapshot.data())}`);
* }
* });
* ```
*/
get exists(): boolean;
/**
* A [DocumentReference]{@link DocumentReference} for the document
* stored in this snapshot.
*
* @type {DocumentReference}
* @name DocumentSnapshot#ref
* @readonly
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then((documentSnapshot) => {
* if (documentSnapshot.exists) {
* console.log(`Found document at '${documentSnapshot.ref.path}'`);
* }
* });
* ```
*/
get ref(): DocumentReference<AppModelType, DbModelType>;
/**
* The ID of the document for which this DocumentSnapshot contains data.
*
* @type {string}
* @name DocumentSnapshot#id
* @readonly
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then((documentSnapshot) => {
* if (documentSnapshot.exists) {
* console.log(`Document found with name '${documentSnapshot.id}'`);
* }
* });
* ```
*/
get id(): string;
/**
* The time the document was created. Undefined for documents that don't
* exist.
*
* @type {Timestamp|undefined}
* @name DocumentSnapshot#createTime
* @readonly
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(documentSnapshot => {
* if (documentSnapshot.exists) {
* let createTime = documentSnapshot.createTime;
* console.log(`Document created at '${createTime.toDate()}'`);
* }
* });
* ```
*/
get createTime(): Timestamp | undefined;
/**
* The time the document was last updated (at the time the snapshot was
* generated). Undefined for documents that don't exist.
*
* @type {Timestamp|undefined}
* @name DocumentSnapshot#updateTime
* @readonly
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(documentSnapshot => {
* if (documentSnapshot.exists) {
* let updateTime = documentSnapshot.updateTime;
* console.log(`Document updated at '${updateTime.toDate()}'`);
* }
* });
* ```
*/
get updateTime(): Timestamp | undefined;
/**
* The time this snapshot was read.
*
* @type {Timestamp}
* @name DocumentSnapshot#readTime
* @readonly
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(documentSnapshot => {
* let readTime = documentSnapshot.readTime;
* console.log(`Document read at '${readTime.toDate()}'`);
* });
* ```
*/
get readTime(): Timestamp;
/**
* Retrieves all fields in the document as an object. Returns 'undefined' if
* the document doesn't exist.
*
* @returns {T|undefined} An object containing all fields in the document or
* 'undefined' if the document doesn't exist.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(documentSnapshot => {
* let data = documentSnapshot.data();
* console.log(`Retrieved data: ${JSON.stringify(data)}`);
* });
* ```
*/
data(): AppModelType | undefined;
/**
* Retrieves the field specified by `field`.
*
* @param {string|FieldPath} field The field path
* (e.g. 'foo' or 'foo.bar') to a specific field.
* @returns {*} The data at the specified field location or undefined if no
* such field exists.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.set({ a: { b: 'c' }}).then(() => {
* return documentRef.get();
* }).then(documentSnapshot => {
* let field = documentSnapshot.get('a.b');
* console.log(`Retrieved field value: ${field}`);
* });
* ```
*/
get(field: string | FieldPath): any;
/**
* Retrieves the field specified by 'fieldPath' in its Protobuf JS
* representation.
*
* @private
* @internal
* @param field The path (e.g. 'foo' or 'foo.bar') to a specific field.
* @returns The Protobuf-encoded data at the specified field location or
* undefined if no such field exists.
*/
protoField(field: string | FieldPath): api.IValue | undefined;
/**
* Convert a document snapshot to the Firestore 'Write' proto.
*
* @private
* @internal
*/
toWriteProto(): api.IWrite;
/**
* Convert a document snapshot to the Firestore 'Document' proto.
*
* @private
* @internal
*/
toDocumentProto(): api.IDocument;
/**
* Returns true if the document's data and path in this `DocumentSnapshot` is
* equal to the provided value.
*
* @param {*} other The value to compare against.
* @return {boolean} true if this `DocumentSnapshot` is equal to the provided
* value.
*/
isEqual(other: firestore.DocumentSnapshot<AppModelType, DbModelType>): boolean;
}
/**
* A QueryDocumentSnapshot contains data read from a document in your
* Firestore database as part of a query. The document is guaranteed to exist
* and its data can be extracted with [data()]{@link QueryDocumentSnapshot#data}
* or [get()]{@link DocumentSnapshot#get} to get a specific field.
*
* A QueryDocumentSnapshot offers the same API surface as a
* {@link DocumentSnapshot}. Since query results contain only existing
* documents, the [exists]{@link DocumentSnapshot#exists} property will
* always be true and [data()]{@link QueryDocumentSnapshot#data} will never
* return 'undefined'.
*
* @class QueryDocumentSnapshot
* @extends DocumentSnapshot
*/
export declare class QueryDocumentSnapshot<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> extends DocumentSnapshot<AppModelType, DbModelType> implements firestore.QueryDocumentSnapshot<AppModelType, DbModelType> {
/**
* The time the document was created.
*
* @type {Timestamp}
* @name QueryDocumentSnapshot#createTime
* @readonly
* @override
*
* @example
* ```
* let query = firestore.collection('col');
*
* query.get().forEach(snapshot => {
* console.log(`Document created at '${snapshot.createTime.toDate()}'`);
* });
* ```
*/
get createTime(): Timestamp;
/**
* The time the document was last updated (at the time the snapshot was
* generated).
*
* @type {Timestamp}
* @name QueryDocumentSnapshot#updateTime
* @readonly
* @override
*
* @example
* ```
* let query = firestore.collection('col');
*
* query.get().forEach(snapshot => {
* console.log(`Document updated at '${snapshot.updateTime.toDate()}'`);
* });
* ```
*/
get updateTime(): Timestamp;
/**
* Retrieves all fields in the document as an object.
*
* @override
*
* @returns {T} An object containing all fields in the document.
*
* @example
* ```
* let query = firestore.collection('col');
*
* query.get().forEach(documentSnapshot => {
* let data = documentSnapshot.data();
* console.log(`Retrieved data: ${JSON.stringify(data)}`);
* });
* ```
*/
data(): AppModelType;
}
/**
* A Firestore Document Mask contains the field paths affected by an update.
*
* @class
* @private
* @internal
*/
export declare class DocumentMask {
private _sortedPaths;
/**
* @private
* @internal
* @private
*
* @param fieldPaths The field paths in this mask.
*/
constructor(fieldPaths: FieldPath[]);
/**
* Creates a document mask with the field paths of a document.
*
* @private
* @internal
* @param data A map with fields to modify. Only the keys are used to extract
* the document mask.
*/
static fromUpdateMap(data: UpdateMap): DocumentMask;
/**
* Creates a document mask from an array of field paths.
*
* @private
* @internal
* @param fieldMask A list of field paths.
*/
static fromFieldMask(fieldMask: Array<string | firestore.FieldPath>): DocumentMask;
/**
* Creates a document mask with the field names of a document.
*
* @private
* @internal
* @param data An object with fields to modify. Only the keys are used to
* extract the document mask.
*/
static fromObject(data: firestore.DocumentData): DocumentMask;
/**
* Returns true if this document mask contains no fields.
*
* @private
* @internal
* @return {boolean} Whether this document mask is empty.
*/
get isEmpty(): boolean;
/**
* Removes the specified values from a sorted field path array.
*
* @private
* @internal
* @param input A sorted array of FieldPaths.
* @param values An array of FieldPaths to remove.
*/
private static removeFromSortedArray;
/**
* Removes the field path specified in 'fieldPaths' from this document mask.
*
* @private
* @internal
* @param fieldPaths An array of FieldPaths.
*/
removeFields(fieldPaths: FieldPath[]): void;
/**
* Returns whether this document mask contains 'fieldPath'.
*
* @private
* @internal
* @param fieldPath The field path to test.
* @return Whether this document mask contains 'fieldPath'.
*/
contains(fieldPath: FieldPath): boolean;
/**
* Removes all properties from 'data' that are not contained in this document
* mask.
*
* @private
* @internal
* @param data An object to filter.
* @return A shallow copy of the object filtered by this document mask.
*/
applyTo(data: firestore.DocumentData): firestore.DocumentData;
/**
* Converts a document mask to the Firestore 'DocumentMask' Proto.
*
* @private
* @internal
* @returns A Firestore 'DocumentMask' Proto.
*/
toProto(): api.IDocumentMask;
}
/**
* A Firestore Document Transform.
*
* A DocumentTransform contains pending server-side transforms and their
* corresponding field paths.
*
* @private
* @internal
* @class
*/
export declare class DocumentTransform<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> {
private readonly ref;
private readonly transforms;
/**
* @private
* @internal
* @private
*
* @param ref The DocumentReference for this transform.
* @param transforms A Map of FieldPaths to FieldTransforms.
*/
constructor(ref: DocumentReference<AppModelType, DbModelType>, transforms: Map<FieldPath, FieldTransform>);
/**
* Generates a DocumentTransform from a JavaScript object.
*
* @private
* @internal
* @param ref The `DocumentReference` to use for the DocumentTransform.
* @param obj The object to extract the transformations from.
* @returns The Document Transform.
*/
static fromObject<AppModelType, DbModelType extends firestore.DocumentData>(ref: firestore.DocumentReference<AppModelType, DbModelType>, obj: firestore.DocumentData): DocumentTransform<AppModelType, DbModelType>;
/**
* Generates a DocumentTransform from an Update Map.
*
* @private
* @internal
* @param ref The `DocumentReference` to use for the DocumentTransform.
* @param data The update data to extract the transformations from.
* @returns The Document Transform.
*/
static fromUpdateMap<AppModelType, DbModelType extends firestore.DocumentData>(ref: firestore.DocumentReference<AppModelType, DbModelType>, data: UpdateMap): DocumentTransform<AppModelType, DbModelType>;
/**
* Whether this DocumentTransform contains any actionable transformations.
*
* @private
* @internal
*/
get isEmpty(): boolean;
/**
* Returns the array of fields in this DocumentTransform.
*
* @private
* @internal
*/
get fields(): FieldPath[];
/**
* Validates the user provided field values in this document transform.
* @private
* @internal
*/
validate(): void;
/**
* Converts a document transform to the Firestore 'FieldTransform' Proto.
*
* @private
* @internal
* @param serializer The Firestore serializer
* @returns A list of Firestore 'FieldTransform' Protos
*/
toProto(serializer: Serializer): api.DocumentTransform.IFieldTransform[];
}
/**
* A Firestore Precondition encapsulates options for database writes.
*
* @private
* @internal
* @class
*/
export declare class Precondition {
private _exists?;
private _lastUpdateTime?;
/**
* @private
* @internal
* @private
*
* @param options.exists - Whether the referenced document should exist in
* Firestore,
* @param options.lastUpdateTime - The last update time of the referenced
* document in Firestore.
* @param options
*/
constructor(options?: {
exists?: boolean;
lastUpdateTime?: firestore.Timestamp;
});
/**
* Generates the Protobuf `Preconditon` object for this precondition.
*
* @private
* @internal
* @returns The `Preconditon` Protobuf object or 'null' if there are no
* preconditions.
*/
toProto(): api.IPrecondition | null;
/**
* Whether this DocumentTransform contains any enforcement.
*
* @private
* @internal
*/
get isEmpty(): boolean;
}

View File

@@ -0,0 +1,936 @@
"use strict";
/*!
* Copyright 2019 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Precondition = exports.DocumentTransform = exports.DocumentMask = exports.QueryDocumentSnapshot = exports.DocumentSnapshot = exports.DocumentSnapshotBuilder = void 0;
const deepEqual = require("fast-deep-equal");
const assert = require("assert");
const field_value_1 = require("./field-value");
const path_1 = require("./path");
const document_reference_1 = require("./reference/document-reference");
const types_1 = require("./types");
const util_1 = require("./util");
/**
* Returns a builder for DocumentSnapshot and QueryDocumentSnapshot instances.
* Invoke `.build()' to assemble the final snapshot.
*
* @private
* @internal
*/
class DocumentSnapshotBuilder {
// We include the DocumentReference in the constructor in order to allow the
// DocumentSnapshotBuilder to be typed with <AppModelType, DbModelType> when
// it is constructed.
constructor(ref) {
this.ref = ref;
}
/**
* Builds the DocumentSnapshot.
*
* @private
* @internal
* @returns Returns either a QueryDocumentSnapshot (if `fieldsProto` was
* provided) or a DocumentSnapshot.
*/
build() {
assert((this.fieldsProto !== undefined) === (this.createTime !== undefined), 'Create time should be set iff document exists.');
assert((this.fieldsProto !== undefined) === (this.updateTime !== undefined), 'Update time should be set iff document exists.');
return this.fieldsProto
? new QueryDocumentSnapshot(this.ref, this.fieldsProto, this.readTime, this.createTime, this.updateTime)
: new DocumentSnapshot(this.ref, undefined, this.readTime);
}
}
exports.DocumentSnapshotBuilder = DocumentSnapshotBuilder;
/**
* A DocumentSnapshot is an immutable representation for a document in a
* Firestore database. The data can be extracted with
* [data()]{@link DocumentSnapshot#data} or
* [get(fieldPath)]{@link DocumentSnapshot#get} to get a
* specific field.
*
* <p>For a DocumentSnapshot that points to a non-existing document, any data
* access will return 'undefined'. You can use the
* [exists]{@link DocumentSnapshot#exists} property to explicitly verify a
* document's existence.
*
* @class DocumentSnapshot
*/
class DocumentSnapshot {
/**
* @private
* @internal
*
* @param ref The reference to the document.
* @param _fieldsProto The fields of the Firestore `Document` Protobuf backing
* this document (or undefined if the document does not exist).
* @param readTime The time when this snapshot was read (or undefined if
* the document exists only locally).
* @param createTime The time when the document was created (or undefined if
* the document does not exist).
* @param updateTime The time when the document was last updated (or undefined
* if the document does not exist).
*/
constructor(ref,
/**
* @internal
* @private
**/
_fieldsProto, readTime, createTime, updateTime) {
this._fieldsProto = _fieldsProto;
this._ref = ref;
this._serializer = ref.firestore._serializer;
this._readTime = readTime;
this._createTime = createTime;
this._updateTime = updateTime;
}
/**
* Creates a DocumentSnapshot from an object.
*
* @private
* @internal
* @param ref The reference to the document.
* @param obj The object to store in the DocumentSnapshot.
* @return The created DocumentSnapshot.
*/
static fromObject(ref, obj) {
const serializer = ref.firestore._serializer;
return new DocumentSnapshot(ref, serializer.encodeFields(obj));
}
/**
* Creates a DocumentSnapshot from an UpdateMap.
*
* This methods expands the top-level field paths in a JavaScript map and
* turns { foo.bar : foobar } into { foo { bar : foobar }}
*
* @private
* @internal
* @param ref The reference to the document.
* @param data The field/value map to expand.
* @return The created DocumentSnapshot.
*/
static fromUpdateMap(ref, data) {
const serializer = ref
.firestore._serializer;
/**
* Merges 'value' at the field path specified by the path array into
* 'target'.
*/
function merge(target, value, path, pos) {
const key = path[pos];
const isLast = pos === path.length - 1;
if (target[key] === undefined) {
if (isLast) {
if (value instanceof field_value_1.FieldTransform) {
// If there is already data at this path, we need to retain it.
// Otherwise, we don't include it in the DocumentSnapshot.
return !(0, util_1.isEmpty)(target) ? target : null;
}
// The merge is done.
const leafNode = serializer.encodeValue(value);
if (leafNode) {
target[key] = leafNode;
}
return target;
}
else {
// We need to expand the target object.
const childNode = {
mapValue: {
fields: {},
},
};
const nestedValue = merge(childNode.mapValue.fields, value, path, pos + 1);
if (nestedValue) {
childNode.mapValue.fields = nestedValue;
target[key] = childNode;
return target;
}
else {
return !(0, util_1.isEmpty)(target) ? target : null;
}
}
}
else {
assert(!isLast, "Can't merge current value into a nested object");
target[key].mapValue.fields = merge(target[key].mapValue.fields, value, path, pos + 1);
return target;
}
}
const res = {};
for (const [key, value] of data) {
const path = key.toArray();
merge(res, value, path, 0);
}
return new DocumentSnapshot(ref, res);
}
/**
* True if the document exists.
*
* @type {boolean}
* @name DocumentSnapshot#exists
* @readonly
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then((documentSnapshot) => {
* if (documentSnapshot.exists) {
* console.log(`Data: ${JSON.stringify(documentSnapshot.data())}`);
* }
* });
* ```
*/
get exists() {
return this._fieldsProto !== undefined;
}
/**
* A [DocumentReference]{@link DocumentReference} for the document
* stored in this snapshot.
*
* @type {DocumentReference}
* @name DocumentSnapshot#ref
* @readonly
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then((documentSnapshot) => {
* if (documentSnapshot.exists) {
* console.log(`Found document at '${documentSnapshot.ref.path}'`);
* }
* });
* ```
*/
get ref() {
return this._ref;
}
/**
* The ID of the document for which this DocumentSnapshot contains data.
*
* @type {string}
* @name DocumentSnapshot#id
* @readonly
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then((documentSnapshot) => {
* if (documentSnapshot.exists) {
* console.log(`Document found with name '${documentSnapshot.id}'`);
* }
* });
* ```
*/
get id() {
return this._ref.id;
}
/**
* The time the document was created. Undefined for documents that don't
* exist.
*
* @type {Timestamp|undefined}
* @name DocumentSnapshot#createTime
* @readonly
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(documentSnapshot => {
* if (documentSnapshot.exists) {
* let createTime = documentSnapshot.createTime;
* console.log(`Document created at '${createTime.toDate()}'`);
* }
* });
* ```
*/
get createTime() {
return this._createTime;
}
/**
* The time the document was last updated (at the time the snapshot was
* generated). Undefined for documents that don't exist.
*
* @type {Timestamp|undefined}
* @name DocumentSnapshot#updateTime
* @readonly
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(documentSnapshot => {
* if (documentSnapshot.exists) {
* let updateTime = documentSnapshot.updateTime;
* console.log(`Document updated at '${updateTime.toDate()}'`);
* }
* });
* ```
*/
get updateTime() {
return this._updateTime;
}
/**
* The time this snapshot was read.
*
* @type {Timestamp}
* @name DocumentSnapshot#readTime
* @readonly
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(documentSnapshot => {
* let readTime = documentSnapshot.readTime;
* console.log(`Document read at '${readTime.toDate()}'`);
* });
* ```
*/
get readTime() {
if (this._readTime === undefined) {
throw new Error("Called 'readTime' on a local document");
}
return this._readTime;
}
/**
* Retrieves all fields in the document as an object. Returns 'undefined' if
* the document doesn't exist.
*
* @returns {T|undefined} An object containing all fields in the document or
* 'undefined' if the document doesn't exist.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(documentSnapshot => {
* let data = documentSnapshot.data();
* console.log(`Retrieved data: ${JSON.stringify(data)}`);
* });
* ```
*/
data() {
const fields = this._fieldsProto;
if (fields === undefined) {
return undefined;
}
// We only want to use the converter and create a new QueryDocumentSnapshot
// if a converter has been provided.
if (this.ref._converter !== (0, types_1.defaultConverter)()) {
const untypedReference = new document_reference_1.DocumentReference(this.ref.firestore, this.ref._path);
return this.ref._converter.fromFirestore(new QueryDocumentSnapshot(untypedReference, this._fieldsProto, this.readTime, this.createTime, this.updateTime));
}
else {
const obj = {};
for (const prop of Object.keys(fields)) {
obj[prop] = this._serializer.decodeValue(fields[prop]);
}
return obj;
}
}
/**
* Retrieves the field specified by `field`.
*
* @param {string|FieldPath} field The field path
* (e.g. 'foo' or 'foo.bar') to a specific field.
* @returns {*} The data at the specified field location or undefined if no
* such field exists.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.set({ a: { b: 'c' }}).then(() => {
* return documentRef.get();
* }).then(documentSnapshot => {
* let field = documentSnapshot.get('a.b');
* console.log(`Retrieved field value: ${field}`);
* });
* ```
*/
// We deliberately use `any` in the external API to not impose type-checking
// on end users.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get(field) {
(0, path_1.validateFieldPath)('field', field);
const protoField = this.protoField(field);
if (protoField === undefined) {
return undefined;
}
return this._serializer.decodeValue(protoField);
}
/**
* Retrieves the field specified by 'fieldPath' in its Protobuf JS
* representation.
*
* @private
* @internal
* @param field The path (e.g. 'foo' or 'foo.bar') to a specific field.
* @returns The Protobuf-encoded data at the specified field location or
* undefined if no such field exists.
*/
protoField(field) {
let fields = this._fieldsProto;
if (fields === undefined) {
return undefined;
}
const components = path_1.FieldPath.fromArgument(field).toArray();
while (components.length > 1) {
fields = fields[components.shift()];
if (!fields || !fields.mapValue) {
return undefined;
}
fields = fields.mapValue.fields;
}
return fields[components[0]];
}
/**
* Convert a document snapshot to the Firestore 'Write' proto.
*
* @private
* @internal
*/
toWriteProto() {
return {
update: {
name: this._ref.formattedName,
fields: this._fieldsProto,
},
};
}
/**
* Convert a document snapshot to the Firestore 'Document' proto.
*
* @private
* @internal
*/
toDocumentProto() {
var _a, _b;
return {
name: this._ref.formattedName,
createTime: (_a = this.createTime) === null || _a === void 0 ? void 0 : _a.toProto().timestampValue,
updateTime: (_b = this.updateTime) === null || _b === void 0 ? void 0 : _b.toProto().timestampValue,
fields: this._fieldsProto,
};
}
/**
* Returns true if the document's data and path in this `DocumentSnapshot` is
* equal to the provided value.
*
* @param {*} other The value to compare against.
* @return {boolean} true if this `DocumentSnapshot` is equal to the provided
* value.
*/
isEqual(other) {
// Since the read time is different on every document read, we explicitly
// ignore all document metadata in this comparison.
return (this === other ||
(other instanceof DocumentSnapshot &&
this._ref.isEqual(other._ref) &&
deepEqual(this._fieldsProto, other._fieldsProto)));
}
}
exports.DocumentSnapshot = DocumentSnapshot;
/**
* A QueryDocumentSnapshot contains data read from a document in your
* Firestore database as part of a query. The document is guaranteed to exist
* and its data can be extracted with [data()]{@link QueryDocumentSnapshot#data}
* or [get()]{@link DocumentSnapshot#get} to get a specific field.
*
* A QueryDocumentSnapshot offers the same API surface as a
* {@link DocumentSnapshot}. Since query results contain only existing
* documents, the [exists]{@link DocumentSnapshot#exists} property will
* always be true and [data()]{@link QueryDocumentSnapshot#data} will never
* return 'undefined'.
*
* @class QueryDocumentSnapshot
* @extends DocumentSnapshot
*/
class QueryDocumentSnapshot extends DocumentSnapshot {
/**
* The time the document was created.
*
* @type {Timestamp}
* @name QueryDocumentSnapshot#createTime
* @readonly
* @override
*
* @example
* ```
* let query = firestore.collection('col');
*
* query.get().forEach(snapshot => {
* console.log(`Document created at '${snapshot.createTime.toDate()}'`);
* });
* ```
*/
get createTime() {
return super.createTime;
}
/**
* The time the document was last updated (at the time the snapshot was
* generated).
*
* @type {Timestamp}
* @name QueryDocumentSnapshot#updateTime
* @readonly
* @override
*
* @example
* ```
* let query = firestore.collection('col');
*
* query.get().forEach(snapshot => {
* console.log(`Document updated at '${snapshot.updateTime.toDate()}'`);
* });
* ```
*/
get updateTime() {
return super.updateTime;
}
/**
* Retrieves all fields in the document as an object.
*
* @override
*
* @returns {T} An object containing all fields in the document.
*
* @example
* ```
* let query = firestore.collection('col');
*
* query.get().forEach(documentSnapshot => {
* let data = documentSnapshot.data();
* console.log(`Retrieved data: ${JSON.stringify(data)}`);
* });
* ```
*/
data() {
const data = super.data();
if (!data) {
throw new Error('The data in a QueryDocumentSnapshot should always exist.');
}
return data;
}
}
exports.QueryDocumentSnapshot = QueryDocumentSnapshot;
/**
* A Firestore Document Mask contains the field paths affected by an update.
*
* @class
* @private
* @internal
*/
class DocumentMask {
/**
* @private
* @internal
* @private
*
* @param fieldPaths The field paths in this mask.
*/
constructor(fieldPaths) {
this._sortedPaths = fieldPaths;
this._sortedPaths.sort((a, b) => a.compareTo(b));
}
/**
* Creates a document mask with the field paths of a document.
*
* @private
* @internal
* @param data A map with fields to modify. Only the keys are used to extract
* the document mask.
*/
static fromUpdateMap(data) {
const fieldPaths = [];
data.forEach((value, key) => {
if (!(value instanceof field_value_1.FieldTransform) || value.includeInDocumentMask) {
fieldPaths.push(path_1.FieldPath.fromArgument(key));
}
});
return new DocumentMask(fieldPaths);
}
/**
* Creates a document mask from an array of field paths.
*
* @private
* @internal
* @param fieldMask A list of field paths.
*/
static fromFieldMask(fieldMask) {
const fieldPaths = [];
for (const fieldPath of fieldMask) {
fieldPaths.push(path_1.FieldPath.fromArgument(fieldPath));
}
return new DocumentMask(fieldPaths);
}
/**
* Creates a document mask with the field names of a document.
*
* @private
* @internal
* @param data An object with fields to modify. Only the keys are used to
* extract the document mask.
*/
static fromObject(data) {
const fieldPaths = [];
function extractFieldPaths(currentData, currentPath) {
let isEmpty = true;
for (const key of Object.keys(currentData)) {
isEmpty = false;
// We don't split on dots since fromObject is called with
// DocumentData.
const childSegment = new path_1.FieldPath(key);
const childPath = currentPath
? currentPath.append(childSegment)
: childSegment;
const value = currentData[key];
if (value instanceof field_value_1.FieldTransform) {
if (value.includeInDocumentMask) {
fieldPaths.push(childPath);
}
}
else if ((0, util_1.isPlainObject)(value)) {
extractFieldPaths(value, childPath);
}
else if (value !== undefined) {
// If the value is undefined it can never participate in the document
// mask. With `ignoreUndefinedProperties` set to false,
// `validateDocumentData` will reject an undefined value before even
// computing the document mask.
fieldPaths.push(childPath);
}
}
// Add a field path for an explicitly updated empty map.
if (currentPath && isEmpty) {
fieldPaths.push(currentPath);
}
}
extractFieldPaths(data);
return new DocumentMask(fieldPaths);
}
/**
* Returns true if this document mask contains no fields.
*
* @private
* @internal
* @return {boolean} Whether this document mask is empty.
*/
get isEmpty() {
return this._sortedPaths.length === 0;
}
/**
* Removes the specified values from a sorted field path array.
*
* @private
* @internal
* @param input A sorted array of FieldPaths.
* @param values An array of FieldPaths to remove.
*/
static removeFromSortedArray(input, values) {
for (let i = 0; i < input.length;) {
let removed = false;
for (const fieldPath of values) {
if (input[i].isEqual(fieldPath)) {
input.splice(i, 1);
removed = true;
break;
}
}
if (!removed) {
++i;
}
}
}
/**
* Removes the field path specified in 'fieldPaths' from this document mask.
*
* @private
* @internal
* @param fieldPaths An array of FieldPaths.
*/
removeFields(fieldPaths) {
DocumentMask.removeFromSortedArray(this._sortedPaths, fieldPaths);
}
/**
* Returns whether this document mask contains 'fieldPath'.
*
* @private
* @internal
* @param fieldPath The field path to test.
* @return Whether this document mask contains 'fieldPath'.
*/
contains(fieldPath) {
for (const sortedPath of this._sortedPaths) {
const cmp = sortedPath.compareTo(fieldPath);
if (cmp === 0) {
return true;
}
else if (cmp > 0) {
return false;
}
}
return false;
}
/**
* Removes all properties from 'data' that are not contained in this document
* mask.
*
* @private
* @internal
* @param data An object to filter.
* @return A shallow copy of the object filtered by this document mask.
*/
applyTo(data) {
/*!
* Applies this DocumentMask to 'data' and computes the list of field paths
* that were specified in the mask but are not present in 'data'.
*/
const applyDocumentMask = data => {
const remainingPaths = this._sortedPaths.slice(0);
const processObject = (currentData, currentPath) => {
let result = null;
Object.keys(currentData).forEach(key => {
const childPath = currentPath
? currentPath.append(key)
: new path_1.FieldPath(key);
if (this.contains(childPath)) {
DocumentMask.removeFromSortedArray(remainingPaths, [childPath]);
result = result || {};
result[key] = currentData[key];
}
else if ((0, util_1.isObject)(currentData[key])) {
const childObject = processObject(currentData[key], childPath);
if (childObject) {
result = result || {};
result[key] = childObject;
}
}
});
return result;
};
// processObject() returns 'null' if the DocumentMask is empty.
const filteredData = processObject(data) || {};
return {
filteredData,
remainingPaths,
};
};
const result = applyDocumentMask(data);
if (result.remainingPaths.length !== 0) {
throw new Error(`Input data is missing for field "${result.remainingPaths[0]}".`);
}
return result.filteredData;
}
/**
* Converts a document mask to the Firestore 'DocumentMask' Proto.
*
* @private
* @internal
* @returns A Firestore 'DocumentMask' Proto.
*/
toProto() {
if (this.isEmpty) {
return {};
}
const encodedPaths = [];
for (const fieldPath of this._sortedPaths) {
encodedPaths.push(fieldPath.formattedName);
}
return {
fieldPaths: encodedPaths,
};
}
}
exports.DocumentMask = DocumentMask;
/**
* A Firestore Document Transform.
*
* A DocumentTransform contains pending server-side transforms and their
* corresponding field paths.
*
* @private
* @internal
* @class
*/
class DocumentTransform {
/**
* @private
* @internal
* @private
*
* @param ref The DocumentReference for this transform.
* @param transforms A Map of FieldPaths to FieldTransforms.
*/
constructor(ref, transforms) {
this.ref = ref;
this.transforms = transforms;
}
/**
* Generates a DocumentTransform from a JavaScript object.
*
* @private
* @internal
* @param ref The `DocumentReference` to use for the DocumentTransform.
* @param obj The object to extract the transformations from.
* @returns The Document Transform.
*/
static fromObject(ref, obj) {
const updateMap = new Map();
for (const prop of Object.keys(obj)) {
updateMap.set(new path_1.FieldPath(prop), obj[prop]);
}
return DocumentTransform.fromUpdateMap(ref, updateMap);
}
/**
* Generates a DocumentTransform from an Update Map.
*
* @private
* @internal
* @param ref The `DocumentReference` to use for the DocumentTransform.
* @param data The update data to extract the transformations from.
* @returns The Document Transform.
*/
static fromUpdateMap(ref, data) {
const transforms = new Map();
function encode_(val, path, allowTransforms) {
if (val instanceof field_value_1.FieldTransform && val.includeInDocumentTransform) {
if (allowTransforms) {
transforms.set(path, val);
}
else {
throw new Error(`${val.methodName}() is not supported inside of array values.`);
}
}
else if (Array.isArray(val)) {
for (let i = 0; i < val.length; ++i) {
// We need to verify that no array value contains a document transform
encode_(val[i], path.append(String(i)), false);
}
}
else if ((0, util_1.isPlainObject)(val)) {
for (const prop of Object.keys(val)) {
encode_(val[prop], path.append(new path_1.FieldPath(prop)), allowTransforms);
}
}
}
data.forEach((value, key) => {
encode_(value, path_1.FieldPath.fromArgument(key), true);
});
return new DocumentTransform(ref, transforms);
}
/**
* Whether this DocumentTransform contains any actionable transformations.
*
* @private
* @internal
*/
get isEmpty() {
return this.transforms.size === 0;
}
/**
* Returns the array of fields in this DocumentTransform.
*
* @private
* @internal
*/
get fields() {
return Array.from(this.transforms.keys());
}
/**
* Validates the user provided field values in this document transform.
* @private
* @internal
*/
validate() {
const allowUndefined = !!this.ref.firestore._settings.ignoreUndefinedProperties;
this.transforms.forEach(transform => transform.validate(allowUndefined));
}
/**
* Converts a document transform to the Firestore 'FieldTransform' Proto.
*
* @private
* @internal
* @param serializer The Firestore serializer
* @returns A list of Firestore 'FieldTransform' Protos
*/
toProto(serializer) {
return Array.from(this.transforms, ([path, transform]) => transform.toProto(serializer, path));
}
}
exports.DocumentTransform = DocumentTransform;
/**
* A Firestore Precondition encapsulates options for database writes.
*
* @private
* @internal
* @class
*/
class Precondition {
/**
* @private
* @internal
* @private
*
* @param options.exists - Whether the referenced document should exist in
* Firestore,
* @param options.lastUpdateTime - The last update time of the referenced
* document in Firestore.
* @param options
*/
constructor(options) {
if (options !== undefined) {
this._exists = options.exists;
this._lastUpdateTime = options.lastUpdateTime;
}
}
/**
* Generates the Protobuf `Preconditon` object for this precondition.
*
* @private
* @internal
* @returns The `Preconditon` Protobuf object or 'null' if there are no
* preconditions.
*/
toProto() {
if (this.isEmpty) {
return null;
}
const proto = {};
if (this._lastUpdateTime !== undefined) {
proto.updateTime = this._lastUpdateTime.toProto().timestampValue;
}
else {
proto.exists = this._exists;
}
return proto;
}
/**
* Whether this DocumentTransform contains any enforcement.
*
* @private
* @internal
*/
get isEmpty() {
return this._exists === undefined && !this._lastUpdateTime;
}
}
exports.Precondition = Precondition;
//# sourceMappingURL=document.js.map

View File

@@ -0,0 +1,289 @@
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import * as proto from '../protos/firestore_v1_proto_api';
import { FieldPath } from './path';
import { Serializer } from './serializer';
import api = proto.google.firestore.v1;
/**
* Represent a vector type in Firestore documents.
* Create an instance with {@link FieldValue.vector}.
*
* @class VectorValue
*/
export declare class VectorValue implements firestore.VectorValue {
private readonly _values;
/**
* @private
* @internal
*/
constructor(values: number[] | undefined);
/**
* Returns a copy of the raw number array form of the vector.
*/
toArray(): number[];
/**
* @private
* @internal
*/
_toProto(serializer: Serializer): api.IValue;
/**
* @private
* @internal
*/
static _fromProto(valueArray: api.IValue): VectorValue;
/**
* Returns `true` if the two VectorValue has the same raw number arrays, returns `false` otherwise.
*/
isEqual(other: VectorValue): boolean;
}
/**
* Sentinel values that can be used when writing documents with set(), create()
* or update().
*
* @class FieldValue
*/
export declare class FieldValue implements firestore.FieldValue {
/** @private */
constructor();
/**
* Creates a new `VectorValue` constructed with a copy of the given array of numbers.
*
* @param values - Create a `VectorValue` instance with a copy of this array of numbers.
*
* @returns A new `VectorValue` constructed with a copy of the given array of numbers.
*/
static vector(values?: number[]): VectorValue;
/**
* Returns a sentinel for use with update() or set() with {merge:true} to mark
* a field for deletion.
*
* @returns {FieldValue} The sentinel value to use in your objects.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
* let data = { a: 'b', c: 'd' };
*
* documentRef.set(data).then(() => {
* return documentRef.update({a: Firestore.FieldValue.delete()});
* }).then(() => {
* // Document now only contains { c: 'd' }
* });
* ```
*/
static delete(): FieldValue;
/**
* Returns a sentinel used with set(), create() or update() to include a
* server-generated timestamp in the written data.
*
* @return {FieldValue} The FieldValue sentinel for use in a call to set(),
* create() or update().
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.set({
* time: Firestore.FieldValue.serverTimestamp()
* }).then(() => {
* return documentRef.get();
* }).then(doc => {
* console.log(`Server time set to ${doc.get('time')}`);
* });
* ```
*/
static serverTimestamp(): FieldValue;
/**
* Returns a special value that can be used with set(), create() or update()
* that tells the server to increment the the field's current value by the
* given value.
*
* If either current field value or the operand uses floating point
* precision, both values will be interpreted as floating point numbers and
* all arithmetic will follow IEEE 754 semantics. Otherwise, integer
* precision is kept and the result is capped between -2^63 and 2^63-1.
*
* If the current field value is not of type 'number', or if the field does
* not yet exist, the transformation will set the field to the given value.
*
* @param {number} n The value to increment by.
* @return {FieldValue} The FieldValue sentinel for use in a call to set(),
* create() or update().
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.update(
* 'counter', Firestore.FieldValue.increment(1)
* ).then(() => {
* return documentRef.get();
* }).then(doc => {
* // doc.get('counter') was incremented
* });
* ```
*/
static increment(n: number): FieldValue;
/**
* Returns a special value that can be used with set(), create() or update()
* that tells the server to union the given elements with any array value that
* already exists on the server. Each specified element that doesn't already
* exist in the array will be added to the end. If the field being modified is
* not already an array it will be overwritten with an array containing
* exactly the specified elements.
*
* @param {...*} elements The elements to union into the array.
* @return {FieldValue} The FieldValue sentinel for use in a call to set(),
* create() or update().
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.update(
* 'array', Firestore.FieldValue.arrayUnion('foo')
* ).then(() => {
* return documentRef.get();
* }).then(doc => {
* // doc.get('array') contains field 'foo'
* });
* ```
*/
static arrayUnion(...elements: unknown[]): FieldValue;
/**
* Returns a special value that can be used with set(), create() or update()
* that tells the server to remove the given elements from any array value
* that already exists on the server. All instances of each element specified
* will be removed from the array. If the field being modified is not already
* an array it will be overwritten with an empty array.
*
* @param {...*} elements The elements to remove from the array.
* @return {FieldValue} The FieldValue sentinel for use in a call to set(),
* create() or update().
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.update(
* 'array', Firestore.FieldValue.arrayRemove('foo')
* ).then(() => {
* return documentRef.get();
* }).then(doc => {
* // doc.get('array') no longer contains field 'foo'
* });
* ```
*/
static arrayRemove(...elements: unknown[]): FieldValue;
/**
* Returns true if this `FieldValue` is equal to the provided value.
*
* @param {*} other The value to compare against.
* @return {boolean} true if this `FieldValue` is equal to the provided value.
*
* @example
* ```
* let fieldValues = [
* Firestore.FieldValue.increment(-1.0),
* Firestore.FieldValue.increment(-1),
* Firestore.FieldValue.increment(-0.0),
* Firestore.FieldValue.increment(-0),
* Firestore.FieldValue.increment(0),
* Firestore.FieldValue.increment(0.0),
* Firestore.FieldValue.increment(1),
* Firestore.FieldValue.increment(1.0)
* ];
*
* let equal = 0;
* for (let i = 0; i < fieldValues.length; ++i) {
* for (let j = i + 1; j < fieldValues.length; ++j) {
* if (fieldValues[i].isEqual(fieldValues[j])) {
* ++equal;
* }
* }
* }
* console.log(`Found ${equal} equalities.`);
* ```
*/
isEqual(other: firestore.FieldValue): boolean;
}
/**
* An internal interface shared by all field transforms.
*
* A 'FieldTransform` subclass should implement '.includeInDocumentMask',
* '.includeInDocumentTransform' and 'toProto' (if '.includeInDocumentTransform'
* is 'true').
*
* @private
* @internal
* @abstract
*/
export declare abstract class FieldTransform extends FieldValue {
/** Whether this FieldTransform should be included in the document mask. */
abstract get includeInDocumentMask(): boolean;
/**
* Whether this FieldTransform should be included in the list of document
* transforms.
*/
abstract get includeInDocumentTransform(): boolean;
/** The method name used to obtain the field transform. */
abstract get methodName(): string;
/**
* Performs input validation on the values of this field transform.
*
* @param allowUndefined Whether to allow nested properties that are `undefined`.
*/
abstract validate(allowUndefined: boolean): void;
/***
* The proto representation for this field transform.
*
* @param serializer The Firestore serializer.
* @param fieldPath The field path to apply this transformation to.
* @return The 'FieldTransform' proto message.
*/
abstract toProto(serializer: Serializer, fieldPath: FieldPath): api.DocumentTransform.IFieldTransform;
}
/**
* A transform that deletes a field from a Firestore document.
*
* @private
* @internal
*/
export declare class DeleteTransform extends FieldTransform {
/**
* Sentinel value for a field delete.
* @private
* @internal
*/
static DELETE_SENTINEL: DeleteTransform;
private constructor();
/**
* Deletes are included in document masks.
* @private
* @internal
*/
get includeInDocumentMask(): true;
/**
* Deletes are are omitted from document transforms.
* @private
* @internal
*/
get includeInDocumentTransform(): false;
get methodName(): string;
validate(): void;
toProto(): never;
}

View File

@@ -0,0 +1,523 @@
"use strict";
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DeleteTransform = exports.FieldTransform = exports.FieldValue = exports.VectorValue = void 0;
const deepEqual = require("fast-deep-equal");
const serializer_1 = require("./serializer");
const util_1 = require("./util");
const validate_1 = require("./validate");
/**
* Represent a vector type in Firestore documents.
* Create an instance with {@link FieldValue.vector}.
*
* @class VectorValue
*/
class VectorValue {
/**
* @private
* @internal
*/
constructor(values) {
// Making a copy of the parameter.
this._values = (values || []).map(n => n);
}
/**
* Returns a copy of the raw number array form of the vector.
*/
toArray() {
return this._values.map(n => n);
}
/**
* @private
* @internal
*/
_toProto(serializer) {
return serializer.encodeVector(this._values);
}
/**
* @private
* @internal
*/
static _fromProto(valueArray) {
var _a, _b;
const values = (_b = (_a = valueArray.arrayValue) === null || _a === void 0 ? void 0 : _a.values) === null || _b === void 0 ? void 0 : _b.map(v => {
return v.doubleValue;
});
return new VectorValue(values);
}
/**
* Returns `true` if the two VectorValue has the same raw number arrays, returns `false` otherwise.
*/
isEqual(other) {
return (0, util_1.isPrimitiveArrayEqual)(this._values, other._values);
}
}
exports.VectorValue = VectorValue;
/**
* Sentinel values that can be used when writing documents with set(), create()
* or update().
*
* @class FieldValue
*/
class FieldValue {
/** @private */
constructor() { }
/**
* Creates a new `VectorValue` constructed with a copy of the given array of numbers.
*
* @param values - Create a `VectorValue` instance with a copy of this array of numbers.
*
* @returns A new `VectorValue` constructed with a copy of the given array of numbers.
*/
static vector(values) {
return new VectorValue(values);
}
/**
* Returns a sentinel for use with update() or set() with {merge:true} to mark
* a field for deletion.
*
* @returns {FieldValue} The sentinel value to use in your objects.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
* let data = { a: 'b', c: 'd' };
*
* documentRef.set(data).then(() => {
* return documentRef.update({a: Firestore.FieldValue.delete()});
* }).then(() => {
* // Document now only contains { c: 'd' }
* });
* ```
*/
static delete() {
return DeleteTransform.DELETE_SENTINEL;
}
/**
* Returns a sentinel used with set(), create() or update() to include a
* server-generated timestamp in the written data.
*
* @return {FieldValue} The FieldValue sentinel for use in a call to set(),
* create() or update().
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.set({
* time: Firestore.FieldValue.serverTimestamp()
* }).then(() => {
* return documentRef.get();
* }).then(doc => {
* console.log(`Server time set to ${doc.get('time')}`);
* });
* ```
*/
static serverTimestamp() {
return ServerTimestampTransform.SERVER_TIMESTAMP_SENTINEL;
}
/**
* Returns a special value that can be used with set(), create() or update()
* that tells the server to increment the the field's current value by the
* given value.
*
* If either current field value or the operand uses floating point
* precision, both values will be interpreted as floating point numbers and
* all arithmetic will follow IEEE 754 semantics. Otherwise, integer
* precision is kept and the result is capped between -2^63 and 2^63-1.
*
* If the current field value is not of type 'number', or if the field does
* not yet exist, the transformation will set the field to the given value.
*
* @param {number} n The value to increment by.
* @return {FieldValue} The FieldValue sentinel for use in a call to set(),
* create() or update().
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.update(
* 'counter', Firestore.FieldValue.increment(1)
* ).then(() => {
* return documentRef.get();
* }).then(doc => {
* // doc.get('counter') was incremented
* });
* ```
*/
static increment(n) {
// eslint-disable-next-line prefer-rest-params
(0, validate_1.validateMinNumberOfArguments)('FieldValue.increment', arguments, 1);
return new NumericIncrementTransform(n);
}
/**
* Returns a special value that can be used with set(), create() or update()
* that tells the server to union the given elements with any array value that
* already exists on the server. Each specified element that doesn't already
* exist in the array will be added to the end. If the field being modified is
* not already an array it will be overwritten with an array containing
* exactly the specified elements.
*
* @param {...*} elements The elements to union into the array.
* @return {FieldValue} The FieldValue sentinel for use in a call to set(),
* create() or update().
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.update(
* 'array', Firestore.FieldValue.arrayUnion('foo')
* ).then(() => {
* return documentRef.get();
* }).then(doc => {
* // doc.get('array') contains field 'foo'
* });
* ```
*/
static arrayUnion(...elements) {
(0, validate_1.validateMinNumberOfArguments)('FieldValue.arrayUnion', elements, 1);
return new ArrayUnionTransform(elements);
}
/**
* Returns a special value that can be used with set(), create() or update()
* that tells the server to remove the given elements from any array value
* that already exists on the server. All instances of each element specified
* will be removed from the array. If the field being modified is not already
* an array it will be overwritten with an empty array.
*
* @param {...*} elements The elements to remove from the array.
* @return {FieldValue} The FieldValue sentinel for use in a call to set(),
* create() or update().
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.update(
* 'array', Firestore.FieldValue.arrayRemove('foo')
* ).then(() => {
* return documentRef.get();
* }).then(doc => {
* // doc.get('array') no longer contains field 'foo'
* });
* ```
*/
static arrayRemove(...elements) {
(0, validate_1.validateMinNumberOfArguments)('FieldValue.arrayRemove', elements, 1);
return new ArrayRemoveTransform(elements);
}
/**
* Returns true if this `FieldValue` is equal to the provided value.
*
* @param {*} other The value to compare against.
* @return {boolean} true if this `FieldValue` is equal to the provided value.
*
* @example
* ```
* let fieldValues = [
* Firestore.FieldValue.increment(-1.0),
* Firestore.FieldValue.increment(-1),
* Firestore.FieldValue.increment(-0.0),
* Firestore.FieldValue.increment(-0),
* Firestore.FieldValue.increment(0),
* Firestore.FieldValue.increment(0.0),
* Firestore.FieldValue.increment(1),
* Firestore.FieldValue.increment(1.0)
* ];
*
* let equal = 0;
* for (let i = 0; i < fieldValues.length; ++i) {
* for (let j = i + 1; j < fieldValues.length; ++j) {
* if (fieldValues[i].isEqual(fieldValues[j])) {
* ++equal;
* }
* }
* }
* console.log(`Found ${equal} equalities.`);
* ```
*/
isEqual(other) {
return this === other;
}
}
exports.FieldValue = FieldValue;
/**
* An internal interface shared by all field transforms.
*
* A 'FieldTransform` subclass should implement '.includeInDocumentMask',
* '.includeInDocumentTransform' and 'toProto' (if '.includeInDocumentTransform'
* is 'true').
*
* @private
* @internal
* @abstract
*/
class FieldTransform extends FieldValue {
}
exports.FieldTransform = FieldTransform;
/**
* A transform that deletes a field from a Firestore document.
*
* @private
* @internal
*/
class DeleteTransform extends FieldTransform {
constructor() {
super();
}
/**
* Deletes are included in document masks.
* @private
* @internal
*/
get includeInDocumentMask() {
return true;
}
/**
* Deletes are are omitted from document transforms.
* @private
* @internal
*/
get includeInDocumentTransform() {
return false;
}
get methodName() {
return 'FieldValue.delete';
}
validate() { }
toProto() {
throw new Error('FieldValue.delete() should not be included in a FieldTransform');
}
}
exports.DeleteTransform = DeleteTransform;
/**
* Sentinel value for a field delete.
* @private
* @internal
*/
DeleteTransform.DELETE_SENTINEL = new DeleteTransform();
/**
* A transform that sets a field to the Firestore server time.
*
* @private
* @internal
*/
class ServerTimestampTransform extends FieldTransform {
constructor() {
super();
}
/**
* Server timestamps are omitted from document masks.
*
* @private
* @internal
*/
get includeInDocumentMask() {
return false;
}
/**
* Server timestamps are included in document transforms.
*
* @private
* @internal
*/
get includeInDocumentTransform() {
return true;
}
get methodName() {
return 'FieldValue.serverTimestamp';
}
validate() { }
toProto(serializer, fieldPath) {
return {
fieldPath: fieldPath.formattedName,
setToServerValue: 'REQUEST_TIME',
};
}
}
/**
* Sentinel value for a server timestamp.
*
* @private
* @internal
*/
ServerTimestampTransform.SERVER_TIMESTAMP_SENTINEL = new ServerTimestampTransform();
/**
* Increments a field value on the backend.
*
* @private
* @internal
*/
class NumericIncrementTransform extends FieldTransform {
constructor(operand) {
super();
this.operand = operand;
}
/**
* Numeric transforms are omitted from document masks.
*
* @private
* @internal
*/
get includeInDocumentMask() {
return false;
}
/**
* Numeric transforms are included in document transforms.
*
* @private
* @internal
*/
get includeInDocumentTransform() {
return true;
}
get methodName() {
return 'FieldValue.increment';
}
validate() {
(0, validate_1.validateNumber)('FieldValue.increment()', this.operand);
}
toProto(serializer, fieldPath) {
const encodedOperand = serializer.encodeValue(this.operand);
return { fieldPath: fieldPath.formattedName, increment: encodedOperand };
}
isEqual(other) {
return (this === other ||
(other instanceof NumericIncrementTransform &&
this.operand === other.operand));
}
}
/**
* Transforms an array value via a union operation.
*
* @private
* @internal
*/
class ArrayUnionTransform extends FieldTransform {
constructor(elements) {
super();
this.elements = elements;
}
/**
* Array transforms are omitted from document masks.
* @private
* @internal
*/
get includeInDocumentMask() {
return false;
}
/**
* Array transforms are included in document transforms.
* @private
* @internal
*/
get includeInDocumentTransform() {
return true;
}
get methodName() {
return 'FieldValue.arrayUnion';
}
validate(allowUndefined) {
for (let i = 0; i < this.elements.length; ++i) {
validateArrayElement(i, this.elements[i], allowUndefined);
}
}
toProto(serializer, fieldPath) {
const encodedElements = serializer.encodeValue(this.elements).arrayValue;
return {
fieldPath: fieldPath.formattedName,
appendMissingElements: encodedElements,
};
}
isEqual(other) {
return (this === other ||
(other instanceof ArrayUnionTransform &&
deepEqual(this.elements, other.elements)));
}
}
/**
* Transforms an array value via a remove operation.
*
* @private
* @internal
*/
class ArrayRemoveTransform extends FieldTransform {
constructor(elements) {
super();
this.elements = elements;
}
/**
* Array transforms are omitted from document masks.
* @private
* @internal
*/
get includeInDocumentMask() {
return false;
}
/**
* Array transforms are included in document transforms.
* @private
* @internal
*/
get includeInDocumentTransform() {
return true;
}
get methodName() {
return 'FieldValue.arrayRemove';
}
validate(allowUndefined) {
for (let i = 0; i < this.elements.length; ++i) {
validateArrayElement(i, this.elements[i], allowUndefined);
}
}
toProto(serializer, fieldPath) {
const encodedElements = serializer.encodeValue(this.elements).arrayValue;
return {
fieldPath: fieldPath.formattedName,
removeAllFromArray: encodedElements,
};
}
isEqual(other) {
return (this === other ||
(other instanceof ArrayRemoveTransform &&
deepEqual(this.elements, other.elements)));
}
}
/**
* Validates that `value` can be used as an element inside of an array. Certain
* field values (such as ServerTimestamps) are rejected. Nested arrays are also
* rejected.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param value The value to validate.
* @param allowUndefined Whether to allow nested properties that are `undefined`.
*/
function validateArrayElement(arg, value, allowUndefined) {
if (Array.isArray(value)) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'array element')} Nested arrays are not supported.`);
}
(0, serializer_1.validateUserInput)(arg, value, 'array element',
/*path=*/ { allowDeletes: 'none', allowTransforms: false, allowUndefined },
/*path=*/ undefined,
/*level=*/ 0,
/*inArray=*/ true);
}
//# sourceMappingURL=field-value.js.map

View File

@@ -0,0 +1,184 @@
/*!
* Copyright 2023 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
/**
* A `Filter` represents a restriction on one or more field values and can
* be used to refine the results of a {@link Query}.
* `Filters`s are created by invoking {@link Filter#where}, {@link Filter#or},
* or {@link Filter#and} and can then be passed to {@link Query#where}
* to create a new {@link Query} instance that also contains this `Filter`.
*/
export declare abstract class Filter {
/**
* Creates and returns a new [Filter]{@link Filter}, which can be
* applied to [Query.where()]{@link Query#where}, [Filter.or()]{@link Filter#or},
* or [Filter.and()]{@link Filter#and}. When applied to a [Query]{@link Query}
* it requires that documents must contain the specified field and that its value should
* satisfy the relation constraint provided.
*
* @param {string|FieldPath} fieldPath The name of a property value to compare.
* @param {string} opStr A comparison operation in the form of a string.
* Acceptable operator strings are "<", "<=", "==", "!=", ">=", ">", "array-contains",
* "in", "not-in", and "array-contains-any".
* @param {*} value The value to which to compare the field for inclusion in
* a query.
* @returns {Filter} The created Filter.
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* collectionRef.where(Filter.where('foo', '==', 'bar')).get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
static where(fieldPath: string | firestore.FieldPath, opStr: firestore.WhereFilterOp, value: unknown): Filter;
/**
* Creates and returns a new [Filter]{@link Filter} that is a
* disjunction of the given {@link Filter}s. A disjunction filter includes
* a document if it satisfies any of the given {@link Filter}s.
*
* The returned Filter can be applied to [Query.where()]{@link Query#where},
* [Filter.or()]{@link Filter#or}, or [Filter.and()]{@link Filter#and}. When
* applied to a [Query]{@link Query} it requires that documents must satisfy
* one of the provided {@link Filter}s.
*
* @param {...Filter} filters Optional. The {@link Filter}s
* for OR operation. These must be created with calls to {@link Filter#where},
* {@link Filter#or}, or {@link Filter#and}.
* @returns {Filter} The created {@link Filter}.
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* // doc.foo == 'bar' || doc.baz > 0
* let orFilter = Filter.or(Filter.where('foo', '==', 'bar'), Filter.where('baz', '>', 0));
*
* collectionRef.where(orFilter).get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
static or(...filters: Filter[]): Filter;
/**
* Creates and returns a new [Filter]{@link Filter} that is a
* conjunction of the given {@link Filter}s. A conjunction filter includes
* a document if it satisfies all of the given {@link Filter}s.
*
* The returned Filter can be applied to [Query.where()]{@link Query#where},
* [Filter.or()]{@link Filter#or}, or [Filter.and()]{@link Filter#and}. When
* applied to a [Query]{@link Query} it requires that documents must satisfy
* one of the provided {@link Filter}s.
*
* @param {...Filter} filters Optional. The {@link Filter}s
* for AND operation. These must be created with calls to {@link Filter#where},
* {@link Filter#or}, or {@link Filter#and}.
* @returns {Filter} The created {@link Filter}.
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* // doc.foo == 'bar' && doc.baz > 0
* let andFilter = Filter.and(Filter.where('foo', '==', 'bar'), Filter.where('baz', '>', 0));
*
* collectionRef.where(andFilter).get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
static and(...filters: Filter[]): Filter;
}
/**
* A `UnaryFilter` represents a restriction on one field value and can
* be used to refine the results of a {@link Query}.
* `UnaryFilter`s are created by invoking {@link Filter#where} and can then
* be passed to {@link Query#where} to create a new {@link Query} instance
* that also contains this `UnaryFilter`.
*
* @private
* @internal
*/
export declare class UnaryFilter extends Filter {
private field;
private operator;
private value;
/**
@private
@internal
*/
constructor(field: string | firestore.FieldPath, operator: firestore.WhereFilterOp, value: unknown);
/**
@private
@internal
*/
_getField(): string | firestore.FieldPath;
/**
@private
@internal
*/
_getOperator(): firestore.WhereFilterOp;
/**
@private
@internal
*/
_getValue(): unknown;
}
/**
* A `CompositeFilter` is used to narrow the set of documents returned
* by a Firestore query by performing the logical OR or AND of multiple
* {@link Filters}s. `CompositeFilters`s are created by invoking {@link Filter#or}
* or {@link Filter#and} and can then be passed to {@link Query#where}
* to create a new query instance that also contains the `CompositeFilter`.
*
* @private
* @internal
*/
export declare class CompositeFilter extends Filter {
private filters;
private operator;
/**
@private
@internal
*/
constructor(filters: Filter[], operator: CompositeOperator);
/**
@private
@internal
*/
_getFilters(): Filter[];
/**
@private
@internal
*/
_getOperator(): CompositeOperator;
}
/**
* Composition operator of a `CompositeFilter`. This operator specifies the
* behavior of the `CompositeFilter`.
*
* @private
* @internal
*/
export type CompositeOperator = 'AND' | 'OR';

View File

@@ -0,0 +1,202 @@
"use strict";
/*!
* Copyright 2023 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CompositeFilter = exports.UnaryFilter = exports.Filter = void 0;
/**
* A `Filter` represents a restriction on one or more field values and can
* be used to refine the results of a {@link Query}.
* `Filters`s are created by invoking {@link Filter#where}, {@link Filter#or},
* or {@link Filter#and} and can then be passed to {@link Query#where}
* to create a new {@link Query} instance that also contains this `Filter`.
*/
class Filter {
/**
* Creates and returns a new [Filter]{@link Filter}, which can be
* applied to [Query.where()]{@link Query#where}, [Filter.or()]{@link Filter#or},
* or [Filter.and()]{@link Filter#and}. When applied to a [Query]{@link Query}
* it requires that documents must contain the specified field and that its value should
* satisfy the relation constraint provided.
*
* @param {string|FieldPath} fieldPath The name of a property value to compare.
* @param {string} opStr A comparison operation in the form of a string.
* Acceptable operator strings are "<", "<=", "==", "!=", ">=", ">", "array-contains",
* "in", "not-in", and "array-contains-any".
* @param {*} value The value to which to compare the field for inclusion in
* a query.
* @returns {Filter} The created Filter.
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* collectionRef.where(Filter.where('foo', '==', 'bar')).get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
static where(fieldPath, opStr, value) {
return new UnaryFilter(fieldPath, opStr, value);
}
/**
* Creates and returns a new [Filter]{@link Filter} that is a
* disjunction of the given {@link Filter}s. A disjunction filter includes
* a document if it satisfies any of the given {@link Filter}s.
*
* The returned Filter can be applied to [Query.where()]{@link Query#where},
* [Filter.or()]{@link Filter#or}, or [Filter.and()]{@link Filter#and}. When
* applied to a [Query]{@link Query} it requires that documents must satisfy
* one of the provided {@link Filter}s.
*
* @param {...Filter} filters Optional. The {@link Filter}s
* for OR operation. These must be created with calls to {@link Filter#where},
* {@link Filter#or}, or {@link Filter#and}.
* @returns {Filter} The created {@link Filter}.
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* // doc.foo == 'bar' || doc.baz > 0
* let orFilter = Filter.or(Filter.where('foo', '==', 'bar'), Filter.where('baz', '>', 0));
*
* collectionRef.where(orFilter).get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
static or(...filters) {
return new CompositeFilter(filters, 'OR');
}
/**
* Creates and returns a new [Filter]{@link Filter} that is a
* conjunction of the given {@link Filter}s. A conjunction filter includes
* a document if it satisfies all of the given {@link Filter}s.
*
* The returned Filter can be applied to [Query.where()]{@link Query#where},
* [Filter.or()]{@link Filter#or}, or [Filter.and()]{@link Filter#and}. When
* applied to a [Query]{@link Query} it requires that documents must satisfy
* one of the provided {@link Filter}s.
*
* @param {...Filter} filters Optional. The {@link Filter}s
* for AND operation. These must be created with calls to {@link Filter#where},
* {@link Filter#or}, or {@link Filter#and}.
* @returns {Filter} The created {@link Filter}.
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* // doc.foo == 'bar' && doc.baz > 0
* let andFilter = Filter.and(Filter.where('foo', '==', 'bar'), Filter.where('baz', '>', 0));
*
* collectionRef.where(andFilter).get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
static and(...filters) {
return new CompositeFilter(filters, 'AND');
}
}
exports.Filter = Filter;
/**
* A `UnaryFilter` represents a restriction on one field value and can
* be used to refine the results of a {@link Query}.
* `UnaryFilter`s are created by invoking {@link Filter#where} and can then
* be passed to {@link Query#where} to create a new {@link Query} instance
* that also contains this `UnaryFilter`.
*
* @private
* @internal
*/
class UnaryFilter extends Filter {
/**
@private
@internal
*/
constructor(field, operator, value) {
super();
this.field = field;
this.operator = operator;
this.value = value;
}
/**
@private
@internal
*/
_getField() {
return this.field;
}
/**
@private
@internal
*/
_getOperator() {
return this.operator;
}
/**
@private
@internal
*/
_getValue() {
return this.value;
}
}
exports.UnaryFilter = UnaryFilter;
/**
* A `CompositeFilter` is used to narrow the set of documents returned
* by a Firestore query by performing the logical OR or AND of multiple
* {@link Filters}s. `CompositeFilters`s are created by invoking {@link Filter#or}
* or {@link Filter#and} and can then be passed to {@link Query#where}
* to create a new query instance that also contains the `CompositeFilter`.
*
* @private
* @internal
*/
class CompositeFilter extends Filter {
/**
@private
@internal
*/
constructor(filters, operator) {
super();
this.filters = filters;
this.operator = operator;
}
/**
@private
@internal
*/
_getFilters() {
return this.filters;
}
/**
@private
@internal
*/
_getOperator() {
return this.operator;
}
}
exports.CompositeFilter = CompositeFilter;
//# sourceMappingURL=filter.js.map

View File

@@ -0,0 +1,83 @@
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import { google } from '../protos/firestore_v1_proto_api';
import { Serializable } from './serializer';
import api = google.firestore.v1;
/**
* An immutable object representing a geographic location in Firestore. The
* location is represented as a latitude/longitude pair.
*
* @class
*/
export declare class GeoPoint implements Serializable, firestore.GeoPoint {
private readonly _latitude;
private readonly _longitude;
/**
* Creates a [GeoPoint]{@link GeoPoint}.
*
* @param {number} latitude The latitude as a number between -90 and 90.
* @param {number} longitude The longitude as a number between -180 and 180.
*
* @example
* ```
* let data = {
* google: new Firestore.GeoPoint(37.422, 122.084)
* };
*
* firestore.doc('col/doc').set(data).then(() => {
* console.log(`Location is ${data.google.latitude}, ` +
* `${data.google.longitude}`);
* });
* ```
*/
constructor(latitude: number, longitude: number);
/**
* The latitude as a number between -90 and 90.
*
* @type {number}
* @name GeoPoint#latitude
* @readonly
*/
get latitude(): number;
/**
* The longitude as a number between -180 and 180.
*
* @type {number}
* @name GeoPoint#longitude
* @readonly
*/
get longitude(): number;
/**
* Returns true if this `GeoPoint` is equal to the provided value.
*
* @param {*} other The value to compare against.
* @return {boolean} true if this `GeoPoint` is equal to the provided value.
*/
isEqual(other: firestore.GeoPoint): boolean;
/**
* Converts the GeoPoint to a google.type.LatLng proto.
* @private
* @internal
*/
toProto(): api.IValue;
/**
* Converts a google.type.LatLng proto to its GeoPoint representation.
* @private
* @internal
*/
static fromProto(proto: google.type.ILatLng): GeoPoint;
}

View File

@@ -0,0 +1,106 @@
"use strict";
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeoPoint = void 0;
const validate_1 = require("./validate");
/**
* An immutable object representing a geographic location in Firestore. The
* location is represented as a latitude/longitude pair.
*
* @class
*/
class GeoPoint {
/**
* Creates a [GeoPoint]{@link GeoPoint}.
*
* @param {number} latitude The latitude as a number between -90 and 90.
* @param {number} longitude The longitude as a number between -180 and 180.
*
* @example
* ```
* let data = {
* google: new Firestore.GeoPoint(37.422, 122.084)
* };
*
* firestore.doc('col/doc').set(data).then(() => {
* console.log(`Location is ${data.google.latitude}, ` +
* `${data.google.longitude}`);
* });
* ```
*/
constructor(latitude, longitude) {
(0, validate_1.validateNumber)('latitude', latitude, { minValue: -90, maxValue: 90 });
(0, validate_1.validateNumber)('longitude', longitude, { minValue: -180, maxValue: 180 });
this._latitude = latitude;
this._longitude = longitude;
}
/**
* The latitude as a number between -90 and 90.
*
* @type {number}
* @name GeoPoint#latitude
* @readonly
*/
get latitude() {
return this._latitude;
}
/**
* The longitude as a number between -180 and 180.
*
* @type {number}
* @name GeoPoint#longitude
* @readonly
*/
get longitude() {
return this._longitude;
}
/**
* Returns true if this `GeoPoint` is equal to the provided value.
*
* @param {*} other The value to compare against.
* @return {boolean} true if this `GeoPoint` is equal to the provided value.
*/
isEqual(other) {
return (this === other ||
(other instanceof GeoPoint &&
this.latitude === other.latitude &&
this.longitude === other.longitude));
}
/**
* Converts the GeoPoint to a google.type.LatLng proto.
* @private
* @internal
*/
toProto() {
return {
geoPointValue: {
latitude: this.latitude,
longitude: this.longitude,
},
};
}
/**
* Converts a google.type.LatLng proto to its GeoPoint representation.
* @private
* @internal
*/
static fromProto(proto) {
return new GeoPoint(proto.latitude || 0, proto.longitude || 0);
}
}
exports.GeoPoint = GeoPoint;
//# sourceMappingURL=geo-point.js.map

View File

@@ -0,0 +1,990 @@
/*!
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import { Duplex } from 'stream';
import { google } from '../protos/firestore_v1_proto_api';
import { BulkWriter } from './bulk-writer';
import { BundleBuilder } from './bundle';
import { DocumentSnapshot, QueryDocumentSnapshot } from './document';
import { CollectionReference } from './reference/collection-reference';
import { DocumentReference } from './reference/document-reference';
import { Serializer } from './serializer';
import { Transaction } from './transaction';
import { FirestoreStreamingMethod, FirestoreUnaryMethod } from './types';
import { WriteBatch } from './write-batch';
import api = google.firestore.v1;
import { CollectionGroup } from './collection-group';
import { TraceUtil } from './telemetry/trace-util';
export { CollectionReference } from './reference/collection-reference';
export { DocumentReference } from './reference/document-reference';
export { QuerySnapshot } from './reference/query-snapshot';
export { Query } from './reference/query';
export type { AggregateQuery } from './reference/aggregate-query';
export type { AggregateQuerySnapshot } from './reference/aggregate-query-snapshot';
export type { VectorQuery } from './reference/vector-query';
export type { VectorQuerySnapshot } from './reference/vector-query-snapshot';
export type { VectorQueryOptions } from './reference/vector-query-options';
export { BulkWriter } from './bulk-writer';
export type { BulkWriterError } from './bulk-writer';
export type { BundleBuilder } from './bundle';
export { DocumentSnapshot, QueryDocumentSnapshot } from './document';
export { FieldValue, VectorValue } from './field-value';
export { Filter } from './filter';
export { WriteBatch, WriteResult } from './write-batch';
export { Transaction } from './transaction';
export { Timestamp } from './timestamp';
export { DocumentChange } from './document-change';
export type { DocumentChangeType } from './document-change';
export { FieldPath } from './path';
export { GeoPoint } from './geo-point';
export { CollectionGroup };
export { QueryPartition } from './query-partition';
export { setLogFunction } from './logger';
export { Aggregate, AggregateField } from './aggregate';
export type { AggregateFieldType, AggregateSpec, AggregateType, } from './aggregate';
export type { PlanSummary, ExecutionStats, ExplainMetrics, ExplainResults, } from './query-profile';
/**
* The maximum number of times to retry idempotent requests.
* @private
*/
export declare const MAX_REQUEST_RETRIES = 5;
/**
* The maximum number of times to attempt a transaction before failing.
* @private
*/
export declare const DEFAULT_MAX_TRANSACTION_ATTEMPTS = 5;
/*!
* The default number of idle GRPC channel to keep.
*/
export declare const DEFAULT_MAX_IDLE_CHANNELS = 1;
/**
* Document data (e.g. for use with
* [set()]{@link DocumentReference#set}) consisting of fields mapped
* to values.
*
* @typedef {Object.<string, *>} DocumentData
*/
/**
* Converter used by [withConverter()]{@link Query#withConverter} to transform
* user objects of type `AppModelType` into Firestore data of type
* `DbModelType`.
*
* Using the converter allows you to specify generic type arguments when storing
* and retrieving objects from Firestore.
*
* @example
* ```
* class Post {
* constructor(readonly title: string, readonly author: string) {}
*
* toString(): string {
* return this.title + ', by ' + this.author;
* }
* }
*
* const postConverter = {
* toFirestore(post: Post): FirebaseFirestore.DocumentData {
* return {title: post.title, author: post.author};
* },
* fromFirestore(
* snapshot: FirebaseFirestore.QueryDocumentSnapshot
* ): Post {
* const data = snapshot.data();
* return new Post(data.title, data.author);
* }
* };
*
* const postSnap = await Firestore()
* .collection('posts')
* .withConverter(postConverter)
* .doc().get();
* const post = postSnap.data();
* if (post !== undefined) {
* post.title; // string
* post.toString(); // Should be defined
* post.someNonExistentProperty; // TS error
* }
*
* ```
* @property {Function} toFirestore Called by the Firestore SDK to convert a
* custom model object of type `AppModelType` into a plain Javascript object
* (suitable for writing directly to the Firestore database).
* @property {Function} fromFirestore Called by the Firestore SDK to convert
* Firestore data into an object of type `AppModelType`.
* @typedef {Object} FirestoreDataConverter
*/
/**
* Update data (for use with [update]{@link DocumentReference#update})
* that contains paths mapped to values. Fields that contain dots
* reference nested fields within the document.
*
* You can update a top-level field in your document by using the field name
* as a key (e.g. `foo`). The provided value completely replaces the contents
* for this field.
*
* You can also update a nested field directly by using its field path as a key
* (e.g. `foo.bar`). This nested field update replaces the contents at `bar`
* but does not modify other data under `foo`.
*
* @example
* ```
* const documentRef = firestore.doc('coll/doc');
* documentRef.set({a1: {a2: 'val'}, b1: {b2: 'val'}, c1: {c2: 'val'}});
* documentRef.update({
* b1: {b3: 'val'},
* 'c1.c3': 'val',
* });
* // Value is {a1: {a2: 'val'}, b1: {b3: 'val'}, c1: {c2: 'val', c3: 'val'}}
*
* ```
* @typedef {Object.<string, *>} UpdateData
*/
/**
* An options object that configures conditional behavior of
* [update()]{@link DocumentReference#update} and
* [delete()]{@link DocumentReference#delete} calls in
* [DocumentReference]{@link DocumentReference},
* [WriteBatch]{@link WriteBatch}, [BulkWriter]{@link BulkWriter}, and
* [Transaction]{@link Transaction}. Using Preconditions, these calls
* can be restricted to only apply to documents that match the specified
* conditions.
*
* @example
* ```
* const documentRef = firestore.doc('coll/doc');
*
* documentRef.get().then(snapshot => {
* const updateTime = snapshot.updateTime;
*
* console.log(`Deleting document at update time: ${updateTime.toDate()}`);
* return documentRef.delete({ lastUpdateTime: updateTime });
* });
*
* ```
* @property {Timestamp} lastUpdateTime The update time to enforce. If set,
* enforces that the document was last updated at lastUpdateTime. Fails the
* operation if the document was last updated at a different time.
* @property {boolean} exists If set, enforces that the target document must
* or must not exist.
* @typedef {Object} Precondition
*/
/**
* An options object that configures the behavior of
* [set()]{@link DocumentReference#set} calls in
* [DocumentReference]{@link DocumentReference},
* [WriteBatch]{@link WriteBatch}, and
* [Transaction]{@link Transaction}. These calls can be
* configured to perform granular merges instead of overwriting the target
* documents in their entirety by providing a SetOptions object with
* { merge : true }.
*
* @property {boolean} merge Changes the behavior of a set() call to only
* replace the values specified in its data argument. Fields omitted from the
* set() call remain untouched.
* @property {Array<(string|FieldPath)>} mergeFields Changes the behavior of
* set() calls to only replace the specified field paths. Any field path that is
* not specified is ignored and remains untouched.
* It is an error to pass a SetOptions object to a set() call that is missing a
* value for any of the fields specified here.
* @typedef {Object} SetOptions
*/
/**
* An options object that can be used to configure the behavior of
* [getAll()]{@link Firestore#getAll} calls. By providing a `fieldMask`, these
* calls can be configured to only return a subset of fields.
*
* @property {Array<(string|FieldPath)>} fieldMask Specifies the set of fields
* to return and reduces the amount of data transmitted by the backend.
* Adding a field mask does not filter results. Documents do not need to
* contain values for all the fields in the mask to be part of the result set.
* @typedef {Object} ReadOptions
*/
/**
* An options object to configure throttling on BulkWriter.
*
* Whether to disable or configure throttling. By default, throttling is
* enabled. `throttling` can be set to either a boolean or a config object.
* Setting it to `true` will use default values. You can override the defaults
* by setting it to `false` to disable throttling, or by setting the config
* values to enable throttling with the provided values.
*
* @property {boolean|Object} throttling Whether to disable or enable
* throttling. Throttling is enabled by default, if the field is set to `true`
* or if any custom throttling options are provided. `{ initialOpsPerSecond:
* number }` sets the initial maximum number of operations per second allowed by
* the throttler. If `initialOpsPerSecond` is not set, the default is 500
* operations per second. `{ maxOpsPerSecond: number }` sets the maximum number
* of operations per second allowed by the throttler. If `maxOpsPerSecond` is
* not set, no maximum is enforced.
* @typedef {Object} BulkWriterOptions
*/
/**
* An error thrown when a BulkWriter operation fails.
*
* The error used by {@link BulkWriter~shouldRetryCallback} set in
* {@link BulkWriter#onWriteError}.
*
* @property {GrpcStatus} code The status code of the error.
* @property {string} message The error message of the error.
* @property {DocumentReference} documentRef The document reference the
* operation was performed on.
* @property {'create' | 'set' | 'update' | 'delete'} operationType The type
* of operation performed.
* @property {number} failedAttempts How many times this operation has been
* attempted unsuccessfully.
* @typedef {Error} BulkWriterError
*/
/**
* Status codes returned by GRPC operations.
*
* @see https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
*
* @enum {number}
* @typedef {Object} GrpcStatus
*/
/**
* The Firestore client represents a Firestore Database and is the entry point
* for all Firestore operations.
*
* @see [Firestore Documentation]{@link https://firebase.google.com/docs/firestore/}
*
* @class
*
* @example Install the client library with <a href="https://www.npmjs.com/">npm</a>:
* ```
* npm install --save @google-cloud/firestore
*
* ```
* @example Import the client library
* ```
* var Firestore = require('@google-cloud/firestore');
*
* ```
* @example Create a client that uses <a href="https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application">Application Default Credentials (ADC)</a>:
* ```
* var firestore = new Firestore();
*
* ```
* @example Create a client with <a href="https://cloud.google.com/docs/authentication/production#obtaining_and_providing_service_account_credentials_manually">explicit credentials</a>:
* ```
* var firestore = new Firestore({ projectId:
* 'your-project-id', keyFilename: '/path/to/keyfile.json'
* });
*
* ```
* @example <caption>include:samples/quickstart.js</caption>
* region_tag:firestore_quickstart
* Full quickstart example:
*/
export declare class Firestore implements firestore.Firestore {
/**
* A client pool to distribute requests over multiple GAPIC clients in order
* to work around a connection limit of 100 concurrent requests per client.
* @private
* @internal
*/
private _clientPool;
/**
* Preloaded instance of google-gax (full module, with gRPC support).
*/
private _gax?;
/**
* Preloaded instance of google-gax HTTP fallback implementation (no gRPC).
*/
private _gaxFallback?;
/**
* The configuration options for the GAPIC client.
* @private
* @internal
*/
_settings: firestore.Settings;
/**
* Settings for the exponential backoff used by the streaming endpoints.
* @private
* @internal
*/
private _backoffSettings;
/**
* Whether the initialization settings can still be changed by invoking
* `settings()`.
* @private
* @internal
*/
private _settingsFrozen;
/**
* The serializer to use for the Protobuf transformation.
* @private
* @internal
*/
_serializer: Serializer | null;
/**
* The OpenTelemetry tracing utility object.
* @private
* @internal
*/
_traceUtil: TraceUtil;
/**
* The project ID for this client.
*
* The project ID is auto-detected during the first request unless a project
* ID is passed to the constructor (or provided via `.settings()`).
* @private
* @internal
*/
private _projectId;
/**
* The database ID provided via `.settings()`.
*
* @private
* @internal
*/
private _databaseId;
/**
* Count of listeners that have been registered on the client.
*
* The client can only be terminated when there are no pending writes or
* registered listeners.
* @private
* @internal
*/
private registeredListenersCount;
/**
* A lazy-loaded BulkWriter instance to be used with recursiveDelete() if no
* BulkWriter instance is provided.
*
* @private
* @internal
*/
private _bulkWriter;
/**
* Lazy-load the Firestore's default BulkWriter.
*
* @private
* @internal
*/
private getBulkWriter;
/**
* Number of pending operations on the client.
*
* The client can only be terminated when there are no pending writes or
* registered listeners.
* @private
* @internal
*/
private bulkWritersCount;
/**
* @param {Object=} settings [Configuration object](#/docs).
* @param {string=} settings.projectId The project ID from the Google
* Developer's Console, e.g. 'grape-spaceship-123'. We will also check the
* environment variable GCLOUD_PROJECT for your project ID. Can be omitted in
* environments that support
* {@link https://cloud.google.com/docs/authentication Application Default
* Credentials}
* @param {string=} settings.keyFilename Local file containing the Service
* Account credentials as downloaded from the Google Developers Console. Can
* be omitted in environments that support
* {@link https://cloud.google.com/docs/authentication Application Default
* Credentials}. To configure Firestore with custom credentials, use
* `settings.credentials` and provide the `client_email` and `private_key` of
* your service account.
* @param {{client_email:string=, private_key:string=}=} settings.credentials
* The `client_email` and `private_key` properties of the service account
* to use with your Firestore project. Can be omitted in environments that
* support {@link https://cloud.google.com/docs/authentication Application
* Default Credentials}. If your credentials are stored in a JSON file, you
* can specify a `keyFilename` instead.
* @param {string=} settings.host The host to connect to.
* @param {boolean=} settings.ssl Whether to use SSL when connecting.
* @param {number=} settings.maxIdleChannels The maximum number of idle GRPC
* channels to keep. A smaller number of idle channels reduces memory usage
* but increases request latency for clients with fluctuating request rates.
* If set to 0, shuts down all GRPC channels when the client becomes idle.
* Defaults to 1.
* @param {boolean=} settings.ignoreUndefinedProperties Whether to skip nested
* properties that are set to `undefined` during object serialization. If set
* to `true`, these properties are skipped and not written to Firestore. If
* set `false` or omitted, the SDK throws an exception when it encounters
* properties of type `undefined`.
* @param {boolean=} settings.preferRest Whether to force the use of HTTP/1.1 REST
* transport until a method that requires gRPC is called. When a method requires gRPC,
* this Firestore client will load dependent gRPC libraries and then use gRPC transport
* for communication from that point forward. Currently the only operation
* that requires gRPC is creating a snapshot listener with the method
* `DocumentReference<T>.onSnapshot()`, `CollectionReference<T>.onSnapshot()`, or
* `Query<T>.onSnapshot()`. If specified, this setting value will take precedent over the
* environment variable `FIRESTORE_PREFER_REST`. If not specified, the
* SDK will use the value specified in the environment variable `FIRESTORE_PREFER_REST`.
* Valid values of `FIRESTORE_PREFER_REST` are `true` ('1') or `false` (`0`). Values are
* not case-sensitive. Any other value for the environment variable will be ignored and
* a warning will be logged to the console.
*/
constructor(settings?: firestore.Settings);
/**
* Specifies custom settings to be used to configure the `Firestore`
* instance. Can only be invoked once and before any other Firestore method.
*
* If settings are provided via both `settings()` and the `Firestore`
* constructor, both settings objects are merged and any settings provided via
* `settings()` take precedence.
*
* @param {object} settings The settings to use for all Firestore operations.
*/
settings(settings: firestore.Settings): void;
private validateAndApplySettings;
private newTraceUtilInstance;
/**
* Returns the Project ID for this Firestore instance. Validates that
* `initializeIfNeeded()` was called before.
*
* @private
* @internal
*/
get projectId(): string;
/**
* Returns the Database ID for this Firestore instance.
*/
get databaseId(): string;
/**
* Returns the root path of the database. Validates that
* `initializeIfNeeded()` was called before.
*
* @private
* @internal
*/
get formattedName(): string;
/**
* Gets a [DocumentReference]{@link DocumentReference} instance that
* refers to the document at the specified path.
*
* @param {string} documentPath A slash-separated path to a document.
* @returns {DocumentReference} The
* [DocumentReference]{@link DocumentReference} instance.
*
* @example
* ```
* let documentRef = firestore.doc('collection/document');
* console.log(`Path of document is ${documentRef.path}`);
* ```
*/
doc(documentPath: string): DocumentReference;
/**
* Gets a [CollectionReference]{@link CollectionReference} instance
* that refers to the collection at the specified path.
*
* @param {string} collectionPath A slash-separated path to a collection.
* @returns {CollectionReference} The
* [CollectionReference]{@link CollectionReference} instance.
*
* @example
* ```
* let collectionRef = firestore.collection('collection');
*
* // Add a document with an auto-generated ID.
* collectionRef.add({foo: 'bar'}).then((documentRef) => {
* console.log(`Added document at ${documentRef.path})`);
* });
* ```
*/
collection(collectionPath: string): CollectionReference;
/**
* Creates and returns a new Query that includes all documents in the
* database that are contained in a collection or subcollection with the
* given collectionId.
*
* @param {string} collectionId Identifies the collections to query over.
* Every collection or subcollection with this ID as the last segment of its
* path will be included. Cannot contain a slash.
* @returns {CollectionGroup} The created CollectionGroup.
*
* @example
* ```
* let docA = firestore.doc('mygroup/docA').set({foo: 'bar'});
* let docB = firestore.doc('abc/def/mygroup/docB').set({foo: 'bar'});
*
* Promise.all([docA, docB]).then(() => {
* let query = firestore.collectionGroup('mygroup');
* query = query.where('foo', '==', 'bar');
* return query.get().then(snapshot => {
* console.log(`Found ${snapshot.size} documents.`);
* });
* });
* ```
*/
collectionGroup(collectionId: string): CollectionGroup;
/**
* Creates a [WriteBatch]{@link WriteBatch}, used for performing
* multiple writes as a single atomic operation.
*
* @returns {WriteBatch} A WriteBatch that operates on this Firestore
* client.
*
* @example
* ```
* let writeBatch = firestore.batch();
*
* // Add two documents in an atomic batch.
* let data = { foo: 'bar' };
* writeBatch.set(firestore.doc('col/doc1'), data);
* writeBatch.set(firestore.doc('col/doc2'), data);
*
* writeBatch.commit().then(res => {
* console.log('Successfully executed batch.');
* });
* ```
*/
batch(): WriteBatch;
/**
* Creates a [BulkWriter]{@link BulkWriter}, used for performing
* multiple writes in parallel. Gradually ramps up writes as specified
* by the 500/50/5 rule.
*
* If you pass [BulkWriterOptions]{@link BulkWriterOptions}, you can
* configure the throttling rates for the created BulkWriter.
*
* @see [500/50/5 Documentation]{@link https://firebase.google.com/docs/firestore/best-practices#ramping_up_traffic}
*
* @param {BulkWriterOptions=} options BulkWriter options.
* @returns {BulkWriter} A BulkWriter that operates on this Firestore
* client.
*
* @example
* ```
* let bulkWriter = firestore.bulkWriter();
*
* bulkWriter.create(firestore.doc('col/doc1'), {foo: 'bar'})
* .then(res => {
* console.log(`Added document at ${res.writeTime}`);
* });
* bulkWriter.update(firestore.doc('col/doc2'), {foo: 'bar'})
* .then(res => {
* console.log(`Updated document at ${res.writeTime}`);
* });
* bulkWriter.delete(firestore.doc('col/doc3'))
* .then(res => {
* console.log(`Deleted document at ${res.writeTime}`);
* });
* await bulkWriter.close().then(() => {
* console.log('Executed all writes');
* });
* ```
*/
bulkWriter(options?: firestore.BulkWriterOptions): BulkWriter;
/**
* Creates a [DocumentSnapshot]{@link DocumentSnapshot} or a
* [QueryDocumentSnapshot]{@link QueryDocumentSnapshot} from a
* `firestore.v1.Document` proto (or from a resource name for missing
* documents).
*
* This API is used by Google Cloud Functions and can be called with both
* 'Proto3 JSON' and 'Protobuf JS' encoded data.
*
* @private
* @param documentOrName The Firestore 'Document' proto or the resource name
* of a missing document.
* @param readTime A 'Timestamp' proto indicating the time this document was
* read.
* @param encoding One of 'json' or 'protobufJS'. Applies to both the
* 'document' Proto and 'readTime'. Defaults to 'protobufJS'.
* @returns A QueryDocumentSnapshot for existing documents, otherwise a
* DocumentSnapshot.
*/
snapshot_(documentName: string, readTime?: google.protobuf.ITimestamp, encoding?: 'protobufJS'): DocumentSnapshot;
/** @private */
snapshot_(documentName: string, readTime: string, encoding: 'json'): DocumentSnapshot;
/** @private */
snapshot_(document: api.IDocument, readTime: google.protobuf.ITimestamp, encoding?: 'protobufJS'): QueryDocumentSnapshot;
/** @private */
snapshot_(document: {
[k: string]: unknown;
}, readTime: string, encoding: 'json'): QueryDocumentSnapshot;
/**
* Creates a new `BundleBuilder` instance to package selected Firestore data into
* a bundle.
*
* @param bundleId. The id of the bundle. When loaded on clients, client SDKs use this id
* and the timestamp associated with the built bundle to tell if it has been loaded already.
* If not specified, a random identifier will be used.
*/
bundle(name?: string): BundleBuilder;
/**
* Function executed by {@link Firestore#runTransaction} within the transaction
* context.
*
* @callback Firestore~updateFunction
* @template T
* @param {Transaction} transaction The transaction object for this
* transaction.
* @returns {Promise<T>} The promise returned at the end of the transaction.
* This promise will be returned by {@link Firestore#runTransaction} if the
* transaction completed successfully.
*/
/**
* Options object for {@link Firestore#runTransaction} to configure a
* read-only transaction.
*
* @param {true} readOnly Set to true to indicate a read-only transaction.
* @param {Timestamp=} readTime If specified, documents are read at the given
* time. This may not be more than 60 seconds in the past from when the
* request is processed by the server.
* @typedef {Object} Firestore~ReadOnlyTransactionOptions
*/
/**
* Options object for {@link Firestore#runTransaction} to configure a
* read-write transaction.
*
* @param {false=} readOnly Set to false or omit to indicate a read-write
* transaction.
* @param {number=} maxAttempts The maximum number of attempts for this
* transaction. Defaults to 5.
* @typedef {Object} Firestore~ReadWriteTransactionOptions
*/
/**
* Executes the given updateFunction and commits the changes applied within
* the transaction.
*
* You can use the transaction object passed to 'updateFunction' to read and
* modify Firestore documents under lock. You have to perform all reads before
* before you perform any write.
*
* Transactions can be performed as read-only or read-write transactions. By
* default, transactions are executed in read-write mode.
*
* A read-write transaction obtains a pessimistic lock on all documents that
* are read during the transaction. These locks block other transactions,
* batched writes, and other non-transactional writes from changing that
* document. Any writes in a read-write transactions are committed once
* 'updateFunction' resolves, which also releases all locks.
*
* If a read-write transaction fails with contention, the transaction is
* retried up to five times. The `updateFunction` is invoked once for each
* attempt.
*
* Read-only transactions do not lock documents. They can be used to read
* documents at a consistent snapshot in time, which may be up to 60 seconds
* in the past. Read-only transactions are not retried.
*
* Transactions time out after 60 seconds if no documents are read.
* Transactions that are not committed within than 270 seconds are also
* aborted. Any remaining locks are released when a transaction times out.
*
* @template T
* @param {Firestore~updateFunction} updateFunction The user function to
* execute within the transaction context.
* @param {
* Firestore~ReadWriteTransactionOptions|Firestore~ReadOnlyTransactionOptions=
* } transactionOptions Transaction options.
* @returns {Promise<T>} If the transaction completed successfully or was
* explicitly aborted (by the updateFunction returning a failed Promise), the
* Promise returned by the updateFunction will be returned here. Else if the
* transaction failed, a rejected Promise with the corresponding failure
* error will be returned.
*
* @example
* ```
* let counterTransaction = firestore.runTransaction(transaction => {
* let documentRef = firestore.doc('col/doc');
* return transaction.get(documentRef).then(doc => {
* if (doc.exists) {
* let count = doc.get('count') || 0;
* if (count > 10) {
* return Promise.reject('Reached maximum count');
* }
* transaction.update(documentRef, { count: ++count });
* return Promise.resolve(count);
* }
*
* transaction.create(documentRef, { count: 1 });
* return Promise.resolve(1);
* });
* });
*
* counterTransaction.then(res => {
* console.log(`Count updated to ${res}`);
* });
* ```
*/
runTransaction<T>(updateFunction: (transaction: Transaction) => Promise<T>, transactionOptions?: firestore.ReadWriteTransactionOptions | firestore.ReadOnlyTransactionOptions): Promise<T>;
/**
* Fetches the root collections that are associated with this Firestore
* database.
*
* @returns {Promise.<Array.<CollectionReference>>} A Promise that resolves
* with an array of CollectionReferences.
*
* @example
* ```
* firestore.listCollections().then(collections => {
* for (let collection of collections) {
* console.log(`Found collection with id: ${collection.id}`);
* }
* });
* ```
*/
listCollections(): Promise<CollectionReference[]>;
/**
* Retrieves multiple documents from Firestore.
*
* The first argument is required and must be of type `DocumentReference`
* followed by any additional `DocumentReference` documents. If used, the
* optional `ReadOptions` must be the last argument.
*
* @param {...DocumentReference|ReadOptions} documentRefsOrReadOptions The
* `DocumentReferences` to receive, followed by an optional field mask.
* @returns {Promise<Array.<DocumentSnapshot>>} A Promise that
* contains an array with the resulting document snapshots.
*
* @example
* ```
* let docRef1 = firestore.doc('col/doc1');
* let docRef2 = firestore.doc('col/doc2');
*
* firestore.getAll(docRef1, docRef2, { fieldMask: ['user'] }).then(docs => {
* console.log(`First document: ${JSON.stringify(docs[0])}`);
* console.log(`Second document: ${JSON.stringify(docs[1])}`);
* });
* ```
*/
getAll<AppModelType, DbModelType extends firestore.DocumentData>(...documentRefsOrReadOptions: Array<firestore.DocumentReference<AppModelType, DbModelType> | firestore.ReadOptions>): Promise<Array<DocumentSnapshot<AppModelType, DbModelType>>>;
/**
* Registers a listener on this client, incrementing the listener count. This
* is used to verify that all listeners are unsubscribed when terminate() is
* called.
*
* @private
* @internal
*/
registerListener(): void;
/**
* Unregisters a listener on this client, decrementing the listener count.
* This is used to verify that all listeners are unsubscribed when terminate()
* is called.
*
* @private
* @internal
*/
unregisterListener(): void;
/**
* Increments the number of open BulkWriter instances. This is used to verify
* that all pending operations are complete when terminate() is called.
*
* @private
* @internal
*/
_incrementBulkWritersCount(): void;
/**
* Decrements the number of open BulkWriter instances. This is used to verify
* that all pending operations are complete when terminate() is called.
*
* @private
* @internal
*/
_decrementBulkWritersCount(): void;
/**
* Recursively deletes all documents and subcollections at and under the
* specified level.
*
* If any delete fails, the promise is rejected with an error message
* containing the number of failed deletes and the stack trace of the last
* failed delete. The provided reference is deleted regardless of whether
* all deletes succeeded.
*
* `recursiveDelete()` uses a BulkWriter instance with default settings to
* perform the deletes. To customize throttling rates or add success/error
* callbacks, pass in a custom BulkWriter instance.
*
* @param ref The reference of a document or collection to delete.
* @param bulkWriter A custom BulkWriter instance used to perform the
* deletes.
* @return A promise that resolves when all deletes have been performed.
* The promise is rejected if any of the deletes fail.
*
* @example
* ```
* // Recursively delete a reference and log the references of failures.
* const bulkWriter = firestore.bulkWriter();
* bulkWriter
* .onWriteError((error) => {
* if (
* error.failedAttempts < MAX_RETRY_ATTEMPTS
* ) {
* return true;
* } else {
* console.log('Failed write at document: ', error.documentRef.path);
* return false;
* }
* });
* await firestore.recursiveDelete(docRef, bulkWriter);
* ```
*/
recursiveDelete(ref: firestore.CollectionReference<any, any> | firestore.DocumentReference<any, any>, bulkWriter?: BulkWriter): Promise<void>;
/**
* This overload is not private in order to test the query resumption with
* startAfter() once the RecursiveDelete instance has MAX_PENDING_OPS pending.
*
* @private
* @internal
*/
_recursiveDelete(ref: firestore.CollectionReference<unknown> | firestore.DocumentReference<unknown>, maxPendingOps: number, minPendingOps: number, bulkWriter?: BulkWriter): Promise<void>;
/**
* Terminates the Firestore client and closes all open streams.
*
* @return A Promise that resolves when the client is terminated.
*/
terminate(): Promise<void>;
/**
* Returns the Project ID to serve as the JSON representation of this
* Firestore instance.
*
* @return An object that contains the project ID (or `undefined` if not yet
* available).
*/
toJSON(): object;
/**
* Initializes the client if it is not already initialized. All methods in the
* SDK can be used after this method completes.
*
* @private
* @internal
* @param requestTag A unique client-assigned identifier that caused this
* initialization.
* @return A Promise that resolves when the client is initialized.
*/
initializeIfNeeded(requestTag: string): Promise<void>;
/**
* Returns GAX call options that set the cloud resource header.
* @private
* @internal
*/
private createCallOptions;
/**
* A function returning a Promise that can be retried.
*
* @private
* @internal
* @callback retryFunction
* @returns {Promise} A Promise indicating the function's success.
*/
/**
* Helper method that retries failed Promises.
*
* If 'delayMs' is specified, waits 'delayMs' between invocations. Otherwise,
* schedules the first attempt immediately, and then waits 100 milliseconds
* for further attempts.
*
* @private
* @internal
* @param methodName Name of the Veneer API endpoint that takes a request
* and GAX options.
* @param requestTag A unique client-assigned identifier for this request.
* @param func Method returning a Promise than can be retried.
* @returns A Promise with the function's result if successful within
* `attemptsRemaining`. Otherwise, returns the last rejected Promise.
*/
private _retry;
/**
* Waits for the provided stream to become active and returns a paused but
* healthy stream. If an error occurs before the first byte is read, the
* method rejects the returned Promise.
*
* @private
* @internal
* @param backendStream The Node stream to monitor.
* @param lifetime A Promise that resolves when the stream receives an 'end',
* 'close' or 'finish' message.
* @param requestTag A unique client-assigned identifier for this request.
* @param request If specified, the request that should be written to the
* stream after opening.
* @returns A guaranteed healthy stream that should be used instead of
* `backendStream`.
*/
private _initializeStream;
/**
* A funnel for all non-streaming API requests, assigning a project ID where
* necessary within the request options.
*
* @private
* @internal
* @param methodName Name of the Veneer API endpoint that takes a request
* and GAX options.
* @param request The Protobuf request to send.
* @param requestTag A unique client-assigned identifier for this request.
* @param retryCodes If provided, a custom list of retry codes. If not
* provided, retry is based on the behavior as defined in the ServiceConfig.
* @returns A Promise with the request result.
*/
request<Req, Resp>(methodName: FirestoreUnaryMethod, request: Req, requestTag: string, retryCodes?: number[]): Promise<Resp>;
/**
* A funnel for streaming API requests, assigning a project ID where necessary
* within the request options.
*
* The stream is returned in paused state and needs to be resumed once all
* listeners are attached.
*
* @private
* @internal
* @param methodName Name of the streaming Veneer API endpoint that
* takes a request and GAX options.
* @param bidrectional Whether the request is bidirectional (true) or
* unidirectional (false_
* @param request The Protobuf request to send.
* @param requestTag A unique client-assigned identifier for this request.
* @returns A Promise with the resulting read-only stream.
*/
requestStream(methodName: FirestoreStreamingMethod, bidrectional: boolean, request: {}, requestTag: string): Promise<Duplex>;
}
/**
* A logging function that takes a single string.
*
* @callback Firestore~logFunction
* @param {string} Log message
*/
/**
* The default export of the `@google-cloud/firestore` package is the
* {@link Firestore} class.
*
* See {@link Firestore} and {@link ClientConfig} for client methods and
* configuration options.
*
* @module {Firestore} @google-cloud/firestore
* @alias nodejs-firestore
*
* @example Install the client library with <a href="https://www.npmjs.com/">npm</a>:
* ```
* npm install --save @google-cloud/firestore
*
* ```
* @example Import the client library
* ```
* var Firestore = require('@google-cloud/firestore');
*
* ```
* @example Create a client that uses <a href="https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application">Application Default Credentials (ADC)</a>:
* ```
* var firestore = new Firestore();
*
* ```
* @example Create a client with <a href="https://cloud.google.com/docs/authentication/production#obtaining_and_providing_service_account_credentials_manually">explicit credentials</a>:
* ```
* var firestore = new Firestore({ projectId:
* 'your-project-id', keyFilename: '/path/to/keyfile.json'
* });
*
* ```
* @example <caption>include:samples/quickstart.js</caption>
* region_tag:firestore_quickstart
* Full quickstart example:
*/
export default Firestore;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
/**
* Log function to use for debug output. By default, we don't perform any
* logging.
*
* @private
* @internal
*/
export declare function logger(methodName: string, requestTag: string | null, logMessage: string, ...additionalArgs: unknown[]): void;
/**
* Sets or disables the log function for all active Firestore instances.
*
* @param logger A log function that takes a message (such as `console.log`) or
* `null` to turn off logging.
*/
export declare function setLogFunction(logger: ((msg: string) => void) | null): void;
/**
* Sets the library version to be used in log messages.
*
* @private
* @internal
*/
export declare function setLibVersion(version: string): void;

View File

@@ -0,0 +1,63 @@
"use strict";
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.logger = logger;
exports.setLogFunction = setLogFunction;
exports.setLibVersion = setLibVersion;
const util = require("util");
const validate_1 = require("./validate");
/*! The Firestore library version */
let libVersion;
/*! The external function used to emit logs. */
let logFunction = null;
/**
* Log function to use for debug output. By default, we don't perform any
* logging.
*
* @private
* @internal
*/
function logger(methodName, requestTag, logMessage, ...additionalArgs) {
requestTag = requestTag || '#####';
if (logFunction) {
const formattedMessage = util.format(logMessage, ...additionalArgs);
const time = new Date().toISOString();
logFunction(`Firestore (${libVersion}) ${time} ${requestTag} [${methodName}]: ` +
formattedMessage);
}
}
/**
* Sets or disables the log function for all active Firestore instances.
*
* @param logger A log function that takes a message (such as `console.log`) or
* `null` to turn off logging.
*/
function setLogFunction(logger) {
if (logger !== null)
(0, validate_1.validateFunction)('logger', logger);
logFunction = logger;
}
/**
* Sets the library version to be used in log messages.
*
* @private
* @internal
*/
function setLibVersion(version) {
libVersion = version;
}
//# sourceMappingURL=logger.js.map

View File

@@ -0,0 +1,18 @@
/*!
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
export declare const RESERVED_MAP_KEY = "__type__";
export declare const RESERVED_MAP_KEY_VECTOR_VALUE = "__vector__";
export declare const VECTOR_MAP_VECTORS_KEY = "value";

View File

@@ -0,0 +1,22 @@
"use strict";
/*!
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.VECTOR_MAP_VECTORS_KEY = exports.RESERVED_MAP_KEY_VECTOR_VALUE = exports.RESERVED_MAP_KEY = void 0;
exports.RESERVED_MAP_KEY = '__type__';
exports.RESERVED_MAP_KEY_VECTOR_VALUE = '__vector__';
exports.VECTOR_MAP_VECTORS_KEY = 'value';
//# sourceMappingURL=map-type.js.map

View File

@@ -0,0 +1,38 @@
/*!
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.
*/
import { google } from '../protos/firestore_v1_proto_api';
import api = google.firestore.v1;
/*!
* @private
* @internal
*/
export declare function primitiveComparator(left: string | boolean | number, right: string | boolean | number): number;
/*!
* @private
* @internal
*/
export declare function compareArrays(left: api.IValue[], right: api.IValue[]): number;
/*!
* Compare strings in UTF-8 encoded byte order
* @private
* @internal
*/
export declare function compareUtf8Strings(left: string, right: string): number;
/*!
* @private
* @internal
*/
export declare function compare(left: api.IValue, right: api.IValue): number;

View File

@@ -0,0 +1,326 @@
"use strict";
/*!
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.primitiveComparator = primitiveComparator;
exports.compareArrays = compareArrays;
exports.compareUtf8Strings = compareUtf8Strings;
exports.compare = compare;
const convert_1 = require("./convert");
const path_1 = require("./path");
/*!
* The type order as defined by the backend.
*/
var TypeOrder;
(function (TypeOrder) {
TypeOrder[TypeOrder["NULL"] = 0] = "NULL";
TypeOrder[TypeOrder["BOOLEAN"] = 1] = "BOOLEAN";
TypeOrder[TypeOrder["NUMBER"] = 2] = "NUMBER";
TypeOrder[TypeOrder["TIMESTAMP"] = 3] = "TIMESTAMP";
TypeOrder[TypeOrder["STRING"] = 4] = "STRING";
TypeOrder[TypeOrder["BLOB"] = 5] = "BLOB";
TypeOrder[TypeOrder["REF"] = 6] = "REF";
TypeOrder[TypeOrder["GEO_POINT"] = 7] = "GEO_POINT";
TypeOrder[TypeOrder["ARRAY"] = 8] = "ARRAY";
TypeOrder[TypeOrder["VECTOR"] = 9] = "VECTOR";
TypeOrder[TypeOrder["OBJECT"] = 10] = "OBJECT";
})(TypeOrder || (TypeOrder = {}));
/*!
* @private
* @internal
*/
function typeOrder(val) {
const valueType = (0, convert_1.detectValueType)(val);
switch (valueType) {
case 'nullValue':
return TypeOrder.NULL;
case 'integerValue':
return TypeOrder.NUMBER;
case 'doubleValue':
return TypeOrder.NUMBER;
case 'stringValue':
return TypeOrder.STRING;
case 'booleanValue':
return TypeOrder.BOOLEAN;
case 'arrayValue':
return TypeOrder.ARRAY;
case 'timestampValue':
return TypeOrder.TIMESTAMP;
case 'geoPointValue':
return TypeOrder.GEO_POINT;
case 'bytesValue':
return TypeOrder.BLOB;
case 'referenceValue':
return TypeOrder.REF;
case 'mapValue':
return TypeOrder.OBJECT;
case 'vectorValue':
return TypeOrder.VECTOR;
default:
throw new Error('Unexpected value type: ' + valueType);
}
}
/*!
* @private
* @internal
*/
function primitiveComparator(left, right) {
if (left < right) {
return -1;
}
if (left > right) {
return 1;
}
return 0;
}
/*!
* Utility function to compare doubles (using Firestore semantics for NaN).
* @private
* @internal
*/
function compareNumbers(left, right) {
if (left < right) {
return -1;
}
if (left > right) {
return 1;
}
if (left === right) {
return 0;
}
// one or both are NaN.
if (isNaN(left)) {
return isNaN(right) ? 0 : -1;
}
return 1;
}
/*!
* @private
* @internal
*/
function compareNumberProtos(left, right) {
let leftValue, rightValue;
if (left.integerValue !== undefined) {
leftValue = Number(left.integerValue);
}
else {
leftValue = Number(left.doubleValue);
}
if (right.integerValue !== undefined) {
rightValue = Number(right.integerValue);
}
else {
rightValue = Number(right.doubleValue);
}
return compareNumbers(leftValue, rightValue);
}
/*!
* @private
* @internal
*/
function compareTimestamps(left, right) {
const seconds = primitiveComparator(left.seconds || 0, right.seconds || 0);
if (seconds !== 0) {
return seconds;
}
return primitiveComparator(left.nanos || 0, right.nanos || 0);
}
/*!
* @private
* @internal
*/
function compareBlobs(left, right) {
if (!(left instanceof Buffer) || !(right instanceof Buffer)) {
throw new Error('Blobs can only be compared if they are Buffers.');
}
return Buffer.compare(left, right);
}
/*!
* @private
* @internal
*/
function compareReferenceProtos(left, right) {
const leftPath = path_1.QualifiedResourcePath.fromSlashSeparatedString(left.referenceValue);
const rightPath = path_1.QualifiedResourcePath.fromSlashSeparatedString(right.referenceValue);
return leftPath.compareTo(rightPath);
}
/*!
* @private
* @internal
*/
function compareGeoPoints(left, right) {
return (primitiveComparator(left.latitude || 0, right.latitude || 0) ||
primitiveComparator(left.longitude || 0, right.longitude || 0));
}
/*!
* @private
* @internal
*/
function compareArrays(left, right) {
for (let i = 0; i < left.length && i < right.length; i++) {
const valueComparison = compare(left[i], right[i]);
if (valueComparison !== 0) {
return valueComparison;
}
}
// If all the values matched so far, just check the length.
return primitiveComparator(left.length, right.length);
}
/*!
* @private
* @internal
*/
function compareObjects(left, right) {
// This requires iterating over the keys in the object in order and doing a
// deep comparison.
const leftKeys = Object.keys(left);
const rightKeys = Object.keys(right);
leftKeys.sort();
rightKeys.sort();
for (let i = 0; i < leftKeys.length && i < rightKeys.length; i++) {
const keyComparison = compareUtf8Strings(leftKeys[i], rightKeys[i]);
if (keyComparison !== 0) {
return keyComparison;
}
const key = leftKeys[i];
const valueComparison = compare(left[key], right[key]);
if (valueComparison !== 0) {
return valueComparison;
}
}
// If all the keys matched so far, just check the length.
return primitiveComparator(leftKeys.length, rightKeys.length);
}
/*!
* @private
* @internal
*/
function compareVectors(left, right) {
var _a, _b, _c, _d, _e, _f;
// The vector is a map, but only vector value is compared.
const leftArray = (_c = (_b = (_a = left === null || left === void 0 ? void 0 : left['value']) === null || _a === void 0 ? void 0 : _a.arrayValue) === null || _b === void 0 ? void 0 : _b.values) !== null && _c !== void 0 ? _c : [];
const rightArray = (_f = (_e = (_d = right === null || right === void 0 ? void 0 : right['value']) === null || _d === void 0 ? void 0 : _d.arrayValue) === null || _e === void 0 ? void 0 : _e.values) !== null && _f !== void 0 ? _f : [];
const lengthCompare = primitiveComparator(leftArray.length, rightArray.length);
if (lengthCompare !== 0) {
return lengthCompare;
}
return compareArrays(leftArray, rightArray);
}
/*!
* Compare strings in UTF-8 encoded byte order
* @private
* @internal
*/
function compareUtf8Strings(left, right) {
// Find the first differing character (a.k.a. "UTF-16 code unit") in the two strings and,
// if found, use that character to determine the relative ordering of the two strings as a
// whole. Comparing UTF-16 strings in UTF-8 byte order can be done simply and efficiently by
// comparing the UTF-16 code units (chars). This serendipitously works because of the way UTF-8
// and UTF-16 happen to represent Unicode code points.
//
// After finding the first pair of differing characters, there are two cases:
//
// Case 1: Both characters are non-surrogates (code points less than or equal to 0xFFFF) or
// both are surrogates from a surrogate pair (that collectively represent code points greater
// than 0xFFFF). In this case their numeric order as UTF-16 code units is the same as the
// lexicographical order of their corresponding UTF-8 byte sequences. A direct comparison is
// sufficient.
//
// Case 2: One character is a surrogate and the other is not. In this case the surrogate-
// containing string is always ordered after the non-surrogate. This is because surrogates are
// used to represent code points greater than 0xFFFF which have 4-byte UTF-8 representations
// and are lexicographically greater than the 1, 2, or 3-byte representations of code points
// less than or equal to 0xFFFF.
//
// An example of why Case 2 is required is comparing the following two Unicode code points:
//
// |-----------------------|------------|---------------------|-----------------|
// | Name | Code Point | UTF-8 Encoding | UTF-16 Encoding |
// |-----------------------|------------|---------------------|-----------------|
// | Replacement Character | U+FFFD | 0xEF 0xBF 0xBD | 0xFFFD |
// | Grinning Face | U+1F600 | 0xF0 0x9F 0x98 0x80 | 0xD83D 0xDE00 |
// |-----------------------|------------|---------------------|-----------------|
//
// A lexicographical comparison of the UTF-8 encodings of these code points would order
// "Replacement Character" _before_ "Grinning Face" because 0xEF is less than 0xF0. However, a
// direct comparison of the UTF-16 code units, as would be done in case 1, would erroneously
// produce the _opposite_ ordering, because 0xFFFD is _greater than_ 0xD83D. As it turns out,
// this relative ordering holds for all comparisons of UTF-16 code points requiring a surrogate
// pair with those that do not.
const length = Math.min(left.length, right.length);
for (let i = 0; i < length; i++) {
const leftChar = left.charAt(i);
const rightChar = right.charAt(i);
if (leftChar !== rightChar) {
return isSurrogate(leftChar) === isSurrogate(rightChar)
? primitiveComparator(leftChar, rightChar)
: isSurrogate(leftChar)
? 1
: -1;
}
}
// Use the lengths of the strings to determine the overall comparison result since either the
// strings were equal or one is a prefix of the other.
return primitiveComparator(left.length, right.length);
}
const MIN_SURROGATE = 0xd800;
const MAX_SURROGATE = 0xdfff;
function isSurrogate(s) {
const c = s.charCodeAt(0);
return c >= MIN_SURROGATE && c <= MAX_SURROGATE;
}
/*!
* @private
* @internal
*/
function compare(left, right) {
// First compare the types.
const leftType = typeOrder(left);
const rightType = typeOrder(right);
const typeComparison = primitiveComparator(leftType, rightType);
if (typeComparison !== 0) {
return typeComparison;
}
// So they are the same type.
switch (leftType) {
case TypeOrder.NULL:
// Nulls are all equal.
return 0;
case TypeOrder.BOOLEAN:
return primitiveComparator(left.booleanValue, right.booleanValue);
case TypeOrder.STRING:
return compareUtf8Strings(left.stringValue, right.stringValue);
case TypeOrder.NUMBER:
return compareNumberProtos(left, right);
case TypeOrder.TIMESTAMP:
return compareTimestamps(left.timestampValue, right.timestampValue);
case TypeOrder.BLOB:
return compareBlobs(left.bytesValue, right.bytesValue);
case TypeOrder.REF:
return compareReferenceProtos(left, right);
case TypeOrder.GEO_POINT:
return compareGeoPoints(left.geoPointValue, right.geoPointValue);
case TypeOrder.ARRAY:
return compareArrays(left.arrayValue.values || [], right.arrayValue.values || []);
case TypeOrder.OBJECT:
return compareObjects(left.mapValue.fields || {}, right.mapValue.fields || {});
case TypeOrder.VECTOR:
return compareVectors(left.mapValue.fields || {}, right.mapValue.fields || {});
default:
throw new Error(`Encountered unknown type order: ${leftType}`);
}
}
//# sourceMappingURL=order.js.map

View File

@@ -0,0 +1,416 @@
/*!
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import { google } from '../protos/firestore_v1_proto_api';
import api = google.firestore.v1;
/*!
* The default database ID for this Firestore client. We do not yet expose the
* ability to use different databases.
*/
export declare const DEFAULT_DATABASE_ID = "(default)";
/**
* An abstract class representing a Firestore path.
*
* Subclasses have to implement `split()` and `canonicalString()`.
*
* @private
* @internal
* @class
*/
declare abstract class Path<T> {
protected readonly segments: string[];
/**
* Creates a new Path with the given segments.
*
* @private
* @internal
* @private
* @param segments Sequence of parts of a path.
*/
constructor(segments: string[]);
/**
* Returns the number of segments of this field path.
*
* @private
* @internal
*/
get size(): number;
abstract construct(segments: string[] | string): T;
abstract split(relativePath: string): string[];
/**
* Create a child path beneath the current level.
*
* @private
* @internal
* @param relativePath Relative path to append to the current path.
* @returns The new path.
*/
append(relativePath: Path<T> | string): T;
/**
* Returns the path of the parent node.
*
* @private
* @internal
* @returns The new path or null if we are already at the root.
*/
parent(): T | null;
/**
* Checks whether the current path is a prefix of the specified path.
*
* @private
* @internal
* @param other The path to check against.
* @returns 'true' iff the current path is a prefix match with 'other'.
*/
isPrefixOf(other: Path<T>): boolean;
/**
* Compare the current path against another Path object.
*
* Compare the current path against another Path object. Paths are compared segment by segment,
* prioritizing numeric IDs (e.g., "__id123__") in numeric ascending order, followed by string
* segments in lexicographical order.
*
* @private
* @internal
* @param other The path to compare to.
* @returns -1 if current < other, 1 if current > other, 0 if equal
*/
compareTo(other: Path<T>): number;
private compareSegments;
private isNumericId;
private extractNumericId;
private compareNumbers;
/**
* Returns a copy of the underlying segments.
*
* @private
* @internal
* @returns A copy of the segments that make up this path.
*/
toArray(): string[];
/**
* Pops the last segment from this `Path` and returns a newly constructed
* `Path`.
*
* @private
* @internal
* @returns The newly created Path.
*/
popLast(): T;
/**
* Returns true if this `Path` is equal to the provided value.
*
* @private
* @internal
* @param other The value to compare against.
* @return true if this `Path` is equal to the provided value.
*/
isEqual(other: Path<T>): boolean;
}
/**
* A slash-separated path for navigating resources within the current Firestore
* instance.
*
* @private
* @internal
*/
export declare class ResourcePath extends Path<ResourcePath> {
/**
* A default instance pointing to the root collection.
* @private
* @internal
*/
static EMPTY: ResourcePath;
/**
* Constructs a ResourcePath.
*
* @private
* @internal
* @param segments Sequence of names of the parts of the path.
*/
constructor(...segments: string[]);
/**
* Indicates whether this path points to a document.
* @private
* @internal
*/
get isDocument(): boolean;
/**
* Indicates whether this path points to a collection.
* @private
* @internal
*/
get isCollection(): boolean;
/**
* The last component of the path.
* @private
* @internal
*/
get id(): string | null;
/**
* Returns the location of this path relative to the root of the project's
* database.
* @private
* @internal
*/
get relativeName(): string;
/**
* Constructs a new instance of ResourcePath.
*
* @private
* @internal
* @param segments Sequence of parts of the path.
* @returns The newly created ResourcePath.
*/
construct(segments: string[]): ResourcePath;
/**
* Splits a string into path segments, using slashes as separators.
*
* @private
* @internal
* @param relativePath The path to split.
* @returns The split path segments.
*/
split(relativePath: string): string[];
/**
* Converts this path to a fully qualified ResourcePath.
*
* @private
* @internal
* @param projectId The project ID of the current Firestore project.
* @return A fully-qualified resource path pointing to the same element.
*/
toQualifiedResourcePath(projectId: string, databaseId: string): QualifiedResourcePath;
}
/**
* A slash-separated path that includes a project and database ID for referring
* to resources in any Firestore project.
*
* @private
* @internal
*/
export declare class QualifiedResourcePath extends ResourcePath {
/**
* The project ID of this path.
*/
readonly projectId: string;
/**
* The database ID of this path.
*/
readonly databaseId: string;
/**
* Constructs a Firestore Resource Path.
*
* @private
* @internal
* @param projectId The Firestore project id.
* @param databaseId The Firestore database id.
* @param segments Sequence of names of the parts of the path.
*/
constructor(projectId: string, databaseId: string, ...segments: string[]);
/**
* String representation of the path relative to the database root.
* @private
* @internal
*/
get relativeName(): string;
/**
* Creates a resource path from an absolute Firestore path.
*
* @private
* @internal
* @param absolutePath A string representation of a Resource Path.
* @returns The new ResourcePath.
*/
static fromSlashSeparatedString(absolutePath: string): QualifiedResourcePath;
/**
* Create a child path beneath the current level.
*
* @private
* @internal
* @param relativePath Relative path to append to the current path.
* @returns The new path.
*/
append(relativePath: ResourcePath | string): QualifiedResourcePath;
/**
* Create a child path beneath the current level.
*
* @private
* @internal
* @returns The new path.
*/
parent(): QualifiedResourcePath | null;
/**
* String representation of a ResourcePath as expected by the API.
*
* @private
* @internal
* @returns The representation as expected by the API.
*/
get formattedName(): string;
/**
* Constructs a new instance of ResourcePath. We need this instead of using
* the normal constructor because polymorphic 'this' doesn't work on static
* methods.
*
* @private
* @internal
* @param segments Sequence of names of the parts of the path.
* @returns The newly created QualifiedResourcePath.
*/
construct(segments: string[]): QualifiedResourcePath;
/**
* Convenience method to match the ResourcePath API. This method always
* returns the current instance.
*
* @private
* @internal
*/
toQualifiedResourcePath(): QualifiedResourcePath;
/**
* Compare the current path against another ResourcePath object.
*
* @private
* @internal
* @param other The path to compare to.
* @returns -1 if current < other, 1 if current > other, 0 if equal
*/
compareTo(other: ResourcePath): number;
/**
* Converts this ResourcePath to the Firestore Proto representation.
* @private
* @internal
*/
toProto(): api.IValue;
}
/**
* Validates that the given string can be used as a relative or absolute
* resource path.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param resourcePath The path to validate.
* @throws if the string can't be used as a resource path.
*/
export declare function validateResourcePath(arg: string | number, resourcePath: string): void;
/**
* A dot-separated path for navigating sub-objects (e.g. nested maps) within a document.
*
* @class
*/
export declare class FieldPath extends Path<FieldPath> implements firestore.FieldPath {
/**
* A special sentinel value to refer to the ID of a document.
*
* @private
* @internal
*/
private static _DOCUMENT_ID;
/**
* Constructs a Firestore Field Path.
*
* @param {...string} segments Sequence of field names that form this path.
*
* @example
* ```
* let query = firestore.collection('col');
* let fieldPath = new FieldPath('f.o.o', 'bar');
*
* query.where(fieldPath, '==', 42).get().then(snapshot => {
* snapshot.forEach(document => {
* console.log(`Document contains {'f.o.o' : {'bar' : 42}}`);
* });
* });
* ```
*/
constructor(...segments: string[]);
/**
* A special FieldPath value to refer to the ID of a document. It can be used
* in queries to sort or filter by the document ID.
*
* @returns {FieldPath}
*/
static documentId(): FieldPath;
/**
* Turns a field path argument into a [FieldPath]{@link FieldPath}.
* Supports FieldPaths as input (which are passed through) and dot-separated
* strings.
*
* @private
* @internal
* @param {string|FieldPath} fieldPath The FieldPath to create.
* @returns {FieldPath} A field path representation.
*/
static fromArgument(fieldPath: string | firestore.FieldPath): FieldPath;
/**
* String representation of a FieldPath as expected by the API.
*
* @private
* @internal
* @override
* @returns {string} The representation as expected by the API.
*/
get formattedName(): string;
/**
* Returns a string representation of this path.
*
* @private
* @internal
* @returns A string representing this path.
*/
toString(): string;
/**
* Splits a string into path segments, using dots as separators.
*
* @private
* @internal
* @override
* @param {string} fieldPath The path to split.
* @returns {Array.<string>} - The split path segments.
*/
split(fieldPath: string): string[];
/**
* Constructs a new instance of FieldPath. We need this instead of using
* the normal constructor because polymorphic 'this' doesn't work on static
* methods.
*
* @private
* @internal
* @override
* @param segments Sequence of field names.
* @returns The newly created FieldPath.
*/
construct(segments: string[]): FieldPath;
/**
* Returns true if this `FieldPath` is equal to the provided value.
*
* @param {*} other The value to compare against.
* @return {boolean} true if this `FieldPath` is equal to the provided value.
*/
isEqual(other: FieldPath): boolean;
}
/**
* Validates that the provided value can be used as a field path argument.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param fieldPath The value to verify.
* @throws if the string can't be used as a field path.
*/
export declare function validateFieldPath(arg: string | number, fieldPath: unknown): asserts fieldPath is string | FieldPath;
export {};

View File

@@ -0,0 +1,656 @@
"use strict";
/*!
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FieldPath = exports.QualifiedResourcePath = exports.ResourcePath = exports.DEFAULT_DATABASE_ID = void 0;
exports.validateResourcePath = validateResourcePath;
exports.validateFieldPath = validateFieldPath;
const order_1 = require("./order");
const util_1 = require("./util");
const validate_1 = require("./validate");
/*!
* The default database ID for this Firestore client. We do not yet expose the
* ability to use different databases.
*/
exports.DEFAULT_DATABASE_ID = '(default)';
/*!
* A regular expression to verify an absolute Resource Path in Firestore. It
* extracts the project ID, the database name and the relative resource path
* if available.
*
* @type {RegExp}
*/
const RESOURCE_PATH_RE =
// Note: [\s\S] matches all characters including newlines.
/^projects\/([^/]*)\/databases\/([^/]*)(?:\/documents\/)?([\s\S]*)$/;
/*!
* A regular expression to verify whether a field name can be passed to the
* backend without escaping.
*
* @type {RegExp}
*/
const UNESCAPED_FIELD_NAME_RE = /^[_a-zA-Z][_a-zA-Z0-9]*$/;
/*!
* A regular expression to verify field paths that are passed to the API as
* strings. Field paths that do not match this expression have to be provided
* as a [FieldPath]{@link FieldPath} object.
*
* @type {RegExp}
*/
const FIELD_PATH_RE = /^[^*~/[\]]+$/;
/**
* An abstract class representing a Firestore path.
*
* Subclasses have to implement `split()` and `canonicalString()`.
*
* @private
* @internal
* @class
*/
class Path {
/**
* Creates a new Path with the given segments.
*
* @private
* @internal
* @private
* @param segments Sequence of parts of a path.
*/
constructor(segments) {
this.segments = segments;
}
/**
* Returns the number of segments of this field path.
*
* @private
* @internal
*/
get size() {
return this.segments.length;
}
/**
* Create a child path beneath the current level.
*
* @private
* @internal
* @param relativePath Relative path to append to the current path.
* @returns The new path.
*/
append(relativePath) {
if (relativePath instanceof Path) {
return this.construct(this.segments.concat(relativePath.segments));
}
return this.construct(this.segments.concat(this.split(relativePath)));
}
/**
* Returns the path of the parent node.
*
* @private
* @internal
* @returns The new path or null if we are already at the root.
*/
parent() {
if (this.segments.length === 0) {
return null;
}
return this.construct(this.segments.slice(0, this.segments.length - 1));
}
/**
* Checks whether the current path is a prefix of the specified path.
*
* @private
* @internal
* @param other The path to check against.
* @returns 'true' iff the current path is a prefix match with 'other'.
*/
isPrefixOf(other) {
if (other.segments.length < this.segments.length) {
return false;
}
for (let i = 0; i < this.segments.length; i++) {
if (this.segments[i] !== other.segments[i]) {
return false;
}
}
return true;
}
/**
* Compare the current path against another Path object.
*
* Compare the current path against another Path object. Paths are compared segment by segment,
* prioritizing numeric IDs (e.g., "__id123__") in numeric ascending order, followed by string
* segments in lexicographical order.
*
* @private
* @internal
* @param other The path to compare to.
* @returns -1 if current < other, 1 if current > other, 0 if equal
*/
compareTo(other) {
const len = Math.min(this.segments.length, other.segments.length);
for (let i = 0; i < len; i++) {
const comparison = this.compareSegments(this.segments[i], other.segments[i]);
if (comparison !== 0) {
return comparison;
}
}
return (0, order_1.primitiveComparator)(this.segments.length, other.segments.length);
}
compareSegments(lhs, rhs) {
const isLhsNumeric = this.isNumericId(lhs);
const isRhsNumeric = this.isNumericId(rhs);
if (isLhsNumeric && !isRhsNumeric) {
// Only lhs is numeric
return -1;
}
else if (!isLhsNumeric && isRhsNumeric) {
// Only rhs is numeric
return 1;
}
else if (isLhsNumeric && isRhsNumeric) {
// both numeric
return this.compareNumbers(this.extractNumericId(lhs), this.extractNumericId(rhs));
}
else {
// both non-numeric
return (0, order_1.compareUtf8Strings)(lhs, rhs);
}
}
// Checks if a segment is a numeric ID (starts with "__id" and ends with "__").
isNumericId(segment) {
return segment.startsWith('__id') && segment.endsWith('__');
}
// Extracts the long number from a numeric ID segment.
extractNumericId(segment) {
return BigInt(segment.substring(4, segment.length - 2));
}
compareNumbers(lhs, rhs) {
if (lhs < rhs) {
return -1;
}
else if (lhs > rhs) {
return 1;
}
else {
return 0;
}
}
/**
* Returns a copy of the underlying segments.
*
* @private
* @internal
* @returns A copy of the segments that make up this path.
*/
toArray() {
return this.segments.slice();
}
/**
* Pops the last segment from this `Path` and returns a newly constructed
* `Path`.
*
* @private
* @internal
* @returns The newly created Path.
*/
popLast() {
this.segments.pop();
return this.construct(this.segments);
}
/**
* Returns true if this `Path` is equal to the provided value.
*
* @private
* @internal
* @param other The value to compare against.
* @return true if this `Path` is equal to the provided value.
*/
isEqual(other) {
return this === other || this.compareTo(other) === 0;
}
}
/**
* A slash-separated path for navigating resources within the current Firestore
* instance.
*
* @private
* @internal
*/
class ResourcePath extends Path {
/**
* Constructs a ResourcePath.
*
* @private
* @internal
* @param segments Sequence of names of the parts of the path.
*/
constructor(...segments) {
super(segments);
}
/**
* Indicates whether this path points to a document.
* @private
* @internal
*/
get isDocument() {
return this.segments.length > 0 && this.segments.length % 2 === 0;
}
/**
* Indicates whether this path points to a collection.
* @private
* @internal
*/
get isCollection() {
return this.segments.length % 2 === 1;
}
/**
* The last component of the path.
* @private
* @internal
*/
get id() {
if (this.segments.length > 0) {
return this.segments[this.segments.length - 1];
}
return null;
}
/**
* Returns the location of this path relative to the root of the project's
* database.
* @private
* @internal
*/
get relativeName() {
return this.segments.join('/');
}
/**
* Constructs a new instance of ResourcePath.
*
* @private
* @internal
* @param segments Sequence of parts of the path.
* @returns The newly created ResourcePath.
*/
construct(segments) {
return new ResourcePath(...segments);
}
/**
* Splits a string into path segments, using slashes as separators.
*
* @private
* @internal
* @param relativePath The path to split.
* @returns The split path segments.
*/
split(relativePath) {
// We may have an empty segment at the beginning or end if they had a
// leading or trailing slash (which we allow).
return relativePath.split('/').filter(segment => segment.length > 0);
}
/**
* Converts this path to a fully qualified ResourcePath.
*
* @private
* @internal
* @param projectId The project ID of the current Firestore project.
* @return A fully-qualified resource path pointing to the same element.
*/
toQualifiedResourcePath(projectId, databaseId) {
return new QualifiedResourcePath(projectId, databaseId, ...this.segments);
}
}
exports.ResourcePath = ResourcePath;
/**
* A default instance pointing to the root collection.
* @private
* @internal
*/
ResourcePath.EMPTY = new ResourcePath();
/**
* A slash-separated path that includes a project and database ID for referring
* to resources in any Firestore project.
*
* @private
* @internal
*/
class QualifiedResourcePath extends ResourcePath {
/**
* Constructs a Firestore Resource Path.
*
* @private
* @internal
* @param projectId The Firestore project id.
* @param databaseId The Firestore database id.
* @param segments Sequence of names of the parts of the path.
*/
constructor(projectId, databaseId, ...segments) {
super(...segments);
this.projectId = projectId;
this.databaseId = databaseId;
}
/**
* String representation of the path relative to the database root.
* @private
* @internal
*/
get relativeName() {
return this.segments.join('/');
}
/**
* Creates a resource path from an absolute Firestore path.
*
* @private
* @internal
* @param absolutePath A string representation of a Resource Path.
* @returns The new ResourcePath.
*/
static fromSlashSeparatedString(absolutePath) {
const elements = RESOURCE_PATH_RE.exec(absolutePath);
if (elements) {
const project = elements[1];
const database = elements[2];
const path = elements[3];
return new QualifiedResourcePath(project, database).append(path);
}
throw new Error(`Resource name '${absolutePath}' is not valid.`);
}
/**
* Create a child path beneath the current level.
*
* @private
* @internal
* @param relativePath Relative path to append to the current path.
* @returns The new path.
*/
append(relativePath) {
// `super.append()` calls `QualifiedResourcePath.construct()` when invoked
// from here and returns a QualifiedResourcePath.
return super.append(relativePath);
}
/**
* Create a child path beneath the current level.
*
* @private
* @internal
* @returns The new path.
*/
parent() {
return super.parent();
}
/**
* String representation of a ResourcePath as expected by the API.
*
* @private
* @internal
* @returns The representation as expected by the API.
*/
get formattedName() {
const components = [
'projects',
this.projectId,
'databases',
this.databaseId,
'documents',
...this.segments,
];
return components.join('/');
}
/**
* Constructs a new instance of ResourcePath. We need this instead of using
* the normal constructor because polymorphic 'this' doesn't work on static
* methods.
*
* @private
* @internal
* @param segments Sequence of names of the parts of the path.
* @returns The newly created QualifiedResourcePath.
*/
construct(segments) {
return new QualifiedResourcePath(this.projectId, this.databaseId, ...segments);
}
/**
* Convenience method to match the ResourcePath API. This method always
* returns the current instance.
*
* @private
* @internal
*/
toQualifiedResourcePath() {
return this;
}
/**
* Compare the current path against another ResourcePath object.
*
* @private
* @internal
* @param other The path to compare to.
* @returns -1 if current < other, 1 if current > other, 0 if equal
*/
compareTo(other) {
if (other instanceof QualifiedResourcePath) {
if (this.projectId < other.projectId) {
return -1;
}
if (this.projectId > other.projectId) {
return 1;
}
if (this.databaseId < other.databaseId) {
return -1;
}
if (this.databaseId > other.databaseId) {
return 1;
}
}
return super.compareTo(other);
}
/**
* Converts this ResourcePath to the Firestore Proto representation.
* @private
* @internal
*/
toProto() {
return {
referenceValue: this.formattedName,
};
}
}
exports.QualifiedResourcePath = QualifiedResourcePath;
/**
* Validates that the given string can be used as a relative or absolute
* resource path.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param resourcePath The path to validate.
* @throws if the string can't be used as a resource path.
*/
function validateResourcePath(arg, resourcePath) {
if (typeof resourcePath !== 'string' || resourcePath === '') {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'resource path')} Path must be a non-empty string.`);
}
if (resourcePath.indexOf('//') >= 0) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'resource path')} Paths must not contain //.`);
}
}
/**
* A dot-separated path for navigating sub-objects (e.g. nested maps) within a document.
*
* @class
*/
class FieldPath extends Path {
/**
* Constructs a Firestore Field Path.
*
* @param {...string} segments Sequence of field names that form this path.
*
* @example
* ```
* let query = firestore.collection('col');
* let fieldPath = new FieldPath('f.o.o', 'bar');
*
* query.where(fieldPath, '==', 42).get().then(snapshot => {
* snapshot.forEach(document => {
* console.log(`Document contains {'f.o.o' : {'bar' : 42}}`);
* });
* });
* ```
*/
constructor(...segments) {
if (Array.isArray(segments[0])) {
throw new Error('The FieldPath constructor no longer supports an array as its first argument. ' +
'Please unpack your array and call FieldPath() with individual arguments.');
}
(0, validate_1.validateMinNumberOfArguments)('FieldPath', segments, 1);
for (let i = 0; i < segments.length; ++i) {
(0, validate_1.validateString)(i, segments[i]);
if (segments[i].length === 0) {
throw new Error(`Element at index ${i} should not be an empty string.`);
}
}
super(segments);
}
/**
* A special FieldPath value to refer to the ID of a document. It can be used
* in queries to sort or filter by the document ID.
*
* @returns {FieldPath}
*/
static documentId() {
return FieldPath._DOCUMENT_ID;
}
/**
* Turns a field path argument into a [FieldPath]{@link FieldPath}.
* Supports FieldPaths as input (which are passed through) and dot-separated
* strings.
*
* @private
* @internal
* @param {string|FieldPath} fieldPath The FieldPath to create.
* @returns {FieldPath} A field path representation.
*/
static fromArgument(fieldPath) {
// validateFieldPath() is used in all public API entry points to validate
// that fromArgument() is only called with a Field Path or a string.
return fieldPath instanceof FieldPath
? fieldPath
: new FieldPath(...fieldPath.split('.'));
}
/**
* String representation of a FieldPath as expected by the API.
*
* @private
* @internal
* @override
* @returns {string} The representation as expected by the API.
*/
get formattedName() {
return this.segments
.map(str => {
return UNESCAPED_FIELD_NAME_RE.test(str)
? str
: '`' + str.replace(/\\/g, '\\\\').replace(/`/g, '\\`') + '`';
})
.join('.');
}
/**
* Returns a string representation of this path.
*
* @private
* @internal
* @returns A string representing this path.
*/
toString() {
return this.formattedName;
}
/**
* Splits a string into path segments, using dots as separators.
*
* @private
* @internal
* @override
* @param {string} fieldPath The path to split.
* @returns {Array.<string>} - The split path segments.
*/
split(fieldPath) {
return fieldPath.split('.');
}
/**
* Constructs a new instance of FieldPath. We need this instead of using
* the normal constructor because polymorphic 'this' doesn't work on static
* methods.
*
* @private
* @internal
* @override
* @param segments Sequence of field names.
* @returns The newly created FieldPath.
*/
construct(segments) {
return new FieldPath(...segments);
}
/**
* Returns true if this `FieldPath` is equal to the provided value.
*
* @param {*} other The value to compare against.
* @return {boolean} true if this `FieldPath` is equal to the provided value.
*/
isEqual(other) {
return super.isEqual(other);
}
}
exports.FieldPath = FieldPath;
/**
* A special sentinel value to refer to the ID of a document.
*
* @private
* @internal
*/
FieldPath._DOCUMENT_ID = new FieldPath('__name__');
/**
* Validates that the provided value can be used as a field path argument.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param fieldPath The value to verify.
* @throws if the string can't be used as a field path.
*/
function validateFieldPath(arg, fieldPath) {
if (fieldPath instanceof FieldPath) {
return;
}
if (fieldPath === undefined) {
throw new Error((0, validate_1.invalidArgumentMessage)(arg, 'field path') + ' The path cannot be omitted.');
}
if ((0, util_1.isObject)(fieldPath) && fieldPath.constructor.name === 'FieldPath') {
throw new Error((0, validate_1.customObjectMessage)(arg, fieldPath));
}
if (typeof fieldPath !== 'string') {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'field path')} Paths can only be specified as strings or via a FieldPath object.`);
}
if (fieldPath.indexOf('..') >= 0) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'field path')} Paths must not contain ".." in them.`);
}
if (fieldPath.startsWith('.') || fieldPath.endsWith('.')) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'field path')} Paths must not start or end with ".".`);
}
if (!FIELD_PATH_RE.test(fieldPath)) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'field path')} Paths can't be empty and must not contain
"*~/[]".`);
}
}
//# sourceMappingURL=path.js.map

View File

@@ -0,0 +1,128 @@
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
export declare const CLIENT_TERMINATED_ERROR_MSG = "The client has already been terminated";
/**
* An auto-resizing pool that distributes concurrent operations over multiple
* clients of type `T`.
*
* ClientPool is used within Firestore to manage a pool of GAPIC clients and
* automatically initializes multiple clients if we issue more than 100
* concurrent operations.
*
* @private
* @internal
*/
export declare class ClientPool<T> {
private readonly concurrentOperationLimit;
private readonly maxIdleClients;
private readonly clientFactory;
private readonly clientDestructor;
private grpcEnabled;
/**
* Stores each active clients and how many operations it has outstanding.
*/
private activeClients;
/**
* A set of clients that have seen RST_STREAM errors (see
* https://github.com/googleapis/nodejs-firestore/issues/1023) and should
* no longer be used.
*/
private failedClients;
/**
* Whether the Firestore instance has been terminated. Once terminated, the
* ClientPool can longer schedule new operations.
*/
private terminated;
/**
* Deferred promise that is resolved when there are no active operations on
* the client pool after terminate() has been called.
*/
private terminateDeferred;
/**
* @param concurrentOperationLimit The number of operations that each client
* can handle.
* @param maxIdleClients The maximum number of idle clients to keep before
* garbage collecting.
* @param clientFactory A factory function called as needed when new clients
* are required.
* @param clientDestructor A cleanup function that is called when a client is
* disposed of.
*/
constructor(concurrentOperationLimit: number, maxIdleClients: number, clientFactory: (requiresGrpc: boolean) => T, clientDestructor?: (client: T) => Promise<void>);
/**
* Returns an already existing client if it has less than the maximum number
* of concurrent operations or initializes and returns a new client.
*
* @private
* @internal
*/
private acquire;
/**
* Reduces the number of operations for the provided client, potentially
* removing it from the pool of active clients.
* @private
* @internal
*/
private release;
/**
* Given the current operation counts, determines if the given client should
* be garbage collected.
* @private
* @internal
*/
private shouldGarbageCollectClient;
/**
* The number of currently registered clients.
*
* @return Number of currently registered clients.
* @private
* @internal
*/
get size(): number;
/**
* The number of currently active operations.
*
* @return Number of currently active operations.
* @private
* @internal
*/
get opCount(): number;
/**
* The currently active clients.
*
* @return The currently active clients.
* @private
* @internal
*/
get _activeClients(): Map<T, {
activeRequestCount: number;
grpcEnabled: boolean;
}>;
/**
* Runs the provided operation in this pool. This function may create an
* additional client if all existing clients already operate at the concurrent
* operation limit.
*
* @param requestTag A unique client-assigned identifier for this operation.
* @param op A callback function that returns a Promise. The client T will
* be returned to the pool when callback finishes.
* @return A Promise that resolves with the result of `op`.
* @private
* @internal
*/
run<V>(requestTag: string, requiresGrpc: boolean, op: (client: T) => Promise<V>): Promise<V>;
terminate(): Promise<void>;
}

View File

@@ -0,0 +1,250 @@
"use strict";
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClientPool = exports.CLIENT_TERMINATED_ERROR_MSG = void 0;
const assert = require("assert");
const logger_1 = require("./logger");
const util_1 = require("./util");
exports.CLIENT_TERMINATED_ERROR_MSG = 'The client has already been terminated';
/**
* An auto-resizing pool that distributes concurrent operations over multiple
* clients of type `T`.
*
* ClientPool is used within Firestore to manage a pool of GAPIC clients and
* automatically initializes multiple clients if we issue more than 100
* concurrent operations.
*
* @private
* @internal
*/
class ClientPool {
/**
* @param concurrentOperationLimit The number of operations that each client
* can handle.
* @param maxIdleClients The maximum number of idle clients to keep before
* garbage collecting.
* @param clientFactory A factory function called as needed when new clients
* are required.
* @param clientDestructor A cleanup function that is called when a client is
* disposed of.
*/
constructor(concurrentOperationLimit, maxIdleClients, clientFactory, clientDestructor = () => Promise.resolve()) {
this.concurrentOperationLimit = concurrentOperationLimit;
this.maxIdleClients = maxIdleClients;
this.clientFactory = clientFactory;
this.clientDestructor = clientDestructor;
this.grpcEnabled = false;
/**
* Stores each active clients and how many operations it has outstanding.
*/
this.activeClients = new Map();
/**
* A set of clients that have seen RST_STREAM errors (see
* https://github.com/googleapis/nodejs-firestore/issues/1023) and should
* no longer be used.
*/
this.failedClients = new Set();
/**
* Whether the Firestore instance has been terminated. Once terminated, the
* ClientPool can longer schedule new operations.
*/
this.terminated = false;
/**
* Deferred promise that is resolved when there are no active operations on
* the client pool after terminate() has been called.
*/
this.terminateDeferred = new util_1.Deferred();
}
/**
* Returns an already existing client if it has less than the maximum number
* of concurrent operations or initializes and returns a new client.
*
* @private
* @internal
*/
acquire(requestTag, requiresGrpc) {
let selectedClient = null;
let selectedClientRequestCount = -1;
// Transition to grpc when we see the first operation that requires grpc.
this.grpcEnabled = this.grpcEnabled || requiresGrpc;
// Require a grpc client for this operation if we have transitioned to grpc.
requiresGrpc = requiresGrpc || this.grpcEnabled;
for (const [client, metadata] of this.activeClients) {
// Use the "most-full" client that can still accommodate the request
// in order to maximize the number of idle clients as operations start to
// complete.
if (!this.failedClients.has(client) &&
metadata.activeRequestCount > selectedClientRequestCount &&
metadata.activeRequestCount < this.concurrentOperationLimit &&
(metadata.grpcEnabled || !requiresGrpc)) {
selectedClient = client;
selectedClientRequestCount = metadata.activeRequestCount;
}
}
if (selectedClient) {
(0, logger_1.logger)('ClientPool.acquire', requestTag, 'Re-using existing client with %s remaining operations', this.concurrentOperationLimit - selectedClientRequestCount);
}
else {
(0, logger_1.logger)('ClientPool.acquire', requestTag, 'Creating a new client (requiresGrpc: %s)', requiresGrpc);
selectedClient = this.clientFactory(requiresGrpc);
selectedClientRequestCount = 0;
assert(!this.activeClients.has(selectedClient), 'The provided client factory returned an existing instance');
}
this.activeClients.set(selectedClient, {
grpcEnabled: requiresGrpc,
activeRequestCount: selectedClientRequestCount + 1,
});
return selectedClient;
}
/**
* Reduces the number of operations for the provided client, potentially
* removing it from the pool of active clients.
* @private
* @internal
*/
async release(requestTag, client) {
const metadata = this.activeClients.get(client);
assert(metadata && metadata.activeRequestCount > 0, 'No active requests');
this.activeClients.set(client, {
grpcEnabled: metadata.grpcEnabled,
activeRequestCount: metadata.activeRequestCount - 1,
});
if (this.terminated && this.opCount === 0) {
this.terminateDeferred.resolve();
}
if (this.shouldGarbageCollectClient(client)) {
this.activeClients.delete(client);
this.failedClients.delete(client);
await this.clientDestructor(client);
(0, logger_1.logger)('ClientPool.release', requestTag, 'Garbage collected 1 client');
}
}
/**
* Given the current operation counts, determines if the given client should
* be garbage collected.
* @private
* @internal
*/
shouldGarbageCollectClient(client) {
const clientMetadata = this.activeClients.get(client);
if (clientMetadata.activeRequestCount !== 0) {
// Don't garbage collect clients that have active requests.
return false;
}
if (this.grpcEnabled !== clientMetadata.grpcEnabled) {
// We are transitioning to GRPC. Garbage collect REST clients.
return true;
}
// Idle clients that have received RST_STREAM errors are always garbage
// collected.
if (this.failedClients.has(client)) {
return true;
}
// Otherwise, only garbage collect if we have too much idle capacity (e.g.
// more than 100 idle capacity with default settings).
let idleCapacityCount = 0;
for (const [, metadata] of this.activeClients) {
idleCapacityCount +=
this.concurrentOperationLimit - metadata.activeRequestCount;
}
return (idleCapacityCount > this.maxIdleClients * this.concurrentOperationLimit);
}
/**
* The number of currently registered clients.
*
* @return Number of currently registered clients.
* @private
* @internal
*/
// Visible for testing.
get size() {
return this.activeClients.size;
}
/**
* The number of currently active operations.
*
* @return Number of currently active operations.
* @private
* @internal
*/
// Visible for testing.
get opCount() {
let activeOperationCount = 0;
this.activeClients.forEach(metadata => (activeOperationCount += metadata.activeRequestCount));
return activeOperationCount;
}
/**
* The currently active clients.
*
* @return The currently active clients.
* @private
* @internal
*/
// Visible for testing.
get _activeClients() {
return this.activeClients;
}
/**
* Runs the provided operation in this pool. This function may create an
* additional client if all existing clients already operate at the concurrent
* operation limit.
*
* @param requestTag A unique client-assigned identifier for this operation.
* @param op A callback function that returns a Promise. The client T will
* be returned to the pool when callback finishes.
* @return A Promise that resolves with the result of `op`.
* @private
* @internal
*/
run(requestTag, requiresGrpc, op) {
if (this.terminated) {
return Promise.reject(new Error(exports.CLIENT_TERMINATED_ERROR_MSG));
}
const client = this.acquire(requestTag, requiresGrpc);
return op(client)
.catch(async (err) => {
var _a;
if ((_a = err.message) === null || _a === void 0 ? void 0 : _a.match(/RST_STREAM/)) {
// Once a client has seen a RST_STREAM error, the GRPC channel can
// no longer be used. We mark the client as failed, which ensures that
// we open a new GRPC channel for the next request.
this.failedClients.add(client);
}
await this.release(requestTag, client);
return Promise.reject(err);
})
.then(async (res) => {
await this.release(requestTag, client);
return res;
});
}
async terminate() {
this.terminated = true;
// Wait for all pending operations to complete before terminating.
if (this.opCount > 0) {
(0, logger_1.logger)('ClientPool.terminate',
/* requestTag= */ null, 'Waiting for %s pending operations to complete before terminating', this.opCount);
await this.terminateDeferred.promise;
}
for (const [client] of this.activeClients) {
this.activeClients.delete(client);
await this.clientDestructor(client);
}
}
}
exports.ClientPool = ClientPool;
//# sourceMappingURL=pool.js.map

View File

@@ -0,0 +1,96 @@
import * as firestore from '@google-cloud/firestore';
import * as protos from '../protos/firestore_v1_proto_api';
import { Query } from './reference/query';
import { Firestore } from './index';
import api = protos.google.firestore.v1;
/**
* A split point that can be used in a query as a starting and/or end point for
* the query results. The cursors returned by {@link #startAt} and {@link
* #endBefore} can only be used in a query that matches the constraint of query
* that produced this partition.
*
* @class QueryPartition
*/
export declare class QueryPartition<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> implements firestore.QueryPartition<AppModelType, DbModelType> {
private readonly _firestore;
private readonly _collectionId;
private readonly _converter;
private readonly _startAt;
private readonly _endBefore;
private readonly _serializer;
private _memoizedStartAt;
private _memoizedEndBefore;
/** @private */
constructor(_firestore: Firestore, _collectionId: string, _converter: firestore.FirestoreDataConverter<AppModelType, DbModelType>, _startAt: api.IValue[] | undefined, _endBefore: api.IValue[] | undefined);
/**
* The cursor that defines the first result for this partition or `undefined`
* if this is the first partition. The cursor value must be
* destructured when passed to `startAt()` (for example with
* `query.startAt(...queryPartition.startAt)`).
*
* @example
* ```
* const query = firestore.collectionGroup('collectionId');
* for await (const partition of query.getPartitions(42)) {
* let partitionedQuery = query.orderBy(FieldPath.documentId());
* if (partition.startAt) {
* partitionedQuery = partitionedQuery.startAt(...partition.startAt);
* }
* if (partition.endBefore) {
* partitionedQuery = partitionedQuery.endBefore(...partition.endBefore);
* }
* const querySnapshot = await partitionedQuery.get();
* console.log(`Partition contained ${querySnapshot.length} documents`);
* }
*
* ```
* @type {Array<*>}
* @return {Array<*>} A cursor value that can be used with {@link
* Query#startAt} or `undefined` if this is the first partition.
*/
get startAt(): unknown[] | undefined;
/**
* The cursor that defines the first result after this partition or
* `undefined` if this is the last partition. The cursor value must be
* destructured when passed to `endBefore()` (for example with
* `query.endBefore(...queryPartition.endBefore)`).
*
* @example
* ```
* const query = firestore.collectionGroup('collectionId');
* for await (const partition of query.getPartitions(42)) {
* let partitionedQuery = query.orderBy(FieldPath.documentId());
* if (partition.startAt) {
* partitionedQuery = partitionedQuery.startAt(...partition.startAt);
* }
* if (partition.endBefore) {
* partitionedQuery = partitionedQuery.endBefore(...partition.endBefore);
* }
* const querySnapshot = await partitionedQuery.get();
* console.log(`Partition contained ${querySnapshot.length} documents`);
* }
*
* ```
* @type {Array<*>}
* @return {Array<*>} A cursor value that can be used with {@link
* Query#endBefore} or `undefined` if this is the last partition.
*/
get endBefore(): unknown[] | undefined;
/**
* Returns a query that only encapsulates the documents for this partition.
*
* @example
* ```
* const query = firestore.collectionGroup('collectionId');
* for await (const partition of query.getPartitions(42)) {
* const partitionedQuery = partition.toQuery();
* const querySnapshot = await partitionedQuery.get();
* console.log(`Partition contained ${querySnapshot.length} documents`);
* }
*
* ```
* @return {Query<T>} A query partitioned by a {@link Query#startAt} and
* {@link Query#endBefore} cursor.
*/
toQuery(): Query<AppModelType, DbModelType>;
}

View File

@@ -0,0 +1,144 @@
"use strict";
/*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueryPartition = void 0;
const field_order_1 = require("./reference/field-order");
const query_1 = require("./reference/query");
const query_options_1 = require("./reference/query-options");
const path_1 = require("./path");
const serializer_1 = require("./serializer");
/**
* A split point that can be used in a query as a starting and/or end point for
* the query results. The cursors returned by {@link #startAt} and {@link
* #endBefore} can only be used in a query that matches the constraint of query
* that produced this partition.
*
* @class QueryPartition
*/
class QueryPartition {
/** @private */
constructor(_firestore, _collectionId, _converter, _startAt, _endBefore) {
this._firestore = _firestore;
this._collectionId = _collectionId;
this._converter = _converter;
this._startAt = _startAt;
this._endBefore = _endBefore;
this._serializer = new serializer_1.Serializer(_firestore);
}
/**
* The cursor that defines the first result for this partition or `undefined`
* if this is the first partition. The cursor value must be
* destructured when passed to `startAt()` (for example with
* `query.startAt(...queryPartition.startAt)`).
*
* @example
* ```
* const query = firestore.collectionGroup('collectionId');
* for await (const partition of query.getPartitions(42)) {
* let partitionedQuery = query.orderBy(FieldPath.documentId());
* if (partition.startAt) {
* partitionedQuery = partitionedQuery.startAt(...partition.startAt);
* }
* if (partition.endBefore) {
* partitionedQuery = partitionedQuery.endBefore(...partition.endBefore);
* }
* const querySnapshot = await partitionedQuery.get();
* console.log(`Partition contained ${querySnapshot.length} documents`);
* }
*
* ```
* @type {Array<*>}
* @return {Array<*>} A cursor value that can be used with {@link
* Query#startAt} or `undefined` if this is the first partition.
*/
get startAt() {
if (this._startAt && !this._memoizedStartAt) {
this._memoizedStartAt = this._startAt.map(v => this._serializer.decodeValue(v));
}
return this._memoizedStartAt;
}
/**
* The cursor that defines the first result after this partition or
* `undefined` if this is the last partition. The cursor value must be
* destructured when passed to `endBefore()` (for example with
* `query.endBefore(...queryPartition.endBefore)`).
*
* @example
* ```
* const query = firestore.collectionGroup('collectionId');
* for await (const partition of query.getPartitions(42)) {
* let partitionedQuery = query.orderBy(FieldPath.documentId());
* if (partition.startAt) {
* partitionedQuery = partitionedQuery.startAt(...partition.startAt);
* }
* if (partition.endBefore) {
* partitionedQuery = partitionedQuery.endBefore(...partition.endBefore);
* }
* const querySnapshot = await partitionedQuery.get();
* console.log(`Partition contained ${querySnapshot.length} documents`);
* }
*
* ```
* @type {Array<*>}
* @return {Array<*>} A cursor value that can be used with {@link
* Query#endBefore} or `undefined` if this is the last partition.
*/
get endBefore() {
if (this._endBefore && !this._memoizedEndBefore) {
this._memoizedEndBefore = this._endBefore.map(v => this._serializer.decodeValue(v));
}
return this._memoizedEndBefore;
}
/**
* Returns a query that only encapsulates the documents for this partition.
*
* @example
* ```
* const query = firestore.collectionGroup('collectionId');
* for await (const partition of query.getPartitions(42)) {
* const partitionedQuery = partition.toQuery();
* const querySnapshot = await partitionedQuery.get();
* console.log(`Partition contained ${querySnapshot.length} documents`);
* }
*
* ```
* @return {Query<T>} A query partitioned by a {@link Query#startAt} and
* {@link Query#endBefore} cursor.
*/
toQuery() {
// Since the api.Value to JavaScript type conversion can be lossy (unless
// `useBigInt` is used), we pass the original protobuf representation to the
// created query.
let queryOptions = query_options_1.QueryOptions.forCollectionGroupQuery(this._collectionId, this._converter);
queryOptions = queryOptions.with({
fieldOrders: [new field_order_1.FieldOrder(path_1.FieldPath.documentId())],
});
if (this._startAt !== undefined) {
queryOptions = queryOptions.with({
startAt: { before: true, values: this._startAt },
});
}
if (this._endBefore !== undefined) {
queryOptions = queryOptions.with({
endAt: { before: true, values: this._endBefore },
});
}
return new query_1.Query(this._firestore, queryOptions);
}
}
exports.QueryPartition = QueryPartition;
//# sourceMappingURL=query-partition.js.map

View File

@@ -0,0 +1,94 @@
/*!
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import { google } from '../protos/firestore_v1_proto_api';
import { Serializer } from './serializer';
import IPlanSummary = google.firestore.v1.IPlanSummary;
import IExecutionStats = google.firestore.v1.IExecutionStats;
import IExplainMetrics = google.firestore.v1.IExplainMetrics;
/**
* PlanSummary contains information about the planning stage of a query.
*
* @class PlanSummary
*/
export declare class PlanSummary implements firestore.PlanSummary {
readonly indexesUsed: Record<string, unknown>[];
/**
* @private
* @internal
*/
constructor(indexesUsed: Record<string, unknown>[]);
/**
* @private
* @internal
*/
static _fromProto(plan: IPlanSummary | null | undefined, serializer: Serializer): PlanSummary;
}
/**
* ExecutionStats contains information about the execution of a query.
*
* @class ExecutionStats
*/
export declare class ExecutionStats implements firestore.ExecutionStats {
readonly resultsReturned: number;
readonly executionDuration: firestore.Duration;
readonly readOperations: number;
readonly debugStats: Record<string, unknown>;
/**
* @private
* @internal
*/
constructor(resultsReturned: number, executionDuration: firestore.Duration, readOperations: number, debugStats: Record<string, unknown>);
/**
* @private
* @internal
*/
static _fromProto(stats: IExecutionStats | null | undefined, serializer: Serializer): ExecutionStats | null;
}
/**
* ExplainMetrics contains information about planning and execution of a query.
*
* @class ExplainMetrics
*/
export declare class ExplainMetrics implements firestore.ExplainMetrics {
readonly planSummary: PlanSummary;
readonly executionStats: ExecutionStats | null;
/**
* @private
* @internal
*/
constructor(planSummary: PlanSummary, executionStats: ExecutionStats | null);
/**
* @private
* @internal
*/
static _fromProto(metrics: IExplainMetrics, serializer: Serializer): ExplainMetrics;
}
/**
* ExplainResults contains information about planning, execution, and results
* of a query.
*
* @class ExplainResults
*/
export declare class ExplainResults<T> implements firestore.ExplainResults<T> {
readonly metrics: ExplainMetrics;
readonly snapshot: T | null;
/**
* @private
* @internal
*/
constructor(metrics: ExplainMetrics, snapshot: T | null);
}

View File

@@ -0,0 +1,119 @@
"use strict";
/*!
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExplainResults = exports.ExplainMetrics = exports.ExecutionStats = exports.PlanSummary = void 0;
/**
* PlanSummary contains information about the planning stage of a query.
*
* @class PlanSummary
*/
class PlanSummary {
/**
* @private
* @internal
*/
constructor(indexesUsed) {
this.indexesUsed = indexesUsed;
}
/**
* @private
* @internal
*/
static _fromProto(plan, serializer) {
const indexes = [];
if (plan && plan.indexesUsed) {
for (const index of plan.indexesUsed) {
indexes.push(serializer.decodeGoogleProtobufStruct(index));
}
}
return new PlanSummary(indexes);
}
}
exports.PlanSummary = PlanSummary;
/**
* ExecutionStats contains information about the execution of a query.
*
* @class ExecutionStats
*/
class ExecutionStats {
/**
* @private
* @internal
*/
constructor(resultsReturned, executionDuration, readOperations, debugStats) {
this.resultsReturned = resultsReturned;
this.executionDuration = executionDuration;
this.readOperations = readOperations;
this.debugStats = debugStats;
}
/**
* @private
* @internal
*/
static _fromProto(stats, serializer) {
var _a, _b;
if (stats) {
return new ExecutionStats(Number(stats.resultsReturned), {
seconds: Number((_a = stats.executionDuration) === null || _a === void 0 ? void 0 : _a.seconds),
nanoseconds: Number((_b = stats.executionDuration) === null || _b === void 0 ? void 0 : _b.nanos),
}, Number(stats.readOperations), serializer.decodeGoogleProtobufStruct(stats.debugStats));
}
return null;
}
}
exports.ExecutionStats = ExecutionStats;
/**
* ExplainMetrics contains information about planning and execution of a query.
*
* @class ExplainMetrics
*/
class ExplainMetrics {
/**
* @private
* @internal
*/
constructor(planSummary, executionStats) {
this.planSummary = planSummary;
this.executionStats = executionStats;
}
/**
* @private
* @internal
*/
static _fromProto(metrics, serializer) {
return new ExplainMetrics(PlanSummary._fromProto(metrics.planSummary, serializer), ExecutionStats._fromProto(metrics.executionStats, serializer));
}
}
exports.ExplainMetrics = ExplainMetrics;
/**
* ExplainResults contains information about planning, execution, and results
* of a query.
*
* @class ExplainResults
*/
class ExplainResults {
/**
* @private
* @internal
*/
constructor(metrics, snapshot) {
this.metrics = metrics;
this.snapshot = snapshot;
}
}
exports.ExplainResults = ExplainResults;
//# sourceMappingURL=query-profile.js.map

View File

@@ -0,0 +1,75 @@
/**
* A helper that uses the Token Bucket algorithm to rate limit the number of
* operations that can be made in a second.
*
* Before a given request containing a number of operations can proceed,
* RateLimiter determines doing so stays under the provided rate limits. It can
* also determine how much time is required before a request can be made.
*
* RateLimiter can also implement a gradually increasing rate limit. This is
* used to enforce the 500/50/5 rule
* (https://firebase.google.com/docs/firestore/best-practices#ramping_up_traffic).
*
* @private
* @internal
*/
export declare class RateLimiter {
private readonly initialCapacity;
private readonly multiplier;
private readonly multiplierMillis;
readonly maximumCapacity: number;
private readonly startTimeMillis;
availableTokens: number;
lastRefillTimeMillis: number;
previousCapacity: number;
/**
* @param initialCapacity Initial maximum number of operations per second.
* @param multiplier Rate by which to increase the capacity.
* @param multiplierMillis How often the capacity should increase in
* milliseconds.
* @param maximumCapacity Maximum number of allowed operations per second.
* The number of tokens added per second will never exceed this number.
* @param startTimeMillis The starting time in epoch milliseconds that the
* rate limit is based on. Used for testing the limiter.
*/
constructor(initialCapacity: number, multiplier: number, multiplierMillis: number, maximumCapacity: number, startTimeMillis?: number);
/**
* Tries to make the number of operations. Returns true if the request
* succeeded and false otherwise.
*
* @param requestTimeMillis The time used to calculate the number of available
* tokens. Used for testing the limiter.
* @private
* @internal
*/
tryMakeRequest(numOperations: number, requestTimeMillis?: number): boolean;
/**
* Returns the number of ms needed to make a request with the provided number
* of operations. Returns 0 if the request can be made with the existing
* capacity. Returns -1 if the request is not possible with the current
* capacity.
*
* @param requestTimeMillis The time used to calculate the number of available
* tokens. Used for testing the limiter.
* @private
* @internal
*/
getNextRequestDelayMs(numOperations: number, requestTimeMillis?: number): number;
/**
* Refills the number of available tokens based on how much time has elapsed
* since the last time the tokens were refilled.
*
* @param requestTimeMillis The time used to calculate the number of available
* tokens. Used for testing the limiter.
* @private
* @internal
*/
private refillTokens;
/**
* Calculates the maximum capacity based on the provided date.
*
* @private
* @internal
*/
calculateCapacity(requestTimeMillis: number): number;
}

View File

@@ -0,0 +1,139 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RateLimiter = void 0;
/*!
* 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 assert = require("assert");
const logger_1 = require("./logger");
/**
* A helper that uses the Token Bucket algorithm to rate limit the number of
* operations that can be made in a second.
*
* Before a given request containing a number of operations can proceed,
* RateLimiter determines doing so stays under the provided rate limits. It can
* also determine how much time is required before a request can be made.
*
* RateLimiter can also implement a gradually increasing rate limit. This is
* used to enforce the 500/50/5 rule
* (https://firebase.google.com/docs/firestore/best-practices#ramping_up_traffic).
*
* @private
* @internal
*/
class RateLimiter {
/**
* @param initialCapacity Initial maximum number of operations per second.
* @param multiplier Rate by which to increase the capacity.
* @param multiplierMillis How often the capacity should increase in
* milliseconds.
* @param maximumCapacity Maximum number of allowed operations per second.
* The number of tokens added per second will never exceed this number.
* @param startTimeMillis The starting time in epoch milliseconds that the
* rate limit is based on. Used for testing the limiter.
*/
constructor(initialCapacity, multiplier, multiplierMillis, maximumCapacity, startTimeMillis = Date.now()) {
this.initialCapacity = initialCapacity;
this.multiplier = multiplier;
this.multiplierMillis = multiplierMillis;
this.maximumCapacity = maximumCapacity;
this.startTimeMillis = startTimeMillis;
this.availableTokens = initialCapacity;
this.lastRefillTimeMillis = startTimeMillis;
this.previousCapacity = initialCapacity;
}
/**
* Tries to make the number of operations. Returns true if the request
* succeeded and false otherwise.
*
* @param requestTimeMillis The time used to calculate the number of available
* tokens. Used for testing the limiter.
* @private
* @internal
*/
tryMakeRequest(numOperations, requestTimeMillis = Date.now()) {
this.refillTokens(requestTimeMillis);
if (numOperations <= this.availableTokens) {
this.availableTokens -= numOperations;
return true;
}
return false;
}
/**
* Returns the number of ms needed to make a request with the provided number
* of operations. Returns 0 if the request can be made with the existing
* capacity. Returns -1 if the request is not possible with the current
* capacity.
*
* @param requestTimeMillis The time used to calculate the number of available
* tokens. Used for testing the limiter.
* @private
* @internal
*/
getNextRequestDelayMs(numOperations, requestTimeMillis = Date.now()) {
this.refillTokens(requestTimeMillis);
if (numOperations < this.availableTokens) {
return 0;
}
const capacity = this.calculateCapacity(requestTimeMillis);
if (capacity < numOperations) {
return -1;
}
const requiredTokens = numOperations - this.availableTokens;
return Math.ceil((requiredTokens * 1000) / capacity);
}
/**
* Refills the number of available tokens based on how much time has elapsed
* since the last time the tokens were refilled.
*
* @param requestTimeMillis The time used to calculate the number of available
* tokens. Used for testing the limiter.
* @private
* @internal
*/
refillTokens(requestTimeMillis) {
if (requestTimeMillis >= this.lastRefillTimeMillis) {
const elapsedTime = requestTimeMillis - this.lastRefillTimeMillis;
const capacity = this.calculateCapacity(requestTimeMillis);
const tokensToAdd = Math.floor((elapsedTime * capacity) / 1000);
if (tokensToAdd > 0) {
this.availableTokens = Math.min(capacity, this.availableTokens + tokensToAdd);
this.lastRefillTimeMillis = requestTimeMillis;
}
}
else {
throw new Error('Request time should not be before the last token refill time.');
}
}
/**
* Calculates the maximum capacity based on the provided date.
*
* @private
* @internal
*/
// Visible for testing.
calculateCapacity(requestTimeMillis) {
assert(requestTimeMillis >= this.startTimeMillis, 'startTime cannot be after currentTime');
const millisElapsed = requestTimeMillis - this.startTimeMillis;
const operationsPerSecond = Math.min(Math.floor(Math.pow(this.multiplier, Math.floor(millisElapsed / this.multiplierMillis)) * this.initialCapacity), this.maximumCapacity);
if (operationsPerSecond !== this.previousCapacity) {
(0, logger_1.logger)('RateLimiter.calculateCapacity', null, `New request capacity: ${operationsPerSecond} operations per second.`);
}
this.previousCapacity = operationsPerSecond;
return operationsPerSecond;
}
}
exports.RateLimiter = RateLimiter;
//# sourceMappingURL=rate-limiter.js.map

View File

@@ -0,0 +1,166 @@
/*!
* Copyright 2021 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.
*/
import * as firestore from '@google-cloud/firestore';
import Firestore, { BulkWriter } from '.';
/*!
* Datastore allowed numeric IDs where Firestore only allows strings. Numeric
* IDs are exposed to Firestore as __idNUM__, so this is the lowest possible
* negative numeric value expressed in that format.
*
* This constant is used to specify startAt/endAt values when querying for all
* descendants in a single collection.
*/
export declare const REFERENCE_NAME_MIN_ID = "__id-9223372036854775808__";
/*!
* The query limit used for recursive deletes when fetching all descendants of
* the specified reference to delete. This is done to prevent the query stream
* from streaming documents faster than Firestore can delete.
*/
export declare const RECURSIVE_DELETE_MAX_PENDING_OPS = 5000;
/*!
* The number of pending BulkWriter operations at which RecursiveDelete
* starts the next limit query to fetch descendants. By starting the query
* while there are pending operations, Firestore can improve BulkWriter
* throughput. This helps prevent BulkWriter from idling while Firestore
* fetches the next query.
*/
export declare const RECURSIVE_DELETE_MIN_PENDING_OPS = 1000;
/**
* Class used to store state required for running a recursive delete operation.
* Each recursive delete call should use a new instance of the class.
* @private
* @internal
*/
export declare class RecursiveDelete {
private readonly firestore;
private readonly writer;
private readonly ref;
private readonly maxLimit;
private readonly minLimit;
/**
* The number of deletes that failed with a permanent error.
* @private
* @internal
*/
private errorCount;
/**
* The most recently thrown error. Used to populate the developer-facing
* error message when the recursive delete operation completes.
* @private
* @internal
*/
private lastError;
/**
* Whether there are still documents to delete that still need to be fetched.
* @private
* @internal
*/
private documentsPending;
/**
* Whether run() has been called.
* @private
* @internal
*/
private started;
/**
* Query limit to use when fetching all descendants.
* @private
* @internal
*/
private readonly maxPendingOps;
/**
* The number of pending BulkWriter operations at which RecursiveDelete
* starts the next limit query to fetch descendants.
* @private
* @internal
*/
private readonly minPendingOps;
/**
* A deferred promise that resolves when the recursive delete operation
* is completed.
* @private
* @internal
*/
private readonly completionDeferred;
/**
* Whether a query stream is currently in progress. Only one stream can be
* run at a time.
* @private
* @internal
*/
private streamInProgress;
/**
* The last document snapshot returned by the stream. Used to set the
* startAfter() field in the subsequent stream.
* @private
* @internal
*/
private lastDocumentSnap;
/**
* The number of pending BulkWriter operations. Used to determine when the
* next query can be run.
* @private
* @internal
*/
private pendingOpsCount;
private errorStack;
/**
*
* @param firestore The Firestore instance to use.
* @param writer The BulkWriter instance to use for delete operations.
* @param ref The document or collection reference to recursively delete.
* @param maxLimit The query limit to use when fetching descendants
* @param minLimit The number of pending BulkWriter operations at which
* RecursiveDelete starts the next limit query to fetch descendants.
*/
constructor(firestore: Firestore, writer: BulkWriter, ref: firestore.CollectionReference<unknown> | firestore.DocumentReference<unknown>, maxLimit: number, minLimit: number);
/**
* Recursively deletes the reference provided in the class constructor.
* Returns a promise that resolves when all descendants have been deleted, or
* if an error occurs.
*/
run(): Promise<void>;
/**
* Creates a query stream and attaches event handlers to it.
* @private
* @internal
*/
private setupStream;
/**
* Retrieves all descendant documents nested under the provided reference.
* @param ref The reference to fetch all descendants for.
* @private
* @internal
* @return {Stream<QueryDocumentSnapshot>} Stream of descendant documents.
*/
private getAllDescendants;
/**
* Called when all descendants of the provided reference have been streamed
* or if a permanent error occurs during the stream. Deletes the developer
* provided reference and wraps any errors that occurred.
* @private
* @internal
*/
private onQueryEnd;
/**
* Deletes the provided reference and starts the next stream if conditions
* are met.
* @private
* @internal
*/
private deleteRef;
private incrementErrorCount;
}

View File

@@ -0,0 +1,251 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RecursiveDelete = exports.RECURSIVE_DELETE_MIN_PENDING_OPS = exports.RECURSIVE_DELETE_MAX_PENDING_OPS = exports.REFERENCE_NAME_MIN_ID = void 0;
const assert = require("assert");
const _1 = require(".");
const util_1 = require("./util");
const query_options_1 = require("./reference/query-options");
/*!
* Datastore allowed numeric IDs where Firestore only allows strings. Numeric
* IDs are exposed to Firestore as __idNUM__, so this is the lowest possible
* negative numeric value expressed in that format.
*
* This constant is used to specify startAt/endAt values when querying for all
* descendants in a single collection.
*/
exports.REFERENCE_NAME_MIN_ID = '__id-9223372036854775808__';
/*!
* The query limit used for recursive deletes when fetching all descendants of
* the specified reference to delete. This is done to prevent the query stream
* from streaming documents faster than Firestore can delete.
*/
// Visible for testing.
exports.RECURSIVE_DELETE_MAX_PENDING_OPS = 5000;
/*!
* The number of pending BulkWriter operations at which RecursiveDelete
* starts the next limit query to fetch descendants. By starting the query
* while there are pending operations, Firestore can improve BulkWriter
* throughput. This helps prevent BulkWriter from idling while Firestore
* fetches the next query.
*/
exports.RECURSIVE_DELETE_MIN_PENDING_OPS = 1000;
/**
* Class used to store state required for running a recursive delete operation.
* Each recursive delete call should use a new instance of the class.
* @private
* @internal
*/
class RecursiveDelete {
/**
*
* @param firestore The Firestore instance to use.
* @param writer The BulkWriter instance to use for delete operations.
* @param ref The document or collection reference to recursively delete.
* @param maxLimit The query limit to use when fetching descendants
* @param minLimit The number of pending BulkWriter operations at which
* RecursiveDelete starts the next limit query to fetch descendants.
*/
constructor(firestore, writer, ref, maxLimit, minLimit) {
this.firestore = firestore;
this.writer = writer;
this.ref = ref;
this.maxLimit = maxLimit;
this.minLimit = minLimit;
/**
* The number of deletes that failed with a permanent error.
* @private
* @internal
*/
this.errorCount = 0;
/**
* Whether there are still documents to delete that still need to be fetched.
* @private
* @internal
*/
this.documentsPending = true;
/**
* Whether run() has been called.
* @private
* @internal
*/
this.started = false;
/**
* A deferred promise that resolves when the recursive delete operation
* is completed.
* @private
* @internal
*/
this.completionDeferred = new util_1.Deferred();
/**
* Whether a query stream is currently in progress. Only one stream can be
* run at a time.
* @private
* @internal
*/
this.streamInProgress = false;
/**
* The number of pending BulkWriter operations. Used to determine when the
* next query can be run.
* @private
* @internal
*/
this.pendingOpsCount = 0;
this.errorStack = '';
this.maxPendingOps = maxLimit;
this.minPendingOps = minLimit;
}
/**
* Recursively deletes the reference provided in the class constructor.
* Returns a promise that resolves when all descendants have been deleted, or
* if an error occurs.
*/
run() {
assert(!this.started, 'RecursiveDelete.run() should only be called once.');
// Capture the error stack to preserve stack tracing across async calls.
this.errorStack = Error().stack;
this.writer._verifyNotClosed();
this.setupStream();
return this.completionDeferred.promise;
}
/**
* Creates a query stream and attaches event handlers to it.
* @private
* @internal
*/
setupStream() {
const stream = this.getAllDescendants(this.ref instanceof _1.CollectionReference
? this.ref
: this.ref);
this.streamInProgress = true;
let streamedDocsCount = 0;
stream
.on('error', err => {
err.code = 14 /* StatusCode.UNAVAILABLE */;
err.stack = 'Failed to fetch children documents: ' + err.stack;
this.lastError = err;
this.onQueryEnd();
})
.on('data', (snap) => {
streamedDocsCount++;
this.lastDocumentSnap = snap;
this.deleteRef(snap.ref);
})
.on('end', () => {
this.streamInProgress = false;
// If there are fewer than the number of documents specified in the
// limit() field, we know that the query is complete.
if (streamedDocsCount < this.minPendingOps) {
this.onQueryEnd();
}
else if (this.pendingOpsCount === 0) {
this.setupStream();
}
});
}
/**
* Retrieves all descendant documents nested under the provided reference.
* @param ref The reference to fetch all descendants for.
* @private
* @internal
* @return {Stream<QueryDocumentSnapshot>} Stream of descendant documents.
*/
getAllDescendants(ref) {
// The parent is the closest ancestor document to the location we're
// deleting. If we are deleting a document, the parent is the path of that
// document. If we are deleting a collection, the parent is the path of the
// document containing that collection (or the database root, if it is a
// root collection).
let parentPath = ref._resourcePath;
if (ref instanceof _1.CollectionReference) {
parentPath = parentPath.popLast();
}
const collectionId = ref instanceof _1.CollectionReference
? ref.id
: ref.parent.id;
let query = new _1.Query(this.firestore, query_options_1.QueryOptions.forKindlessAllDescendants(parentPath, collectionId,
/* requireConsistency= */ false));
// Query for names only to fetch empty snapshots.
query = query.select(_1.FieldPath.documentId()).limit(this.maxPendingOps);
if (ref instanceof _1.CollectionReference) {
// To find all descendants of a collection reference, we need to use a
// composite filter that captures all documents that start with the
// collection prefix. The MIN_KEY constant represents the minimum key in
// this collection, and a null byte + the MIN_KEY represents the minimum
// key is the next possible collection.
const nullChar = String.fromCharCode(0);
const startAt = collectionId + '/' + exports.REFERENCE_NAME_MIN_ID;
const endAt = collectionId + nullChar + '/' + exports.REFERENCE_NAME_MIN_ID;
query = query
.where(_1.FieldPath.documentId(), '>=', startAt)
.where(_1.FieldPath.documentId(), '<', endAt);
}
if (this.lastDocumentSnap) {
query = query.startAfter(this.lastDocumentSnap);
}
return query.stream();
}
/**
* Called when all descendants of the provided reference have been streamed
* or if a permanent error occurs during the stream. Deletes the developer
* provided reference and wraps any errors that occurred.
* @private
* @internal
*/
onQueryEnd() {
this.documentsPending = false;
if (this.ref instanceof _1.DocumentReference) {
this.writer.delete(this.ref).catch(err => this.incrementErrorCount(err));
}
this.writer.flush().then(async () => {
var _a;
if (this.lastError === undefined) {
this.completionDeferred.resolve();
}
else {
let error = new (require('google-gax/build/src/fallback').GoogleError)(`${this.errorCount} ` +
`${this.errorCount !== 1 ? 'deletes' : 'delete'} ` +
'failed. The last delete failed with: ');
if (this.lastError.code !== undefined) {
error.code = this.lastError.code;
}
error = (0, util_1.wrapError)(error, this.errorStack);
// Wrap the BulkWriter error last to provide the full stack trace.
this.completionDeferred.reject(this.lastError.stack
? (0, util_1.wrapError)(error, (_a = this.lastError.stack) !== null && _a !== void 0 ? _a : '')
: error);
}
});
}
/**
* Deletes the provided reference and starts the next stream if conditions
* are met.
* @private
* @internal
*/
deleteRef(docRef) {
this.pendingOpsCount++;
this.writer
.delete(docRef)
.catch(err => {
this.incrementErrorCount(err);
})
.then(() => {
this.pendingOpsCount--;
// We wait until the previous stream has ended in order to sure the
// startAfter document is correct. Starting the next stream while
// there are pending operations allows Firestore to maximize
// BulkWriter throughput.
if (this.documentsPending &&
!this.streamInProgress &&
this.pendingOpsCount < this.minPendingOps) {
this.setupStream();
}
});
}
incrementErrorCount(err) {
this.errorCount++;
this.lastError = err;
}
}
exports.RecursiveDelete = RecursiveDelete;
//# sourceMappingURL=recursive-delete.js.map

View File

@@ -0,0 +1,63 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import { Timestamp } from '../timestamp';
import { AggregateQuery } from './aggregate-query';
/**
* The results of executing an aggregation query.
*/
export declare class AggregateQuerySnapshot<AggregateSpecType extends firestore.AggregateSpec, AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> implements firestore.AggregateQuerySnapshot<AggregateSpecType, AppModelType, DbModelType> {
private readonly _query;
private readonly _readTime;
private readonly _data;
/**
* @internal
*
* @param _query The query that was executed to produce this result.
* @param _readTime The time this snapshot was read.
* @param _data The results of the aggregations performed over the underlying
* query.
*/
constructor(_query: AggregateQuery<AggregateSpecType, AppModelType, DbModelType>, _readTime: Timestamp, _data: firestore.AggregateSpecData<AggregateSpecType>);
/** The query that was executed to produce this result. */
get query(): AggregateQuery<AggregateSpecType, AppModelType, DbModelType>;
/** The time this snapshot was read. */
get readTime(): Timestamp;
/**
* Returns the results of the aggregations performed over the underlying
* query.
*
* The keys of the returned object will be the same as those of the
* `AggregateSpec` object specified to the aggregation method, and the
* values will be the corresponding aggregation result.
*
* @returns The results of the aggregations performed over the underlying
* query.
*/
data(): firestore.AggregateSpecData<AggregateSpecType>;
/**
* Compares this object with the given object for equality.
*
* Two `AggregateQuerySnapshot` instances are considered "equal" if they
* have the same data and their underlying queries compare "equal" using
* `AggregateQuery.isEqual()`.
*
* @param other The object to compare to this object for equality.
* @return `true` if this object is "equal" to the given object, as
* defined above, or `false` otherwise.
*/
isEqual(other: firestore.AggregateQuerySnapshot<AggregateSpecType, AppModelType, DbModelType>): boolean;
}

View File

@@ -0,0 +1,87 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AggregateQuerySnapshot = void 0;
const deepEqual = require("fast-deep-equal");
/**
* The results of executing an aggregation query.
*/
class AggregateQuerySnapshot {
/**
* @internal
*
* @param _query The query that was executed to produce this result.
* @param _readTime The time this snapshot was read.
* @param _data The results of the aggregations performed over the underlying
* query.
*/
constructor(_query, _readTime, _data) {
this._query = _query;
this._readTime = _readTime;
this._data = _data;
}
/** The query that was executed to produce this result. */
get query() {
return this._query;
}
/** The time this snapshot was read. */
get readTime() {
return this._readTime;
}
/**
* Returns the results of the aggregations performed over the underlying
* query.
*
* The keys of the returned object will be the same as those of the
* `AggregateSpec` object specified to the aggregation method, and the
* values will be the corresponding aggregation result.
*
* @returns The results of the aggregations performed over the underlying
* query.
*/
data() {
return this._data;
}
/**
* Compares this object with the given object for equality.
*
* Two `AggregateQuerySnapshot` instances are considered "equal" if they
* have the same data and their underlying queries compare "equal" using
* `AggregateQuery.isEqual()`.
*
* @param other The object to compare to this object for equality.
* @return `true` if this object is "equal" to the given object, as
* defined above, or `false` otherwise.
*/
isEqual(other) {
if (this === other) {
return true;
}
if (!(other instanceof AggregateQuerySnapshot)) {
return false;
}
// Since the read time is different on every read, we explicitly ignore all
// document metadata in this comparison, just like
// `DocumentSnapshot.isEqual()` does.
if (!this.query.isEqual(other.query)) {
return false;
}
return deepEqual(this._data, other._data);
}
}
exports.AggregateQuerySnapshot = AggregateQuerySnapshot;
//# sourceMappingURL=aggregate-query-snapshot.js.map

View File

@@ -0,0 +1,119 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as protos from '../../protos/firestore_v1_proto_api';
import api = protos.google.firestore.v1;
import * as firestore from '@google-cloud/firestore';
import { AggregateSpec } from '../aggregate';
import { Timestamp } from '../timestamp';
import { ExplainResults } from '../query-profile';
import { AggregateQuerySnapshot } from './aggregate-query-snapshot';
import { Query } from './query';
import { Readable } from 'stream';
import { QueryResponse, QuerySnapshotResponse } from './types';
/**
* A query that calculates aggregations over an underlying query.
*/
export declare class AggregateQuery<AggregateSpecType extends AggregateSpec, AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> implements firestore.AggregateQuery<AggregateSpecType, AppModelType, DbModelType> {
private readonly _query;
private readonly _aggregates;
private readonly clientAliasToServerAliasMap;
private readonly serverAliasToClientAliasMap;
/**
* @internal
* @param _query The query whose aggregations will be calculated by this
* object.
* @param _aggregates The aggregations that will be performed by this query.
*/
constructor(_query: Query<AppModelType, DbModelType>, _aggregates: AggregateSpecType);
/** The query whose aggregations will be calculated by this object. */
get query(): Query<AppModelType, DbModelType>;
/**
* Executes this query.
*
* @return A promise that will be resolved with the results of the query.
*/
get(): Promise<AggregateQuerySnapshot<AggregateSpecType, AppModelType, DbModelType>>;
/**
* Internal get() method that accepts an optional transaction options and
* returns a snapshot with transaction and explain metadata.
*
* @private
* @internal
* @param transactionOrReadTime A transaction ID, options to start a new
* transaction, or timestamp to use as read time.
*/
_get(transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions): Promise<QuerySnapshotResponse<AggregateQuerySnapshot<AggregateSpecType, AppModelType, DbModelType>>>;
/**
* Internal get() method that accepts an optional transaction id, and returns
* transaction metadata.
*
* @private
* @internal
* @param transactionOrReadTime A transaction ID, options to start a new
* transaction, or timestamp to use as read time.
*/
_getResponse(transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, explainOptions?: firestore.ExplainOptions): Promise<QueryResponse<AggregateQuerySnapshot<AggregateSpecType, AppModelType, DbModelType>>>;
/**
* Internal streaming method that accepts an optional transaction ID.
*
* BEWARE: If `transactionOrReadTime` is `ITransactionOptions`, then the first
* response in the stream will be a transaction response.
*
* @private
* @internal
* @param transactionOrReadTime A transaction ID, options to start a new
* transaction, or timestamp to use as read time.
* @param explainOptions Options to use for explaining the query (if any).
* @returns A stream of document results optionally preceded by a transaction response.
*/
_stream(transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, explainOptions?: firestore.ExplainOptions): Readable;
/**
* Internal method to decode values within result.
* @private
*/
private decodeResult;
/**
* Internal method for serializing a query to its RunAggregationQuery proto
* representation with an optional transaction id.
*
* @private
* @internal
* @returns Serialized JSON for the query.
*/
toProto(transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, explainOptions?: firestore.ExplainOptions): api.IRunAggregationQueryRequest;
/**
* Compares this object with the given object for equality.
*
* This object is considered "equal" to the other object if and only if
* `other` performs the same aggregations as this `AggregateQuery` and
* the underlying Query of `other` compares equal to that of this object
* using `Query.isEqual()`.
*
* @param other The object to compare to this object for equality.
* @return `true` if this object is "equal" to the given object, as
* defined above, or `false` otherwise.
*/
isEqual(other: firestore.AggregateQuery<AggregateSpecType, AppModelType, DbModelType>): boolean;
/**
* Plans and optionally executes this query. Returns a Promise that will be
* resolved with the planner information, statistics from the query
* execution (if any), and the query results (if any).
*
* @return A Promise that will be resolved with the planner information,
* statistics from the query execution (if any), and the query results (if any).
*/
explain(options?: firestore.ExplainOptions): Promise<ExplainResults<AggregateQuerySnapshot<AggregateSpecType, AppModelType, DbModelType>>>;
}

View File

@@ -0,0 +1,291 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AggregateQuery = void 0;
const assert = require("assert");
const deepEqual = require("fast-deep-equal");
const aggregate_1 = require("../aggregate");
const timestamp_1 = require("../timestamp");
const util_1 = require("../util");
const query_profile_1 = require("../query-profile");
const logger_1 = require("../logger");
const aggregate_query_snapshot_1 = require("./aggregate-query-snapshot");
const stream_1 = require("stream");
const trace_util_1 = require("../telemetry/trace-util");
/**
* A query that calculates aggregations over an underlying query.
*/
class AggregateQuery {
/**
* @internal
* @param _query The query whose aggregations will be calculated by this
* object.
* @param _aggregates The aggregations that will be performed by this query.
*/
constructor(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_query, _aggregates) {
this._query = _query;
this._aggregates = _aggregates;
this.clientAliasToServerAliasMap = {};
this.serverAliasToClientAliasMap = {};
// Client-side aliases may be too long and exceed the 1500-byte string size limit.
// Such long strings do not need to be transferred over the wire either.
// The client maps the user's alias to a short form alias and send that to the server.
let aggregationNum = 0;
for (const clientAlias in this._aggregates) {
if (Object.prototype.hasOwnProperty.call(this._aggregates, clientAlias)) {
const serverAlias = `aggregate_${aggregationNum++}`;
this.clientAliasToServerAliasMap[clientAlias] = serverAlias;
this.serverAliasToClientAliasMap[serverAlias] = clientAlias;
}
}
}
/** The query whose aggregations will be calculated by this object. */
get query() {
return this._query;
}
/**
* Executes this query.
*
* @return A promise that will be resolved with the results of the query.
*/
async get() {
return this._query._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_AGGREGATION_QUERY_GET, async () => {
const { result } = await this._get();
return result;
});
}
/**
* Internal get() method that accepts an optional transaction options and
* returns a snapshot with transaction and explain metadata.
*
* @private
* @internal
* @param transactionOrReadTime A transaction ID, options to start a new
* transaction, or timestamp to use as read time.
*/
async _get(transactionOrReadTime) {
const response = await this._getResponse(transactionOrReadTime);
if (!response.result) {
throw new Error('No AggregateQuery results');
}
return response;
}
/**
* Internal get() method that accepts an optional transaction id, and returns
* transaction metadata.
*
* @private
* @internal
* @param transactionOrReadTime A transaction ID, options to start a new
* transaction, or timestamp to use as read time.
*/
_getResponse(transactionOrReadTime, explainOptions) {
// Capture the error stack to preserve stack tracing across async calls.
const stack = Error().stack;
return new Promise((resolve, reject) => {
const output = {};
const stream = this._stream(transactionOrReadTime, explainOptions);
stream.on('error', err => {
reject((0, util_1.wrapError)(err, stack));
});
stream.on('data', (data) => {
if (data.transaction) {
output.transaction = data.transaction;
}
if (data.explainMetrics) {
output.explainMetrics = data.explainMetrics;
}
if (data.result) {
output.result = data.result;
}
});
stream.on('end', () => {
stream.destroy();
resolve(output);
});
});
}
/**
* Internal streaming method that accepts an optional transaction ID.
*
* BEWARE: If `transactionOrReadTime` is `ITransactionOptions`, then the first
* response in the stream will be a transaction response.
*
* @private
* @internal
* @param transactionOrReadTime A transaction ID, options to start a new
* transaction, or timestamp to use as read time.
* @param explainOptions Options to use for explaining the query (if any).
* @returns A stream of document results optionally preceded by a transaction response.
*/
_stream(transactionOrReadTime, explainOptions) {
const tag = (0, util_1.requestTag)();
const firestore = this._query.firestore;
const stream = new stream_1.Transform({
objectMode: true,
transform: (proto, enc, callback) => {
var _a;
const output = {};
// Proto comes with zero-length buffer by default
if ((_a = proto.transaction) === null || _a === void 0 ? void 0 : _a.length) {
output.transaction = proto.transaction;
}
if (proto.explainMetrics) {
output.explainMetrics = query_profile_1.ExplainMetrics._fromProto(proto.explainMetrics, firestore._serializer);
}
if (proto.result) {
const readTime = timestamp_1.Timestamp.fromProto(proto.readTime);
const data = this.decodeResult(proto.result);
output.result = new aggregate_query_snapshot_1.AggregateQuerySnapshot(this, readTime, data);
}
callback(undefined, output);
},
});
firestore
.initializeIfNeeded(tag)
.then(async () => {
// `toProto()` might throw an exception. We rely on the behavior of an
// async function to convert this exception into the rejected Promise we
// catch below.
const request = this.toProto(transactionOrReadTime, explainOptions);
const backendStream = await firestore.requestStream('runAggregationQuery',
/* bidirectional= */ false, request, tag);
stream.on('close', () => {
backendStream.resume();
backendStream.end();
});
backendStream.on('error', err => {
// TODO(group-by) When group-by queries are supported for aggregates
// consider implementing retries if the stream is making progress
// receiving results for groups. See the use of lastReceivedDocument
// in the retry strategy for runQuery.
// Also note that explain queries should not be retried.
backendStream.unpipe(stream);
(0, logger_1.logger)('AggregateQuery._stream', tag, 'AggregateQuery failed with stream error:', err);
this._query._firestore._traceUtil
.currentSpan()
.addEvent(`${trace_util_1.SPAN_NAME_RUN_AGGREGATION_QUERY}: Error.`, {
'error.message': err.message,
});
stream.destroy(err);
});
backendStream.resume();
backendStream.pipe(stream);
})
.catch(e => stream.destroy(e));
return stream;
}
/**
* Internal method to decode values within result.
* @private
*/
decodeResult(proto) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data = {};
const fields = proto.aggregateFields;
if (fields) {
const serializer = this._query.firestore._serializer;
for (const prop of Object.keys(fields)) {
const alias = this.serverAliasToClientAliasMap[prop];
assert(alias !== null && alias !== undefined, `'${prop}' not present in server-client alias mapping.`);
if (this._aggregates[alias] === undefined) {
throw new Error(`Unexpected alias [${prop}] in result aggregate result`);
}
data[alias] = serializer.decodeValue(fields[prop]);
}
}
return data;
}
/**
* Internal method for serializing a query to its RunAggregationQuery proto
* representation with an optional transaction id.
*
* @private
* @internal
* @returns Serialized JSON for the query.
*/
toProto(transactionOrReadTime, explainOptions) {
const queryProto = this._query.toProto();
const runQueryRequest = {
parent: queryProto.parent,
structuredAggregationQuery: {
structuredQuery: queryProto.structuredQuery,
aggregations: (0, util_1.mapToArray)(this._aggregates, (aggregate, clientAlias) => {
const serverAlias = this.clientAliasToServerAliasMap[clientAlias];
assert(serverAlias !== null && serverAlias !== undefined, `'${clientAlias}' not present in client-server alias mapping.`);
return new aggregate_1.Aggregate(serverAlias, aggregate.aggregateType, aggregate._field).toProto();
}),
},
};
if (transactionOrReadTime instanceof Uint8Array) {
runQueryRequest.transaction = transactionOrReadTime;
}
else if (transactionOrReadTime instanceof timestamp_1.Timestamp) {
runQueryRequest.readTime = transactionOrReadTime.toProto().timestampValue;
}
else if (transactionOrReadTime) {
runQueryRequest.newTransaction = transactionOrReadTime;
}
if (explainOptions) {
runQueryRequest.explainOptions = explainOptions;
}
return runQueryRequest;
}
/**
* Compares this object with the given object for equality.
*
* This object is considered "equal" to the other object if and only if
* `other` performs the same aggregations as this `AggregateQuery` and
* the underlying Query of `other` compares equal to that of this object
* using `Query.isEqual()`.
*
* @param other The object to compare to this object for equality.
* @return `true` if this object is "equal" to the given object, as
* defined above, or `false` otherwise.
*/
isEqual(other) {
if (this === other) {
return true;
}
if (!(other instanceof AggregateQuery)) {
return false;
}
if (!this.query.isEqual(other.query)) {
return false;
}
return deepEqual(this._aggregates, other._aggregates);
}
/**
* Plans and optionally executes this query. Returns a Promise that will be
* resolved with the planner information, statistics from the query
* execution (if any), and the query results (if any).
*
* @return A Promise that will be resolved with the planner information,
* statistics from the query execution (if any), and the query results (if any).
*/
async explain(options) {
const { result, explainMetrics } = await this._getResponse(undefined, options || {});
if (!explainMetrics) {
throw new Error('No explain results');
}
return new query_profile_1.ExplainResults(explainMetrics, result || null);
}
}
exports.AggregateQuery = AggregateQuery;
//# sourceMappingURL=aggregate-query.js.map

View File

@@ -0,0 +1,150 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import { ResourcePath } from '../path';
import { Query } from './query';
import Firestore from '../index';
import { DocumentReference } from './document-reference';
/**
* A CollectionReference object can be used for adding documents, getting
* document references, and querying for documents (using the methods
* inherited from [Query]{@link Query}).
*
* @class CollectionReference
* @extends Query
*/
export declare class CollectionReference<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> extends Query<AppModelType, DbModelType> implements firestore.CollectionReference<AppModelType, DbModelType> {
/**
* @private
*
* @param firestore The Firestore Database client.
* @param path The Path of this collection.
*/
constructor(firestore: Firestore, path: ResourcePath, converter?: firestore.FirestoreDataConverter<AppModelType, DbModelType>);
/**
* Returns a resource path for this collection.
* @private
* @internal
*/
get _resourcePath(): ResourcePath;
/**
* The last path element of the referenced collection.
*
* @type {string}
* @name CollectionReference#id
* @readonly
*
* @example
* ```
* let collectionRef = firestore.collection('col/doc/subcollection');
* console.log(`ID of the subcollection: ${collectionRef.id}`);
* ```
*/
get id(): string;
/**
* A reference to the containing Document if this is a subcollection, else
* null.
*
* @type {DocumentReference|null}
* @name CollectionReference#parent
* @readonly
*
* @example
* ```
* let collectionRef = firestore.collection('col/doc/subcollection');
* let documentRef = collectionRef.parent;
* console.log(`Parent name: ${documentRef.path}`);
* ```
*/
get parent(): DocumentReference | null;
/**
* A string representing the path of the referenced collection (relative
* to the root of the database).
*
* @type {string}
* @name CollectionReference#path
* @readonly
*
* @example
* ```
* let collectionRef = firestore.collection('col/doc/subcollection');
* console.log(`Path of the subcollection: ${collectionRef.path}`);
* ```
*/
get path(): string;
/**
* Retrieves the list of documents in this collection.
*
* The document references returned may include references to "missing
* documents", i.e. document locations that have no document present but
* which contain subcollections with documents. Attempting to read such a
* document reference (e.g. via `.get()` or `.onSnapshot()`) will return a
* `DocumentSnapshot` whose `.exists` property is false.
*
* @return {Promise<DocumentReference[]>} The list of documents in this
* collection.
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* return collectionRef.listDocuments().then(documentRefs => {
* return firestore.getAll(...documentRefs);
* }).then(documentSnapshots => {
* for (let documentSnapshot of documentSnapshots) {
* if (documentSnapshot.exists) {
* console.log(`Found document with data: ${documentSnapshot.id}`);
* } else {
* console.log(`Found missing document: ${documentSnapshot.id}`);
* }
* }
* });
* ```
*/
listDocuments(): Promise<Array<DocumentReference<AppModelType, DbModelType>>>;
doc(): DocumentReference<AppModelType, DbModelType>;
doc(documentPath: string): DocumentReference<AppModelType, DbModelType>;
/**
* Add a new document to this collection with the specified data, assigning
* it a document ID automatically.
*
* @param {DocumentData} data An Object containing the data for the new
* document.
* @throws {Error} If the provided input is not a valid Firestore document.
* @returns {Promise.<DocumentReference>} A Promise resolved with a
* [DocumentReference]{@link DocumentReference} pointing to the
* newly created document.
*
* @example
* ```
* let collectionRef = firestore.collection('col');
* collectionRef.add({foo: 'bar'}).then(documentReference => {
* console.log(`Added document with name: ${documentReference.id}`);
* });
* ```
*/
add(data: firestore.WithFieldValue<AppModelType>): Promise<DocumentReference<AppModelType, DbModelType>>;
/**
* Returns true if this `CollectionReference` is equal to the provided value.
*
* @param {*} other The value to compare against.
* @return {boolean} true if this `CollectionReference` is equal to the
* provided value.
*/
isEqual(other: firestore.CollectionReference<AppModelType, DbModelType>): boolean;
withConverter(converter: null): CollectionReference;
withConverter<NewAppModelType, NewDbModelType extends firestore.DocumentData = firestore.DocumentData>(converter: firestore.FirestoreDataConverter<NewAppModelType, NewDbModelType>): CollectionReference<NewAppModelType, NewDbModelType>;
}

View File

@@ -0,0 +1,287 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CollectionReference = void 0;
const path_1 = require("../path");
const util_1 = require("../util");
const write_batch_1 = require("../write-batch");
const types_1 = require("../types");
const query_1 = require("./query");
const document_reference_1 = require("./document-reference");
const query_options_1 = require("./query-options");
const trace_util_1 = require("../telemetry/trace-util");
/**
* A CollectionReference object can be used for adding documents, getting
* document references, and querying for documents (using the methods
* inherited from [Query]{@link Query}).
*
* @class CollectionReference
* @extends Query
*/
class CollectionReference extends query_1.Query {
/**
* @private
*
* @param firestore The Firestore Database client.
* @param path The Path of this collection.
*/
constructor(firestore, path, converter) {
super(firestore, query_options_1.QueryOptions.forCollectionQuery(path, converter));
}
/**
* Returns a resource path for this collection.
* @private
* @internal
*/
get _resourcePath() {
return this._queryOptions.parentPath.append(this._queryOptions.collectionId);
}
/**
* The last path element of the referenced collection.
*
* @type {string}
* @name CollectionReference#id
* @readonly
*
* @example
* ```
* let collectionRef = firestore.collection('col/doc/subcollection');
* console.log(`ID of the subcollection: ${collectionRef.id}`);
* ```
*/
get id() {
return this._queryOptions.collectionId;
}
/**
* A reference to the containing Document if this is a subcollection, else
* null.
*
* @type {DocumentReference|null}
* @name CollectionReference#parent
* @readonly
*
* @example
* ```
* let collectionRef = firestore.collection('col/doc/subcollection');
* let documentRef = collectionRef.parent;
* console.log(`Parent name: ${documentRef.path}`);
* ```
*/
get parent() {
if (this._queryOptions.parentPath.isDocument) {
return new document_reference_1.DocumentReference(this.firestore, this._queryOptions.parentPath);
}
return null;
}
/**
* A string representing the path of the referenced collection (relative
* to the root of the database).
*
* @type {string}
* @name CollectionReference#path
* @readonly
*
* @example
* ```
* let collectionRef = firestore.collection('col/doc/subcollection');
* console.log(`Path of the subcollection: ${collectionRef.path}`);
* ```
*/
get path() {
return this._resourcePath.relativeName;
}
/**
* Retrieves the list of documents in this collection.
*
* The document references returned may include references to "missing
* documents", i.e. document locations that have no document present but
* which contain subcollections with documents. Attempting to read such a
* document reference (e.g. via `.get()` or `.onSnapshot()`) will return a
* `DocumentSnapshot` whose `.exists` property is false.
*
* @return {Promise<DocumentReference[]>} The list of documents in this
* collection.
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* return collectionRef.listDocuments().then(documentRefs => {
* return firestore.getAll(...documentRefs);
* }).then(documentSnapshots => {
* for (let documentSnapshot of documentSnapshots) {
* if (documentSnapshot.exists) {
* console.log(`Found document with data: ${documentSnapshot.id}`);
* } else {
* console.log(`Found missing document: ${documentSnapshot.id}`);
* }
* }
* });
* ```
*/
listDocuments() {
return this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_COL_REF_LIST_DOCUMENTS, () => {
const tag = (0, util_1.requestTag)();
return this.firestore.initializeIfNeeded(tag).then(() => {
const parentPath = this._queryOptions.parentPath.toQualifiedResourcePath(this.firestore.projectId, this.firestore.databaseId);
const request = {
parent: parentPath.formattedName,
collectionId: this.id,
showMissing: true,
mask: { fieldPaths: [] },
};
return this.firestore
.request('listDocuments', request, tag)
.then(documents => {
// Note that the backend already orders these documents by name,
// so we do not need to manually sort them.
return documents.map(doc => {
const path = path_1.QualifiedResourcePath.fromSlashSeparatedString(doc.name);
return this.doc(path.id);
});
});
});
});
}
/**
* Gets a [DocumentReference]{@link DocumentReference} instance that
* refers to the document at the specified path. If no path is specified, an
* automatically-generated unique ID will be used for the returned
* DocumentReference.
*
* @param {string=} documentPath A slash-separated path to a document.
* @returns {DocumentReference} The `DocumentReference`
* instance.
*
* @example
* ```
* let collectionRef = firestore.collection('col');
* let documentRefWithName = collectionRef.doc('doc');
* let documentRefWithAutoId = collectionRef.doc();
* console.log(`Reference with name: ${documentRefWithName.path}`);
* console.log(`Reference with auto-id: ${documentRefWithAutoId.path}`);
* ```
*/
doc(documentPath) {
if (arguments.length === 0) {
documentPath = (0, util_1.autoId)();
}
else {
(0, path_1.validateResourcePath)('documentPath', documentPath);
}
const path = this._resourcePath.append(documentPath);
if (!path.isDocument) {
throw new Error(`Value for argument "documentPath" must point to a document, but was "${documentPath}". Your path does not contain an even number of components.`);
}
return new document_reference_1.DocumentReference(this.firestore, path, this._queryOptions.converter);
}
/**
* Add a new document to this collection with the specified data, assigning
* it a document ID automatically.
*
* @param {DocumentData} data An Object containing the data for the new
* document.
* @throws {Error} If the provided input is not a valid Firestore document.
* @returns {Promise.<DocumentReference>} A Promise resolved with a
* [DocumentReference]{@link DocumentReference} pointing to the
* newly created document.
*
* @example
* ```
* let collectionRef = firestore.collection('col');
* collectionRef.add({foo: 'bar'}).then(documentReference => {
* console.log(`Added document with name: ${documentReference.id}`);
* });
* ```
*/
add(data) {
return this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_COL_REF_ADD, () => {
const firestoreData = this._queryOptions.converter.toFirestore(data);
(0, write_batch_1.validateDocumentData)('data', firestoreData,
/*allowDeletes=*/ false, this._allowUndefined);
const documentRef = this.doc();
return documentRef.create(data).then(() => documentRef);
});
}
/**
* Returns true if this `CollectionReference` is equal to the provided value.
*
* @param {*} other The value to compare against.
* @return {boolean} true if this `CollectionReference` is equal to the
* provided value.
*/
isEqual(other) {
return (this === other ||
(other instanceof CollectionReference && super.isEqual(other)));
}
/**
* Applies a custom data converter to this CollectionReference, allowing you
* to use your own custom model objects with Firestore. When you call add() on
* the returned CollectionReference instance, the provided converter will
* convert between Firestore data of type `NewDbModelType` and your custom
* type `NewAppModelType`.
*
* Using the converter allows you to specify generic type arguments when
* storing and retrieving objects from Firestore.
*
* Passing in `null` as the converter parameter removes the current
* converter.
*
* @example
* ```
* class Post {
* constructor(readonly title: string, readonly author: string) {}
*
* toString(): string {
* return this.title + ', by ' + this.author;
* }
* }
*
* const postConverter = {
* toFirestore(post: Post): FirebaseFirestore.DocumentData {
* return {title: post.title, author: post.author};
* },
* fromFirestore(
* snapshot: FirebaseFirestore.QueryDocumentSnapshot
* ): Post {
* const data = snapshot.data();
* return new Post(data.title, data.author);
* }
* };
*
* const postSnap = await Firestore()
* .collection('posts')
* .withConverter(postConverter)
* .doc().get();
* const post = postSnap.data();
* if (post !== undefined) {
* post.title; // string
* post.toString(); // Should be defined
* post.someNonExistentProperty; // TS error
* }
*
* ```
* @param {FirestoreDataConverter | null} converter Converts objects to and
* from Firestore. Passing in `null` removes the current converter.
* @return A CollectionReference that uses the provided converter.
*/
withConverter(converter) {
return new CollectionReference(this.firestore, this._resourcePath, converter !== null && converter !== void 0 ? converter : (0, types_1.defaultConverter)());
}
}
exports.CollectionReference = CollectionReference;
//# sourceMappingURL=collection-reference.js.map

View File

@@ -0,0 +1,30 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as protos from '../../protos/firestore_v1_proto_api';
import api = protos.google.firestore.v1;
import { FilterInternal } from './filter-internal';
import { FieldFilterInternal } from './field-filter-internal';
export declare class CompositeFilterInternal extends FilterInternal {
private filters;
private operator;
constructor(filters: FilterInternal[], operator: api.StructuredQuery.CompositeFilter.Operator);
private memoizedFlattenedFilters;
getFilters(): FilterInternal[];
isConjunction(): boolean;
getFlattenedFilters(): FieldFilterInternal[];
toProto(): api.StructuredQuery.IFilter;
isEqual(other: FilterInternal): boolean;
}

View File

@@ -0,0 +1,67 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CompositeFilterInternal = void 0;
const filter_internal_1 = require("./filter-internal");
class CompositeFilterInternal extends filter_internal_1.FilterInternal {
constructor(filters, operator) {
super();
this.filters = filters;
this.operator = operator;
// Memoized list of all field filters that can be found by traversing the tree of filters
// contained in this composite filter.
this.memoizedFlattenedFilters = null;
}
getFilters() {
return this.filters;
}
isConjunction() {
return this.operator === 'AND';
}
getFlattenedFilters() {
if (this.memoizedFlattenedFilters !== null) {
return this.memoizedFlattenedFilters;
}
this.memoizedFlattenedFilters = this.filters.reduce((allFilters, subfilter) => allFilters.concat(subfilter.getFlattenedFilters()), []);
return this.memoizedFlattenedFilters;
}
toProto() {
if (this.filters.length === 1) {
return this.filters[0].toProto();
}
const proto = {
compositeFilter: {
op: this.operator,
filters: this.filters.map(filter => filter.toProto()),
},
};
return proto;
}
isEqual(other) {
if (other instanceof CompositeFilterInternal) {
const otherFilters = other.getFilters();
return (this.operator === other.operator &&
this.getFilters().length === other.getFilters().length &&
this.getFilters().every((filter, index) => filter.isEqual(otherFilters[index])));
}
else {
return false;
}
}
}
exports.CompositeFilterInternal = CompositeFilterInternal;
//# sourceMappingURL=composite-filter-internal.js.map

View File

@@ -0,0 +1,39 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as protos from '../../protos/firestore_v1_proto_api';
import api = protos.google.firestore.v1;
/**
* The direction of a `Query.orderBy()` clause is specified as 'desc' or 'asc'
* (descending or ascending).
*
* @private
* @internal
*/
export declare const directionOperators: {
[k: string]: api.StructuredQuery.Direction;
};
/**
* Filter conditions in a `Query.where()` clause are specified using the
* strings '<', '<=', '==', '!=', '>=', '>', 'array-contains', 'in', 'not-in',
* and 'array-contains-any'.
*
* @private
* @internal
*/
export declare const comparisonOperators: {
[k: string]: api.StructuredQuery.FieldFilter.Operator;
};
export declare const NOOP_MESSAGE: unique symbol;

View File

@@ -0,0 +1,51 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.NOOP_MESSAGE = exports.comparisonOperators = exports.directionOperators = void 0;
/**
* The direction of a `Query.orderBy()` clause is specified as 'desc' or 'asc'
* (descending or ascending).
*
* @private
* @internal
*/
exports.directionOperators = {
asc: 'ASCENDING',
desc: 'DESCENDING',
};
/**
* Filter conditions in a `Query.where()` clause are specified using the
* strings '<', '<=', '==', '!=', '>=', '>', 'array-contains', 'in', 'not-in',
* and 'array-contains-any'.
*
* @private
* @internal
*/
exports.comparisonOperators = {
'<': 'LESS_THAN',
'<=': 'LESS_THAN_OR_EQUAL',
'==': 'EQUAL',
'!=': 'NOT_EQUAL',
'>': 'GREATER_THAN',
'>=': 'GREATER_THAN_OR_EQUAL',
'array-contains': 'ARRAY_CONTAINS',
in: 'IN',
'not-in': 'NOT_IN',
'array-contains-any': 'ARRAY_CONTAINS_ANY',
};
exports.NOOP_MESSAGE = Symbol('a noop message');
//# sourceMappingURL=constants.js.map

View File

@@ -0,0 +1,332 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as protos from '../../protos/firestore_v1_proto_api';
import api = protos.google.firestore.v1;
import * as firestore from '@google-cloud/firestore';
import Firestore, { DocumentSnapshot, WriteResult } from '../index';
import { ResourcePath } from '../path';
import { Serializable } from '../serializer';
import { CollectionReference } from './collection-reference';
/**
* A DocumentReference refers to a document location in a Firestore database
* and can be used to write, read, or listen to the location. The document at
* the referenced location may or may not exist. A DocumentReference can
* also be used to create a
* [CollectionReference]{@link CollectionReference} to a
* subcollection.
*
* @class DocumentReference
*/
export declare class DocumentReference<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> implements Serializable, firestore.DocumentReference<AppModelType, DbModelType> {
private readonly _firestore;
/**
* @private
* @internal
**/
readonly _path: ResourcePath;
/**
* @internal
* @private
**/
readonly _converter: firestore.FirestoreDataConverter<AppModelType, DbModelType>;
/**
* @private
* @internal
* @param _firestore The Firestore Database client.
* @param _path The Path of this reference.
* @param _converter The converter to use when serializing data.
*/
constructor(_firestore: Firestore,
/**
* @private
* @internal
**/
_path: ResourcePath,
/**
* @internal
* @private
**/
_converter?: firestore.FirestoreDataConverter<AppModelType, DbModelType>);
/**
* The string representation of the DocumentReference's location.
* @private
* @internal
* @type {string}
* @name DocumentReference#formattedName
*/
get formattedName(): string;
/**
* The [Firestore]{@link Firestore} instance for the Firestore
* database (useful for performing transactions, etc.).
*
* @type {Firestore}
* @name DocumentReference#firestore
* @readonly
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* collectionRef.add({foo: 'bar'}).then(documentReference => {
* let firestore = documentReference.firestore;
* console.log(`Root location for document is ${firestore.formattedName}`);
* });
* ```
*/
get firestore(): Firestore;
/**
* A string representing the path of the referenced document (relative
* to the root of the database).
*
* @type {string}
* @name DocumentReference#path
* @readonly
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* collectionRef.add({foo: 'bar'}).then(documentReference => {
* console.log(`Added document at '${documentReference.path}'`);
* });
* ```
*/
get path(): string;
/**
* The last path element of the referenced document.
*
* @type {string}
* @name DocumentReference#id
* @readonly
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* collectionRef.add({foo: 'bar'}).then(documentReference => {
* console.log(`Added document with name '${documentReference.id}'`);
* });
* ```
*/
get id(): string;
/**
* Returns a resource path for this document.
* @private
* @internal
*/
get _resourcePath(): ResourcePath;
/**
* A reference to the collection to which this DocumentReference belongs.
*
* @name DocumentReference#parent
* @type {CollectionReference}
* @readonly
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
* let collectionRef = documentRef.parent;
*
* collectionRef.where('foo', '==', 'bar').get().then(results => {
* console.log(`Found ${results.size} matches in parent collection`);
* }):
* ```
*/
get parent(): CollectionReference<AppModelType, DbModelType>;
/**
* Reads the document referred to by this DocumentReference.
*
* @returns {Promise.<DocumentSnapshot>} A Promise resolved with a
* DocumentSnapshot for the retrieved document on success. For missing
* documents, DocumentSnapshot.exists will be false. If the get() fails for
* other reasons, the Promise will be rejected.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(documentSnapshot => {
* if (documentSnapshot.exists) {
* console.log('Document retrieved successfully.');
* }
* });
* ```
*/
get(): Promise<DocumentSnapshot<AppModelType, DbModelType>>;
/**
* Gets a [CollectionReference]{@link CollectionReference} instance
* that refers to the collection at the specified path.
*
* @param {string} collectionPath A slash-separated path to a collection.
* @returns {CollectionReference} A reference to the new
* subcollection.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
* let subcollection = documentRef.collection('subcollection');
* console.log(`Path to subcollection: ${subcollection.path}`);
* ```
*/
collection(collectionPath: string): CollectionReference;
/**
* Fetches the subcollections that are direct children of this document.
*
* @returns {Promise.<Array.<CollectionReference>>} A Promise that resolves
* with an array of CollectionReferences.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.listCollections().then(collections => {
* for (let collection of collections) {
* console.log(`Found subcollection with id: ${collection.id}`);
* }
* });
* ```
*/
listCollections(): Promise<Array<CollectionReference>>;
/**
* Create a document with the provided object values. This will fail the write
* if a document exists at its location.
*
* @param {DocumentData} data An object that contains the fields and data to
* serialize as the document.
* @throws {Error} If the provided input is not a valid Firestore document or if the document already exists.
* @returns {Promise.<WriteResult>} A Promise that resolves with the
* write time of this create.
*
* @example
* ```
* let documentRef = firestore.collection('col').doc();
*
* documentRef.create({foo: 'bar'}).then((res) => {
* console.log(`Document created at ${res.updateTime}`);
* }).catch((err) => {
* console.log(`Failed to create document: ${err}`);
* });
* ```
*/
create(data: firestore.WithFieldValue<AppModelType>): Promise<WriteResult>;
/**
* Deletes the document referred to by this `DocumentReference`.
*
* A delete for a non-existing document is treated as a success (unless
* lastUptimeTime is provided).
*
* @param {Precondition=} precondition A precondition to enforce for this
* delete.
* @param {Timestamp=} precondition.lastUpdateTime If set, enforces that the
* document was last updated at lastUpdateTime. Fails the delete if the
* document was last updated at a different time.
* @param {boolean=} precondition.exists If set, enforces that the target
* document must or must not exist.
* @returns {Promise.<WriteResult>} A Promise that resolves with the
* delete time.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.delete().then(() => {
* console.log('Document successfully deleted.');
* });
* ```
*/
delete(precondition?: firestore.Precondition): Promise<WriteResult>;
set(data: firestore.PartialWithFieldValue<AppModelType>, options: firestore.SetOptions): Promise<WriteResult>;
set(data: firestore.WithFieldValue<AppModelType>): Promise<WriteResult>;
/**
* Updates fields in the document referred to by this DocumentReference.
* If the document doesn't yet exist, the update fails and the returned
* Promise will be rejected.
*
* The update() method accepts either an object with field paths encoded as
* keys and field values encoded as values, or a variable number of arguments
* that alternate between field paths and field values.
*
* A Precondition restricting this update can be specified as the last
* argument.
*
* @param {UpdateData|string|FieldPath} dataOrField An object containing the
* fields and values with which to update the document or the path of the
* first field to update.
* @param {
* ...(*|string|FieldPath|Precondition)} preconditionOrValues An alternating
* list of field paths and values to update or a Precondition to restrict
* this update.
* @throws {Error} If the provided input is not valid Firestore data.
* @returns {Promise.<WriteResult>} A Promise that resolves once the
* data has been successfully written to the backend.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.update({foo: 'bar'}).then(res => {
* console.log(`Document updated at ${res.updateTime}`);
* });
* ```
*/
update(dataOrField: firestore.UpdateData<DbModelType> | string | firestore.FieldPath, ...preconditionOrValues: Array<unknown | string | firestore.FieldPath | firestore.Precondition>): Promise<WriteResult>;
/**
* Attaches a listener for DocumentSnapshot events.
*
* @param {documentSnapshotCallback} onNext A callback to be called every
* time a new `DocumentSnapshot` is available.
* @param {errorCallback=} onError A callback to be called if the listen fails
* or is cancelled. No further callbacks will occur. If unset, errors will be
* logged to the console.
*
* @returns {function()} An unsubscribe function that can be called to cancel
* the snapshot listener.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* let unsubscribe = documentRef.onSnapshot(documentSnapshot => {
* if (documentSnapshot.exists) {
* console.log(documentSnapshot.data());
* }
* }, err => {
* console.log(`Encountered error: ${err}`);
* });
*
* // Remove this listener.
* unsubscribe();
* ```
*/
onSnapshot(onNext: (snapshot: firestore.DocumentSnapshot<AppModelType, DbModelType>) => void, onError?: (error: Error) => void): () => void;
/**
* Returns true if this `DocumentReference` is equal to the provided value.
*
* @param {*} other The value to compare against.
* @return {boolean} true if this `DocumentReference` is equal to the provided
* value.
*/
isEqual(other: firestore.DocumentReference<AppModelType, DbModelType>): boolean;
/**
* Converts this DocumentReference to the Firestore Proto representation.
*
* @private
* @internal
*/
toProto(): api.IValue;
withConverter(converter: null): DocumentReference;
withConverter<NewAppModelType, NewDbModelType extends firestore.DocumentData = firestore.DocumentData>(converter: firestore.FirestoreDataConverter<NewAppModelType, NewDbModelType>): DocumentReference<NewAppModelType, NewDbModelType>;
}

View File

@@ -0,0 +1,521 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DocumentReference = void 0;
const index_1 = require("../index");
const path_1 = require("../path");
const types_1 = require("../types");
const collection_reference_1 = require("./collection-reference");
const util_1 = require("../util");
const validate_1 = require("../validate");
const document_1 = require("../document");
const trace_util_1 = require("../telemetry/trace-util");
/**
* A DocumentReference refers to a document location in a Firestore database
* and can be used to write, read, or listen to the location. The document at
* the referenced location may or may not exist. A DocumentReference can
* also be used to create a
* [CollectionReference]{@link CollectionReference} to a
* subcollection.
*
* @class DocumentReference
*/
class DocumentReference {
/**
* @private
* @internal
* @param _firestore The Firestore Database client.
* @param _path The Path of this reference.
* @param _converter The converter to use when serializing data.
*/
constructor(_firestore,
/**
* @private
* @internal
**/
_path,
/**
* @internal
* @private
**/
_converter = (0, types_1.defaultConverter)()) {
this._firestore = _firestore;
this._path = _path;
this._converter = _converter;
}
/**
* The string representation of the DocumentReference's location.
* @private
* @internal
* @type {string}
* @name DocumentReference#formattedName
*/
get formattedName() {
const projectId = this.firestore.projectId;
const databaseId = this.firestore.databaseId;
return this._path.toQualifiedResourcePath(projectId, databaseId)
.formattedName;
}
/**
* The [Firestore]{@link Firestore} instance for the Firestore
* database (useful for performing transactions, etc.).
*
* @type {Firestore}
* @name DocumentReference#firestore
* @readonly
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* collectionRef.add({foo: 'bar'}).then(documentReference => {
* let firestore = documentReference.firestore;
* console.log(`Root location for document is ${firestore.formattedName}`);
* });
* ```
*/
get firestore() {
return this._firestore;
}
/**
* A string representing the path of the referenced document (relative
* to the root of the database).
*
* @type {string}
* @name DocumentReference#path
* @readonly
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* collectionRef.add({foo: 'bar'}).then(documentReference => {
* console.log(`Added document at '${documentReference.path}'`);
* });
* ```
*/
get path() {
return this._path.relativeName;
}
/**
* The last path element of the referenced document.
*
* @type {string}
* @name DocumentReference#id
* @readonly
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* collectionRef.add({foo: 'bar'}).then(documentReference => {
* console.log(`Added document with name '${documentReference.id}'`);
* });
* ```
*/
get id() {
return this._path.id;
}
/**
* Returns a resource path for this document.
* @private
* @internal
*/
get _resourcePath() {
return this._path;
}
/**
* A reference to the collection to which this DocumentReference belongs.
*
* @name DocumentReference#parent
* @type {CollectionReference}
* @readonly
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
* let collectionRef = documentRef.parent;
*
* collectionRef.where('foo', '==', 'bar').get().then(results => {
* console.log(`Found ${results.size} matches in parent collection`);
* }):
* ```
*/
get parent() {
return new collection_reference_1.CollectionReference(this._firestore, this._path.parent(), this._converter);
}
/**
* Reads the document referred to by this DocumentReference.
*
* @returns {Promise.<DocumentSnapshot>} A Promise resolved with a
* DocumentSnapshot for the retrieved document on success. For missing
* documents, DocumentSnapshot.exists will be false. If the get() fails for
* other reasons, the Promise will be rejected.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(documentSnapshot => {
* if (documentSnapshot.exists) {
* console.log('Document retrieved successfully.');
* }
* });
* ```
*/
get() {
return this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_DOC_REF_GET, () => {
return this._firestore.getAll(this).then(([result]) => result);
});
}
/**
* Gets a [CollectionReference]{@link CollectionReference} instance
* that refers to the collection at the specified path.
*
* @param {string} collectionPath A slash-separated path to a collection.
* @returns {CollectionReference} A reference to the new
* subcollection.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
* let subcollection = documentRef.collection('subcollection');
* console.log(`Path to subcollection: ${subcollection.path}`);
* ```
*/
collection(collectionPath) {
(0, path_1.validateResourcePath)('collectionPath', collectionPath);
const path = this._path.append(collectionPath);
if (!path.isCollection) {
throw new Error(`Value for argument "collectionPath" must point to a collection, but was "${collectionPath}". Your path does not contain an odd number of components.`);
}
return new collection_reference_1.CollectionReference(this._firestore, path);
}
/**
* Fetches the subcollections that are direct children of this document.
*
* @returns {Promise.<Array.<CollectionReference>>} A Promise that resolves
* with an array of CollectionReferences.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.listCollections().then(collections => {
* for (let collection of collections) {
* console.log(`Found subcollection with id: ${collection.id}`);
* }
* });
* ```
*/
listCollections() {
return this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_DOC_REF_LIST_COLLECTIONS, () => {
const tag = (0, util_1.requestTag)();
return this.firestore.initializeIfNeeded(tag).then(() => {
const request = {
parent: this.formattedName,
};
return this._firestore
.request('listCollectionIds', request, tag)
.then(collectionIds => {
const collections = [];
// We can just sort this list using the default comparator since it
// will only contain collection ids.
collectionIds.sort();
for (const collectionId of collectionIds) {
collections.push(this.collection(collectionId));
}
return collections;
});
});
});
}
/**
* Create a document with the provided object values. This will fail the write
* if a document exists at its location.
*
* @param {DocumentData} data An object that contains the fields and data to
* serialize as the document.
* @throws {Error} If the provided input is not a valid Firestore document or if the document already exists.
* @returns {Promise.<WriteResult>} A Promise that resolves with the
* write time of this create.
*
* @example
* ```
* let documentRef = firestore.collection('col').doc();
*
* documentRef.create({foo: 'bar'}).then((res) => {
* console.log(`Document created at ${res.updateTime}`);
* }).catch((err) => {
* console.log(`Failed to create document: ${err}`);
* });
* ```
*/
create(data) {
return this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_DOC_REF_CREATE, () => {
const writeBatch = new index_1.WriteBatch(this._firestore);
return writeBatch
.create(this, data)
.commit()
.then(([writeResult]) => writeResult);
});
}
/**
* Deletes the document referred to by this `DocumentReference`.
*
* A delete for a non-existing document is treated as a success (unless
* lastUptimeTime is provided).
*
* @param {Precondition=} precondition A precondition to enforce for this
* delete.
* @param {Timestamp=} precondition.lastUpdateTime If set, enforces that the
* document was last updated at lastUpdateTime. Fails the delete if the
* document was last updated at a different time.
* @param {boolean=} precondition.exists If set, enforces that the target
* document must or must not exist.
* @returns {Promise.<WriteResult>} A Promise that resolves with the
* delete time.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.delete().then(() => {
* console.log('Document successfully deleted.');
* });
* ```
*/
delete(precondition) {
return this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_DOC_REF_DELETE, () => {
const writeBatch = new index_1.WriteBatch(this._firestore);
return writeBatch
.delete(this, precondition)
.commit()
.then(([writeResult]) => writeResult);
});
}
/**
* Writes to the document referred to by this DocumentReference. If the
* document does not yet exist, it will be created. If you pass
* [SetOptions]{@link SetOptions}, the provided data can be merged into an
* existing document.
*
* @param {T|Partial<AppModelType>} data A map of the fields and values for
* the document.
* @param {SetOptions=} options An object to configure the set behavior.
* @param {boolean=} options.merge If true, set() merges the values specified
* in its data argument. Fields omitted from this set() call remain untouched.
* If your input sets any field to an empty map, all nested fields are
* overwritten.
* @param {Array.<string|FieldPath>=} options.mergeFields If provided,
* set() only replaces the specified field paths. Any field path that is not
* specified is ignored and remains untouched. If your input sets any field to
* an empty map, all nested fields are overwritten.
* @throws {Error} If the provided input is not a valid Firestore document.
* @returns {Promise.<WriteResult>} A Promise that resolves with the
* write time of this set.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.set({foo: 'bar'}).then(res => {
* console.log(`Document written at ${res.updateTime}`);
* });
* ```
*/
set(data, options) {
return this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_DOC_REF_SET, () => {
let writeBatch = new index_1.WriteBatch(this._firestore);
if (options) {
writeBatch = writeBatch.set(this, data, options);
}
else {
writeBatch = writeBatch.set(this, data);
}
return writeBatch.commit().then(([writeResult]) => writeResult);
});
}
/**
* Updates fields in the document referred to by this DocumentReference.
* If the document doesn't yet exist, the update fails and the returned
* Promise will be rejected.
*
* The update() method accepts either an object with field paths encoded as
* keys and field values encoded as values, or a variable number of arguments
* that alternate between field paths and field values.
*
* A Precondition restricting this update can be specified as the last
* argument.
*
* @param {UpdateData|string|FieldPath} dataOrField An object containing the
* fields and values with which to update the document or the path of the
* first field to update.
* @param {
* ...(*|string|FieldPath|Precondition)} preconditionOrValues An alternating
* list of field paths and values to update or a Precondition to restrict
* this update.
* @throws {Error} If the provided input is not valid Firestore data.
* @returns {Promise.<WriteResult>} A Promise that resolves once the
* data has been successfully written to the backend.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.update({foo: 'bar'}).then(res => {
* console.log(`Document updated at ${res.updateTime}`);
* });
* ```
*/
update(dataOrField, ...preconditionOrValues) {
return this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_DOC_REF_UPDATE, () => {
// eslint-disable-next-line prefer-rest-params
(0, validate_1.validateMinNumberOfArguments)('DocumentReference.update', arguments, 1);
const writeBatch = new index_1.WriteBatch(this._firestore);
return writeBatch
.update(this, dataOrField, ...preconditionOrValues)
.commit()
.then(([writeResult]) => writeResult);
});
}
/**
* Attaches a listener for DocumentSnapshot events.
*
* @param {documentSnapshotCallback} onNext A callback to be called every
* time a new `DocumentSnapshot` is available.
* @param {errorCallback=} onError A callback to be called if the listen fails
* or is cancelled. No further callbacks will occur. If unset, errors will be
* logged to the console.
*
* @returns {function()} An unsubscribe function that can be called to cancel
* the snapshot listener.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* let unsubscribe = documentRef.onSnapshot(documentSnapshot => {
* if (documentSnapshot.exists) {
* console.log(documentSnapshot.data());
* }
* }, err => {
* console.log(`Encountered error: ${err}`);
* });
*
* // Remove this listener.
* unsubscribe();
* ```
*/
onSnapshot(onNext, onError) {
(0, validate_1.validateFunction)('onNext', onNext);
(0, validate_1.validateFunction)('onError', onError, { optional: true });
const watch = new (require('../watch').DocumentWatch)(this.firestore, this);
return watch.onSnapshot((readTime, size, docs) => {
for (const document of docs()) {
if (document.ref.path === this.path) {
onNext(document);
return;
}
}
// The document is missing.
const ref = new DocumentReference(this._firestore, this._path, this._converter);
const document = new document_1.DocumentSnapshotBuilder(ref);
document.readTime = readTime;
onNext(document.build());
}, onError || console.error);
}
/**
* Returns true if this `DocumentReference` is equal to the provided value.
*
* @param {*} other The value to compare against.
* @return {boolean} true if this `DocumentReference` is equal to the provided
* value.
*/
isEqual(other) {
return (this === other ||
(other instanceof DocumentReference &&
this._firestore === other._firestore &&
this._path.isEqual(other._path) &&
this._converter === other._converter));
}
/**
* Converts this DocumentReference to the Firestore Proto representation.
*
* @private
* @internal
*/
toProto() {
return { referenceValue: this.formattedName };
}
/**
* Applies a custom data converter to this DocumentReference, allowing you to
* use your own custom model objects with Firestore. When you call set(),
* get(), etc. on the returned DocumentReference instance, the provided
* converter will convert between Firestore data of type `NewDbModelType` and
* your custom type `NewAppModelType`.
*
* Using the converter allows you to specify generic type arguments when
* storing and retrieving objects from Firestore.
*
* Passing in `null` as the converter parameter removes the current
* converter.
*
* @example
* ```
* class Post {
* constructor(readonly title: string, readonly author: string) {}
*
* toString(): string {
* return this.title + ', by ' + this.author;
* }
* }
*
* const postConverter = {
* toFirestore(post: Post): FirebaseFirestore.DocumentData {
* return {title: post.title, author: post.author};
* },
* fromFirestore(
* snapshot: FirebaseFirestore.QueryDocumentSnapshot
* ): Post {
* const data = snapshot.data();
* return new Post(data.title, data.author);
* }
* };
*
* const postSnap = await Firestore()
* .collection('posts')
* .withConverter(postConverter)
* .doc().get();
* const post = postSnap.data();
* if (post !== undefined) {
* post.title; // string
* post.toString(); // Should be defined
* post.someNonExistentProperty; // TS error
* }
*
* ```
* @param {FirestoreDataConverter | null} converter Converts objects to and
* from Firestore. Passing in `null` removes the current converter.
* @return A DocumentReference that uses the provided converter.
*/
withConverter(converter) {
return new DocumentReference(this.firestore, this._path, converter !== null && converter !== void 0 ? converter : (0, types_1.defaultConverter)());
}
}
exports.DocumentReference = DocumentReference;
//# sourceMappingURL=document-reference.js.map

View File

@@ -0,0 +1,58 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as protos from '../../protos/firestore_v1_proto_api';
import api = protos.google.firestore.v1;
import { FilterInternal } from './filter-internal';
import { Serializer } from '../serializer';
import { FieldPath } from '../path';
/**
* A field constraint for a Query where clause.
*
* @private
* @internal
* @class
*/
export declare class FieldFilterInternal extends FilterInternal {
private readonly serializer;
readonly field: FieldPath;
private readonly op;
private readonly value;
getFlattenedFilters(): FieldFilterInternal[];
getFilters(): FilterInternal[];
/**
* @param serializer The Firestore serializer
* @param field The path of the property value to compare.
* @param op A comparison operation.
* @param value The value to which to compare the field for inclusion in a
* query.
*/
constructor(serializer: Serializer, field: FieldPath, op: api.StructuredQuery.FieldFilter.Operator, value: unknown);
/**
* Returns whether this FieldFilter uses an equals comparison.
*
* @private
* @internal
*/
isInequalityFilter(): boolean;
/**
* Generates the proto representation for this field filter.
*
* @private
* @internal
*/
toProto(): api.StructuredQuery.IFilter;
isEqual(other: FilterInternal): boolean;
}

View File

@@ -0,0 +1,113 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FieldFilterInternal = void 0;
const deepEqual = require("fast-deep-equal");
const filter_internal_1 = require("./filter-internal");
/**
* A field constraint for a Query where clause.
*
* @private
* @internal
* @class
*/
class FieldFilterInternal extends filter_internal_1.FilterInternal {
getFlattenedFilters() {
return [this];
}
getFilters() {
return [this];
}
/**
* @param serializer The Firestore serializer
* @param field The path of the property value to compare.
* @param op A comparison operation.
* @param value The value to which to compare the field for inclusion in a
* query.
*/
constructor(serializer, field, op, value) {
super();
this.serializer = serializer;
this.field = field;
this.op = op;
this.value = value;
}
/**
* Returns whether this FieldFilter uses an equals comparison.
*
* @private
* @internal
*/
isInequalityFilter() {
switch (this.op) {
case 'GREATER_THAN':
case 'GREATER_THAN_OR_EQUAL':
case 'LESS_THAN':
case 'LESS_THAN_OR_EQUAL':
case 'NOT_EQUAL':
case 'NOT_IN':
return true;
default:
return false;
}
}
/**
* Generates the proto representation for this field filter.
*
* @private
* @internal
*/
toProto() {
if (typeof this.value === 'number' && isNaN(this.value)) {
return {
unaryFilter: {
field: {
fieldPath: this.field.formattedName,
},
op: this.op === 'EQUAL' ? 'IS_NAN' : 'IS_NOT_NAN',
},
};
}
if (this.value === null) {
return {
unaryFilter: {
field: {
fieldPath: this.field.formattedName,
},
op: this.op === 'EQUAL' ? 'IS_NULL' : 'IS_NOT_NULL',
},
};
}
return {
fieldFilter: {
field: {
fieldPath: this.field.formattedName,
},
op: this.op,
value: this.serializer.encodeValue(this.value),
},
};
}
isEqual(other) {
return (other instanceof FieldFilterInternal &&
this.field.isEqual(other.field) &&
this.op === other.op &&
deepEqual(this.value, other.value));
}
}
exports.FieldFilterInternal = FieldFilterInternal;
//# sourceMappingURL=field-filter-internal.js.map

View File

@@ -0,0 +1,42 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as protos from '../../protos/firestore_v1_proto_api';
import api = protos.google.firestore.v1;
import { FieldPath } from '../path';
/**
* A Query order-by field.
*
* @private
* @internal
* @class
*/
export declare class FieldOrder {
readonly field: FieldPath;
readonly direction: api.StructuredQuery.Direction;
/**
* @param field The name of a document field (member) on which to order query
* results.
* @param direction One of 'ASCENDING' (default) or 'DESCENDING' to
* set the ordering direction to ascending or descending, respectively.
*/
constructor(field: FieldPath, direction?: api.StructuredQuery.Direction);
/**
* Generates the proto representation for this field order.
* @private
* @internal
*/
toProto(): api.StructuredQuery.IOrder;
}

View File

@@ -0,0 +1,52 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FieldOrder = void 0;
/**
* A Query order-by field.
*
* @private
* @internal
* @class
*/
class FieldOrder {
/**
* @param field The name of a document field (member) on which to order query
* results.
* @param direction One of 'ASCENDING' (default) or 'DESCENDING' to
* set the ordering direction to ascending or descending, respectively.
*/
constructor(field, direction = 'ASCENDING') {
this.field = field;
this.direction = direction;
}
/**
* Generates the proto representation for this field order.
* @private
* @internal
*/
toProto() {
return {
field: {
fieldPath: this.field.formattedName,
},
direction: this.direction,
};
}
}
exports.FieldOrder = FieldOrder;
//# sourceMappingURL=field-order.js.map

View File

@@ -0,0 +1,26 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import { Filter } from '../filter';
import { FieldFilterInternal } from './field-filter-internal';
export declare abstract class FilterInternal {
/** Returns a list of all field filters that are contained within this filter */
abstract getFlattenedFilters(): FieldFilterInternal[];
/** Returns a list of all filters that are contained within this filter */
abstract getFilters(): FilterInternal[];
/** Returns the proto representation of this filter */
abstract toProto(): Filter;
abstract isEqual(other: FilterInternal): boolean;
}

View File

@@ -0,0 +1,22 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FilterInternal = void 0;
class FilterInternal {
}
exports.FilterInternal = FilterInternal;
//# sourceMappingURL=filter-internal.js.map

View File

@@ -0,0 +1,68 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import { DocumentReference } from './document-reference';
/**
* Validates the input string as a field order direction.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param op Order direction to validate.
* @throws when the direction is invalid
* @return a validated input value, which may be different from the provided
* value.
*/
export declare function validateQueryOrder(arg: string, op: unknown): firestore.OrderByDirection | undefined;
/**
* Validates the input string as a field comparison operator.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param op Field comparison operator to validate.
* @param fieldValue Value that is used in the filter.
* @throws when the comparison operation is invalid
* @return a validated input value, which may be different from the provided
* value.
*/
export declare function validateQueryOperator(arg: string | number, op: unknown, fieldValue: unknown): firestore.WhereFilterOp;
/**
* Validates that 'value' is a DocumentReference.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param value The argument to validate.
* @return the DocumentReference if valid
*/
export declare function validateDocumentReference<AppModelType, DbModelType extends firestore.DocumentData>(arg: string | number, value: firestore.DocumentReference<AppModelType, DbModelType>): DocumentReference<AppModelType, DbModelType>;
/**
* Validates that 'value' can be used as a query value.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param value The argument to validate.
* @param allowUndefined Whether to allow nested properties that are `undefined`.
*/
export declare function validateQueryValue(arg: string | number, value: unknown, allowUndefined: boolean): void;
/**
* Returns the first non-undefined value or `undefined` if no such value exists.
* @private
* @internal
*/
export declare function coalesce<T>(...values: Array<T | undefined>): T | undefined;

View File

@@ -0,0 +1,112 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateQueryOrder = validateQueryOrder;
exports.validateQueryOperator = validateQueryOperator;
exports.validateDocumentReference = validateDocumentReference;
exports.validateQueryValue = validateQueryValue;
exports.coalesce = coalesce;
const validate_1 = require("../validate");
const serializer_1 = require("../serializer");
const document_reference_1 = require("./document-reference");
const constants_1 = require("./constants");
/**
* Validates the input string as a field order direction.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param op Order direction to validate.
* @throws when the direction is invalid
* @return a validated input value, which may be different from the provided
* value.
*/
function validateQueryOrder(arg, op) {
// For backwards compatibility, we support both lower and uppercase values.
op = typeof op === 'string' ? op.toLowerCase() : op;
(0, validate_1.validateEnumValue)(arg, op, Object.keys(constants_1.directionOperators), { optional: true });
return op;
}
/**
* Validates the input string as a field comparison operator.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param op Field comparison operator to validate.
* @param fieldValue Value that is used in the filter.
* @throws when the comparison operation is invalid
* @return a validated input value, which may be different from the provided
* value.
*/
function validateQueryOperator(arg, op, fieldValue) {
// For backwards compatibility, we support both `=` and `==` for "equals".
if (op === '=') {
op = '==';
}
(0, validate_1.validateEnumValue)(arg, op, Object.keys(constants_1.comparisonOperators));
if (typeof fieldValue === 'number' &&
isNaN(fieldValue) &&
op !== '==' &&
op !== '!=') {
throw new Error("Invalid query. You can only perform '==' and '!=' comparisons on NaN.");
}
if (fieldValue === null && op !== '==' && op !== '!=') {
throw new Error("Invalid query. You can only perform '==' and '!=' comparisons on Null.");
}
return op;
}
/**
* Validates that 'value' is a DocumentReference.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param value The argument to validate.
* @return the DocumentReference if valid
*/
function validateDocumentReference(arg, value) {
if (!(value instanceof document_reference_1.DocumentReference)) {
throw new Error((0, validate_1.invalidArgumentMessage)(arg, 'DocumentReference'));
}
return value;
}
/**
* Validates that 'value' can be used as a query value.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param value The argument to validate.
* @param allowUndefined Whether to allow nested properties that are `undefined`.
*/
function validateQueryValue(arg, value, allowUndefined) {
(0, serializer_1.validateUserInput)(arg, value, 'query constraint', {
allowDeletes: 'none',
allowTransforms: false,
allowUndefined,
});
}
/**
* Returns the first non-undefined value or `undefined` if no such value exists.
* @private
* @internal
*/
function coalesce(...values) {
return values.find(value => value !== undefined);
}
//# sourceMappingURL=helpers.js.map

View File

@@ -0,0 +1,76 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as protos from '../../protos/firestore_v1_proto_api';
import api = protos.google.firestore.v1;
import * as firestore from '@google-cloud/firestore';
import { ResourcePath } from '../path';
import { FilterInternal } from './filter-internal';
import { FieldOrder } from './field-order';
import { LimitType, QueryCursor } from './types';
/**
* Internal class representing custom Query options.
*
* These options are immutable. Modified options can be created using `with()`.
* @private
* @internal
*/
export declare class QueryOptions<AppModelType, DbModelType extends firestore.DocumentData> {
readonly parentPath: ResourcePath;
readonly collectionId: string;
readonly converter: firestore.FirestoreDataConverter<AppModelType, DbModelType>;
readonly allDescendants: boolean;
readonly filters: FilterInternal[];
readonly fieldOrders: FieldOrder[];
readonly startAt?: QueryCursor | undefined;
readonly endAt?: QueryCursor | undefined;
readonly limit?: number | undefined;
readonly limitType?: LimitType | undefined;
readonly offset?: number | undefined;
readonly projection?: api.StructuredQuery.IProjection | undefined;
readonly kindless: boolean;
readonly requireConsistency: boolean;
constructor(parentPath: ResourcePath, collectionId: string, converter: firestore.FirestoreDataConverter<AppModelType, DbModelType>, allDescendants: boolean, filters: FilterInternal[], fieldOrders: FieldOrder[], startAt?: QueryCursor | undefined, endAt?: QueryCursor | undefined, limit?: number | undefined, limitType?: LimitType | undefined, offset?: number | undefined, projection?: api.StructuredQuery.IProjection | undefined, kindless?: boolean, requireConsistency?: boolean);
/**
* Returns query options for a collection group query.
* @private
* @internal
*/
static forCollectionGroupQuery<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData>(collectionId: string, converter?: firestore.FirestoreDataConverter<AppModelType, DbModelType>): QueryOptions<AppModelType, DbModelType>;
/**
* Returns query options for a single-collection query.
* @private
* @internal
*/
static forCollectionQuery<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData>(collectionRef: ResourcePath, converter?: firestore.FirestoreDataConverter<AppModelType, DbModelType>): QueryOptions<AppModelType, DbModelType>;
/**
* Returns query options for a query that fetches all descendants under the
* specified reference.
*
* @private
* @internal
*/
static forKindlessAllDescendants(parent: ResourcePath, id: string, requireConsistency?: boolean): QueryOptions<firestore.DocumentData, firestore.DocumentData>;
/**
* Returns the union of the current and the provided options.
* @private
* @internal
*/
with(settings: Partial<Omit<QueryOptions<AppModelType, DbModelType>, 'converter'>>): QueryOptions<AppModelType, DbModelType>;
withConverter<NewAppModelType, NewDbModelType extends firestore.DocumentData = firestore.DocumentData>(converter: firestore.FirestoreDataConverter<NewAppModelType, NewDbModelType>): QueryOptions<NewAppModelType, NewDbModelType>;
hasFieldOrders(): boolean;
isEqual(other: QueryOptions<AppModelType, DbModelType>): boolean;
private filtersEqual;
}

View File

@@ -0,0 +1,141 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueryOptions = void 0;
const deepEqual = require("fast-deep-equal");
const path_1 = require("../path");
const types_1 = require("../types");
const helpers_1 = require("./helpers");
/**
* Internal class representing custom Query options.
*
* These options are immutable. Modified options can be created using `with()`.
* @private
* @internal
*/
class QueryOptions {
constructor(parentPath, collectionId, converter, allDescendants, filters, fieldOrders, startAt, endAt, limit, limitType, offset, projection,
// Whether to select all documents under `parentPath`. By default, only
// collections that match `collectionId` are selected.
kindless = false,
// Whether to require consistent documents when restarting the query. By
// default, restarting the query uses the readTime offset of the original
// query to provide consistent results.
requireConsistency = true) {
this.parentPath = parentPath;
this.collectionId = collectionId;
this.converter = converter;
this.allDescendants = allDescendants;
this.filters = filters;
this.fieldOrders = fieldOrders;
this.startAt = startAt;
this.endAt = endAt;
this.limit = limit;
this.limitType = limitType;
this.offset = offset;
this.projection = projection;
this.kindless = kindless;
this.requireConsistency = requireConsistency;
}
/**
* Returns query options for a collection group query.
* @private
* @internal
*/
static forCollectionGroupQuery(collectionId, converter = (0, types_1.defaultConverter)()) {
return new QueryOptions(
/*parentPath=*/ path_1.ResourcePath.EMPTY, collectionId, converter,
/*allDescendants=*/ true,
/*fieldFilters=*/ [],
/*fieldOrders=*/ []);
}
/**
* Returns query options for a single-collection query.
* @private
* @internal
*/
static forCollectionQuery(collectionRef, converter = (0, types_1.defaultConverter)()) {
return new QueryOptions(collectionRef.parent(), collectionRef.id, converter,
/*allDescendants=*/ false,
/*fieldFilters=*/ [],
/*fieldOrders=*/ []);
}
/**
* Returns query options for a query that fetches all descendants under the
* specified reference.
*
* @private
* @internal
*/
static forKindlessAllDescendants(parent, id, requireConsistency = true) {
let options = new QueryOptions(parent, id, (0, types_1.defaultConverter)(),
/*allDescendants=*/ true,
/*fieldFilters=*/ [],
/*fieldOrders=*/ []);
options = options.with({
kindless: true,
requireConsistency,
});
return options;
}
/**
* Returns the union of the current and the provided options.
* @private
* @internal
*/
with(settings) {
return new QueryOptions((0, helpers_1.coalesce)(settings.parentPath, this.parentPath), (0, helpers_1.coalesce)(settings.collectionId, this.collectionId), this.converter, (0, helpers_1.coalesce)(settings.allDescendants, this.allDescendants), (0, helpers_1.coalesce)(settings.filters, this.filters), (0, helpers_1.coalesce)(settings.fieldOrders, this.fieldOrders), (0, helpers_1.coalesce)(settings.startAt, this.startAt), (0, helpers_1.coalesce)(settings.endAt, this.endAt), (0, helpers_1.coalesce)(settings.limit, this.limit), (0, helpers_1.coalesce)(settings.limitType, this.limitType), (0, helpers_1.coalesce)(settings.offset, this.offset), (0, helpers_1.coalesce)(settings.projection, this.projection), (0, helpers_1.coalesce)(settings.kindless, this.kindless), (0, helpers_1.coalesce)(settings.requireConsistency, this.requireConsistency));
}
withConverter(converter) {
return new QueryOptions(this.parentPath, this.collectionId, converter, this.allDescendants, this.filters, this.fieldOrders, this.startAt, this.endAt, this.limit, this.limitType, this.offset, this.projection);
}
hasFieldOrders() {
return this.fieldOrders.length > 0;
}
isEqual(other) {
if (this === other) {
return true;
}
return (other instanceof QueryOptions &&
this.parentPath.isEqual(other.parentPath) &&
this.filtersEqual(other.filters) &&
this.collectionId === other.collectionId &&
this.converter === other.converter &&
this.allDescendants === other.allDescendants &&
this.limit === other.limit &&
this.offset === other.offset &&
deepEqual(this.fieldOrders, other.fieldOrders) &&
deepEqual(this.startAt, other.startAt) &&
deepEqual(this.endAt, other.endAt) &&
deepEqual(this.projection, other.projection) &&
this.kindless === other.kindless &&
this.requireConsistency === other.requireConsistency);
}
filtersEqual(other) {
if (this.filters.length !== other.length) {
return false;
}
for (let i = 0; i < other.length; i++) {
if (!this.filters[i].isEqual(other[i])) {
return false;
}
}
return true;
}
}
exports.QueryOptions = QueryOptions;
//# sourceMappingURL=query-options.js.map

View File

@@ -0,0 +1,199 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import { QueryDocumentSnapshot } from '../document';
import { DocumentChange } from '../document-change';
import { Timestamp } from '../timestamp';
import { Query } from './query';
/**
* A QuerySnapshot contains zero or more
* [QueryDocumentSnapshot]{@link QueryDocumentSnapshot} objects
* representing the results of a query. The documents can be accessed as an
* array via the [documents]{@link QuerySnapshot#documents} property
* or enumerated using the [forEach]{@link QuerySnapshot#forEach}
* method. The number of documents can be determined via the
* [empty]{@link QuerySnapshot#empty} and
* [size]{@link QuerySnapshot#size} properties.
*
* @class QuerySnapshot
*/
export declare class QuerySnapshot<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> implements firestore.QuerySnapshot<AppModelType, DbModelType> {
private readonly _query;
private readonly _readTime;
private readonly _size;
private _materializedDocs;
private _materializedChanges;
private _docs;
private _changes;
/**
* @private
*
* @param _query The originating query.
* @param _readTime The time when this query snapshot was obtained.
* @param _size The number of documents in the result set.
* @param docs A callback returning a sorted array of documents matching
* this query
* @param changes A callback returning a sorted array of document change
* events for this snapshot.
*/
constructor(_query: Query<AppModelType, DbModelType>, _readTime: Timestamp, _size: number, docs: () => Array<QueryDocumentSnapshot<AppModelType, DbModelType>>, changes: () => Array<DocumentChange<AppModelType, DbModelType>>);
/**
* The query on which you called get() or onSnapshot() in order to get this
* QuerySnapshot.
*
* @type {Query}
* @name QuerySnapshot#query
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.limit(10).get().then(querySnapshot => {
* console.log(`Returned first batch of results`);
* let query = querySnapshot.query;
* return query.offset(10).get();
* }).then(() => {
* console.log(`Returned second batch of results`);
* });
* ```
*/
get query(): Query<AppModelType, DbModelType>;
/**
* An array of all the documents in this QuerySnapshot.
*
* @type {Array.<QueryDocumentSnapshot>}
* @name QuerySnapshot#docs
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.get().then(querySnapshot => {
* let docs = querySnapshot.docs;
* for (let doc of docs) {
* console.log(`Document found at path: ${doc.ref.path}`);
* }
* });
* ```
*/
get docs(): Array<QueryDocumentSnapshot<AppModelType, DbModelType>>;
/**
* True if there are no documents in the QuerySnapshot.
*
* @type {boolean}
* @name QuerySnapshot#empty
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.get().then(querySnapshot => {
* if (querySnapshot.empty) {
* console.log('No documents found.');
* }
* });
* ```
*/
get empty(): boolean;
/**
* The number of documents in the QuerySnapshot.
*
* @type {number}
* @name QuerySnapshot#size
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.get().then(querySnapshot => {
* console.log(`Found ${querySnapshot.size} documents.`);
* });
* ```
*/
get size(): number;
/**
* The time this query snapshot was obtained.
*
* @type {Timestamp}
* @name QuerySnapshot#readTime
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.get().then((querySnapshot) => {
* let readTime = querySnapshot.readTime;
* console.log(`Query results returned at '${readTime.toDate()}'`);
* });
* ```
*/
get readTime(): Timestamp;
/**
* Returns an array of the documents changes since the last snapshot. If
* this is the first snapshot, all documents will be in the list as added
* changes.
*
* @return {Array.<DocumentChange>}
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.onSnapshot(querySnapshot => {
* let changes = querySnapshot.docChanges();
* for (let change of changes) {
* console.log(`A document was ${change.type}.`);
* }
* });
* ```
*/
docChanges(): Array<DocumentChange<AppModelType, DbModelType>>;
/**
* Enumerates all of the documents in the QuerySnapshot. This is a convenience
* method for running the same callback on each {@link QueryDocumentSnapshot}
* that is returned.
*
* @param {function} callback A callback to be called with a
* [QueryDocumentSnapshot]{@link QueryDocumentSnapshot} for each document in
* the snapshot.
* @param {*=} thisArg The `this` binding for the callback..
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Document found at path: ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
forEach(callback: (result: firestore.QueryDocumentSnapshot<AppModelType, DbModelType>) => void, thisArg?: unknown): void;
/**
* Returns true if the document data in this `QuerySnapshot` is equal to the
* provided value.
*
* @param {*} other The value to compare against.
* @return {boolean} true if this `QuerySnapshot` is equal to the provided
* value.
*/
isEqual(other: firestore.QuerySnapshot<AppModelType, DbModelType>): boolean;
}

View File

@@ -0,0 +1,254 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuerySnapshot = void 0;
const validate_1 = require("../validate");
const util_1 = require("../util");
/**
* A QuerySnapshot contains zero or more
* [QueryDocumentSnapshot]{@link QueryDocumentSnapshot} objects
* representing the results of a query. The documents can be accessed as an
* array via the [documents]{@link QuerySnapshot#documents} property
* or enumerated using the [forEach]{@link QuerySnapshot#forEach}
* method. The number of documents can be determined via the
* [empty]{@link QuerySnapshot#empty} and
* [size]{@link QuerySnapshot#size} properties.
*
* @class QuerySnapshot
*/
class QuerySnapshot {
/**
* @private
*
* @param _query The originating query.
* @param _readTime The time when this query snapshot was obtained.
* @param _size The number of documents in the result set.
* @param docs A callback returning a sorted array of documents matching
* this query
* @param changes A callback returning a sorted array of document change
* events for this snapshot.
*/
constructor(_query, _readTime, _size, docs, changes) {
this._query = _query;
this._readTime = _readTime;
this._size = _size;
this._materializedDocs = null;
this._materializedChanges = null;
this._docs = null;
this._changes = null;
this._docs = docs;
this._changes = changes;
}
/**
* The query on which you called get() or onSnapshot() in order to get this
* QuerySnapshot.
*
* @type {Query}
* @name QuerySnapshot#query
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.limit(10).get().then(querySnapshot => {
* console.log(`Returned first batch of results`);
* let query = querySnapshot.query;
* return query.offset(10).get();
* }).then(() => {
* console.log(`Returned second batch of results`);
* });
* ```
*/
get query() {
return this._query;
}
/**
* An array of all the documents in this QuerySnapshot.
*
* @type {Array.<QueryDocumentSnapshot>}
* @name QuerySnapshot#docs
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.get().then(querySnapshot => {
* let docs = querySnapshot.docs;
* for (let doc of docs) {
* console.log(`Document found at path: ${doc.ref.path}`);
* }
* });
* ```
*/
get docs() {
if (this._materializedDocs) {
return this._materializedDocs;
}
this._materializedDocs = this._docs();
this._docs = null;
return this._materializedDocs;
}
/**
* True if there are no documents in the QuerySnapshot.
*
* @type {boolean}
* @name QuerySnapshot#empty
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.get().then(querySnapshot => {
* if (querySnapshot.empty) {
* console.log('No documents found.');
* }
* });
* ```
*/
get empty() {
return this._size === 0;
}
/**
* The number of documents in the QuerySnapshot.
*
* @type {number}
* @name QuerySnapshot#size
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.get().then(querySnapshot => {
* console.log(`Found ${querySnapshot.size} documents.`);
* });
* ```
*/
get size() {
return this._size;
}
/**
* The time this query snapshot was obtained.
*
* @type {Timestamp}
* @name QuerySnapshot#readTime
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.get().then((querySnapshot) => {
* let readTime = querySnapshot.readTime;
* console.log(`Query results returned at '${readTime.toDate()}'`);
* });
* ```
*/
get readTime() {
return this._readTime;
}
/**
* Returns an array of the documents changes since the last snapshot. If
* this is the first snapshot, all documents will be in the list as added
* changes.
*
* @return {Array.<DocumentChange>}
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.onSnapshot(querySnapshot => {
* let changes = querySnapshot.docChanges();
* for (let change of changes) {
* console.log(`A document was ${change.type}.`);
* }
* });
* ```
*/
docChanges() {
if (this._materializedChanges) {
return this._materializedChanges;
}
this._materializedChanges = this._changes();
this._changes = null;
return this._materializedChanges;
}
/**
* Enumerates all of the documents in the QuerySnapshot. This is a convenience
* method for running the same callback on each {@link QueryDocumentSnapshot}
* that is returned.
*
* @param {function} callback A callback to be called with a
* [QueryDocumentSnapshot]{@link QueryDocumentSnapshot} for each document in
* the snapshot.
* @param {*=} thisArg The `this` binding for the callback..
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Document found at path: ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
forEach(callback, thisArg) {
(0, validate_1.validateFunction)('callback', callback);
for (const doc of this.docs) {
callback.call(thisArg, doc);
}
}
/**
* Returns true if the document data in this `QuerySnapshot` is equal to the
* provided value.
*
* @param {*} other The value to compare against.
* @return {boolean} true if this `QuerySnapshot` is equal to the provided
* value.
*/
isEqual(other) {
// Since the read time is different on every query read, we explicitly
// ignore all metadata in this comparison.
if (this === other) {
return true;
}
if (!(other instanceof QuerySnapshot)) {
return false;
}
if (this._size !== other._size) {
return false;
}
if (!this._query.isEqual(other._query)) {
return false;
}
if (this._materializedDocs && !this._materializedChanges) {
// If we have only materialized the documents, we compare them first.
return ((0, util_1.isArrayEqual)(this.docs, other.docs) &&
(0, util_1.isArrayEqual)(this.docChanges(), other.docChanges()));
}
// Otherwise, we compare the changes first as we expect there to be fewer.
return ((0, util_1.isArrayEqual)(this.docChanges(), other.docChanges()) &&
(0, util_1.isArrayEqual)(this.docs, other.docs));
}
}
exports.QuerySnapshot = QuerySnapshot;
//# sourceMappingURL=query-snapshot.js.map

View File

@@ -0,0 +1,46 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import { GoogleError } from 'google-gax';
import { Serializer } from '../serializer';
import { Timestamp } from '../timestamp';
import { VectorQuery } from './vector-query';
import { Query } from './query';
import Firestore from '../index';
import { QueryOptions } from './query-options';
import { QueryResponse } from './types';
import * as protos from '../../protos/firestore_v1_proto_api';
import api = protos.google.firestore.v1;
export declare class QueryUtil<AppModelType, DbModelType extends firestore.DocumentData, Template extends Query<AppModelType, DbModelType> | VectorQuery<AppModelType, DbModelType>> {
/** @private */
readonly _firestore: Firestore;
/** @private */
readonly _queryOptions: QueryOptions<AppModelType, DbModelType>;
/** @private */
readonly _serializer: Serializer;
constructor(
/** @private */
_firestore: Firestore,
/** @private */
_queryOptions: QueryOptions<AppModelType, DbModelType>,
/** @private */
_serializer: Serializer);
_getResponse(query: Template, transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, retryWithCursor?: boolean, explainOptions?: firestore.ExplainOptions): Promise<QueryResponse<ReturnType<Template['_createSnapshot']>>>;
_isPermanentRpcError(err: GoogleError, methodName: string): boolean;
_hasRetryTimedOut(methodName: string, startTime: number): boolean;
stream(query: Template): NodeJS.ReadableStream;
_stream(query: Template, transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, retryWithCursor?: boolean, explainOptions?: firestore.ExplainOptions): NodeJS.ReadableStream;
}

View File

@@ -0,0 +1,286 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueryUtil = void 0;
const stream_1 = require("stream");
const timestamp_1 = require("../timestamp");
const document_1 = require("../document");
const util_1 = require("../util");
const document_change_1 = require("../document-change");
const query_profile_1 = require("../query-profile");
const logger_1 = require("../logger");
const vector_query_1 = require("./vector-query");
const types_1 = require("./types");
const constants_1 = require("./constants");
const trace_util_1 = require("../telemetry/trace-util");
class QueryUtil {
constructor(
/** @private */
_firestore,
/** @private */
_queryOptions,
/** @private */
_serializer) {
this._firestore = _firestore;
this._queryOptions = _queryOptions;
this._serializer = _serializer;
}
_getResponse(query, transactionOrReadTime, retryWithCursor = true, explainOptions) {
// Capture the error stack to preserve stack tracing across async calls.
const stack = Error().stack;
return new Promise((resolve, reject) => {
const docs = [];
const output = {};
this._stream(query, transactionOrReadTime, retryWithCursor, explainOptions)
.on('error', err => {
reject((0, util_1.wrapError)(err, stack));
})
.on('data', (data) => {
if (data.transaction) {
output.transaction = data.transaction;
}
if (data.readTime) {
output.readTime = data.readTime;
}
if (data.explainMetrics) {
output.explainMetrics = data.explainMetrics;
}
if (data.document) {
docs.push(data.document);
}
})
.on('end', () => {
if (this._queryOptions.limitType === types_1.LimitType.Last) {
// The results for limitToLast queries need to be flipped since
// we reversed the ordering constraints before sending the query
// to the backend.
docs.reverse();
}
// Only return a snapshot when we have a readTime
// explain queries with analyze !== true will return no documents and no read time
const result = output.readTime
? query._createSnapshot(output.readTime, docs.length, () => docs, () => {
const changes = [];
for (let i = 0; i < docs.length; ++i) {
changes.push(new document_change_1.DocumentChange('added', docs[i], -1, i));
}
return changes;
})
: undefined;
resolve({
transaction: output.transaction,
explainMetrics: output.explainMetrics,
result,
});
});
});
}
// This method exists solely to enable unit tests to mock it.
_isPermanentRpcError(err, methodName) {
return (0, util_1.isPermanentRpcError)(err, methodName);
}
_hasRetryTimedOut(methodName, startTime) {
const totalTimeout = (0, util_1.getTotalTimeout)(methodName);
if (totalTimeout === 0) {
return false;
}
return Date.now() - startTime >= totalTimeout;
}
stream(query) {
if (this._queryOptions.limitType === types_1.LimitType.Last) {
throw new Error('Query results for queries that include limitToLast() ' +
'constraints cannot be streamed. Use Query.get() instead.');
}
const responseStream = this._stream(query);
const transform = new stream_1.Transform({
objectMode: true,
transform(chunk, encoding, callback) {
callback(undefined, chunk.document);
},
});
responseStream.pipe(transform);
responseStream.on('error', e => transform.destroy(e));
return transform;
}
_stream(query, transactionOrReadTime, retryWithCursor = true, explainOptions) {
const tag = (0, util_1.requestTag)();
const startTime = Date.now();
const isExplain = explainOptions !== undefined;
const methodName = 'runQuery';
let numDocumentsReceived = 0;
let lastReceivedDocument = null;
let backendStream;
const stream = new stream_1.Transform({
objectMode: true,
transform: (proto, enc, callback) => {
var _a;
if (proto === constants_1.NOOP_MESSAGE) {
callback(undefined);
return;
}
const output = {};
// Proto comes with zero-length buffer by default
if ((_a = proto.transaction) === null || _a === void 0 ? void 0 : _a.length) {
output.transaction = proto.transaction;
}
if (proto.readTime) {
output.readTime = timestamp_1.Timestamp.fromProto(proto.readTime);
}
if (proto.document) {
const document = this._firestore.snapshot_(proto.document, proto.readTime);
const finalDoc = new document_1.DocumentSnapshotBuilder(document.ref.withConverter(this._queryOptions.converter));
// Recreate the QueryDocumentSnapshot with the DocumentReference
// containing the original converter.
finalDoc.fieldsProto = document._fieldsProto;
finalDoc.readTime = document.readTime;
finalDoc.createTime = document.createTime;
finalDoc.updateTime = document.updateTime;
lastReceivedDocument = finalDoc.build();
output.document = lastReceivedDocument;
}
if (proto.explainMetrics) {
output.explainMetrics = query_profile_1.ExplainMetrics._fromProto(proto.explainMetrics, this._serializer);
}
++numDocumentsReceived;
callback(undefined, output);
if (proto.done) {
(0, logger_1.logger)('QueryUtil._stream', tag, 'Trigger Logical Termination.');
this._firestore._traceUtil
.currentSpan()
.addEvent(`Firestore.${methodName}: Received RunQueryResponse.Done.`);
backendStream.unpipe(stream);
backendStream.resume();
backendStream.end();
stream.end();
}
},
});
this._firestore
.initializeIfNeeded(tag)
.then(async () => {
// `toProto()` might throw an exception. We rely on the behavior of an
// async function to convert this exception into the rejected Promise we
// catch below.
let request = query.toProto(transactionOrReadTime, explainOptions);
let isRetryRequestWithCursor = false;
let streamActive;
do {
streamActive = new util_1.Deferred();
this._firestore._traceUtil
.currentSpan()
.addEvent(trace_util_1.SPAN_NAME_RUN_QUERY, {
[trace_util_1.ATTRIBUTE_KEY_IS_TRANSACTIONAL]: !!request.transaction,
[trace_util_1.ATTRIBUTE_KEY_IS_RETRY_WITH_CURSOR]: isRetryRequestWithCursor,
});
backendStream = await this._firestore.requestStream(methodName,
/* bidirectional= */ false, request, tag);
backendStream.on('error', err => {
backendStream.unpipe(stream);
// If a non-transactional query failed, attempt to restart.
// Transactional queries are retried via the transaction runner.
// Explain queries are not retried with a cursor. That would produce
// incorrect/partial profiling results.
if (!isExplain &&
!transactionOrReadTime &&
!this._isPermanentRpcError(err, methodName)) {
(0, logger_1.logger)('QueryUtil._stream', tag, 'Query failed with retryable stream error:', err);
this._firestore._traceUtil
.currentSpan()
.addEvent(`${trace_util_1.SPAN_NAME_RUN_QUERY}: Retryable Error.`, {
'error.message': err.message,
});
// Enqueue a "no-op" write into the stream and wait for it to be
// read by the downstream consumer. This ensures that all enqueued
// results in the stream are consumed, which will give us an accurate
// value for `lastReceivedDocument`.
stream.write(constants_1.NOOP_MESSAGE, () => {
if (this._hasRetryTimedOut(methodName, startTime)) {
(0, logger_1.logger)('QueryUtil._stream', tag, 'Query failed with retryable stream error but the total retry timeout has exceeded.');
stream.destroy(err);
streamActive.resolve(/* active= */ false);
}
else if (lastReceivedDocument && retryWithCursor) {
if (query instanceof vector_query_1.VectorQuery) {
throw new Error('Unimplemented: Vector query does not support cursors yet.');
}
(0, logger_1.logger)('Query._stream', tag, 'Query failed with retryable stream error and progress was made receiving ' +
'documents, so the stream is being retried.');
isRetryRequestWithCursor = true;
// Restart the query but use the last document we received as
// the query cursor. Note that we do not use backoff here. The
// call to `requestStream()` will backoff should the restart
// fail before delivering any results.
let newQuery;
if (!this._queryOptions.limit) {
newQuery = query;
}
else {
const newLimit = this._queryOptions.limit - numDocumentsReceived;
if (this._queryOptions.limitType === undefined ||
this._queryOptions.limitType === types_1.LimitType.First) {
newQuery = query.limit(newLimit);
}
else {
newQuery = query.limitToLast(newLimit);
}
}
if (this._queryOptions.requireConsistency) {
request = newQuery
.startAfter(lastReceivedDocument)
.toProto(lastReceivedDocument.readTime);
}
else {
request = newQuery
.startAfter(lastReceivedDocument)
.toProto();
}
// Set lastReceivedDocument to null before each retry attempt to ensure the retry makes progress
lastReceivedDocument = null;
streamActive.resolve(/* active= */ true);
}
else {
(0, logger_1.logger)('QueryUtil._stream', tag, `Query failed with retryable stream error however either retryWithCursor="${retryWithCursor}", or ` +
'no progress was made receiving documents, so the stream is being closed.');
stream.destroy(err);
streamActive.resolve(/* active= */ false);
}
});
}
else {
(0, logger_1.logger)('QueryUtil._stream', tag, 'Query failed with stream error:', err);
this._firestore._traceUtil
.currentSpan()
.addEvent(`${trace_util_1.SPAN_NAME_RUN_QUERY}: Error.`, {
'error.message': err.message,
});
stream.destroy(err);
streamActive.resolve(/* active= */ false);
}
});
backendStream.on('end', () => {
streamActive.resolve(/* active= */ false);
});
backendStream.resume();
backendStream.pipe(stream);
} while (await streamActive.promise);
})
.catch(e => stream.destroy(e));
return stream;
}
}
exports.QueryUtil = QueryUtil;
//# sourceMappingURL=query-util.js.map

View File

@@ -0,0 +1,742 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as protos from '../../protos/firestore_v1_proto_api';
import api = protos.google.firestore.v1;
import * as firestore from '@google-cloud/firestore';
import { GoogleError } from 'google-gax';
import { QueryUtil } from './query-util';
import { Firestore, DocumentChange, DocumentSnapshot, FieldPath, Filter, QueryDocumentSnapshot, Timestamp } from '../index';
import { QueryOptions } from './query-options';
import { FieldOrder } from './field-order';
import { FilterInternal } from './filter-internal';
import { FieldFilterInternal } from './field-filter-internal';
import { VectorQueryOptions } from './vector-query-options';
import { QuerySnapshot } from './query-snapshot';
import { Serializer } from '../serializer';
import { ExplainResults } from '../query-profile';
import { CompositeFilter, UnaryFilter } from '../filter';
import { QueryResponse, QuerySnapshotResponse } from './types';
import { AggregateQuery } from './aggregate-query';
import { VectorQuery } from './vector-query';
/**
* A Query refers to a query which you can read or stream from. You can also
* construct refined Query objects by adding filters and ordering.
*
* @class Query
*/
export declare class Query<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> implements firestore.Query<AppModelType, DbModelType> {
/**
* @internal
* @private
**/
readonly _firestore: Firestore;
/**
* @internal
* @private
**/
readonly _queryOptions: QueryOptions<AppModelType, DbModelType>;
/**
* @internal
* @private
**/
readonly _serializer: Serializer;
/**
* @internal
* @private
**/
protected readonly _allowUndefined: boolean;
/**
* @internal
* @private
**/
readonly _queryUtil: QueryUtil<AppModelType, DbModelType, Query<AppModelType, DbModelType>>;
/**
* @internal
* @private
*
* @param _firestore The Firestore Database client.
* @param _queryOptions Options that define the query.
*/
constructor(
/**
* @internal
* @private
**/
_firestore: Firestore,
/**
* @internal
* @private
**/
_queryOptions: QueryOptions<AppModelType, DbModelType>);
/**
* Extracts field values from the DocumentSnapshot based on the provided
* field order.
*
* @private
* @internal
* @param documentSnapshot The document to extract the fields from.
* @param fieldOrders The field order that defines what fields we should
* extract.
* @return {Array.<*>} The field values to use.
*/
static _extractFieldValues(documentSnapshot: DocumentSnapshot, fieldOrders: FieldOrder[]): unknown[];
/**
* The [Firestore]{@link Firestore} instance for the Firestore
* database (useful for performing transactions, etc.).
*
* @type {Firestore}
* @name Query#firestore
* @readonly
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* collectionRef.add({foo: 'bar'}).then(documentReference => {
* let firestore = documentReference.firestore;
* console.log(`Root location for document is ${firestore.formattedName}`);
* });
* ```
*/
get firestore(): Firestore;
/**
* Creates and returns a new [Query]{@link Query} with the additional filter
* that documents must contain the specified field and that its value should
* satisfy the relation constraint provided.
*
* This function returns a new (immutable) instance of the Query (rather than
* modify the existing instance) to impose the filter.
*
* @param {string|FieldPath} fieldPath The name of a property value to compare.
* @param {string} opStr A comparison operation in the form of a string.
* Acceptable operator strings are "<", "<=", "==", "!=", ">=", ">", "array-contains",
* "in", "not-in", and "array-contains-any".
* @param {*} value The value to which to compare the field for inclusion in
* a query.
* @returns {Query} The created Query.
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* collectionRef.where('foo', '==', 'bar').get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
where(fieldPath: string | FieldPath, opStr: firestore.WhereFilterOp, value: unknown): Query<AppModelType, DbModelType>;
/**
* Creates and returns a new [Query]{@link Query} with the additional filter
* that documents should satisfy the relation constraint(s) provided.
*
* This function returns a new (immutable) instance of the Query (rather than
* modify the existing instance) to impose the filter.
*
* @param {Filter} filter A unary or composite filter to apply to the Query.
* @returns {Query} The created Query.
*
* @example
* ```
* let collectionRef = firestore.collection('col');
*
* collectionRef.where(Filter.and(Filter.where('foo', '==', 'bar'), Filter.where('foo', '!=', 'baz'))).get()
* .then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
where(filter: Filter): Query<AppModelType, DbModelType>;
/**
* @internal
* @private
*/
_parseFilter(filter: Filter): FilterInternal;
/**
* @internal
* @private
*/
_parseFieldFilter(fieldFilterData: UnaryFilter): FieldFilterInternal;
/**
* @internal
* @private
*/
_parseCompositeFilter(compositeFilterData: CompositeFilter): FilterInternal;
/**
* Creates and returns a new [Query]{@link Query} instance that applies a
* field mask to the result and returns only the specified subset of fields.
* You can specify a list of field paths to return, or use an empty list to
* only return the references of matching documents.
*
* Queries that contain field masks cannot be listened to via `onSnapshot()`
* listeners.
*
* This function returns a new (immutable) instance of the Query (rather than
* modify the existing instance) to impose the field mask.
*
* @param {...(string|FieldPath)} fieldPaths The field paths to return.
* @returns {Query} The created Query.
*
* @example
* ```
* let collectionRef = firestore.collection('col');
* let documentRef = collectionRef.doc('doc');
*
* return documentRef.set({x:10, y:5}).then(() => {
* return collectionRef.where('x', '>', 5).select('y').get();
* }).then((res) => {
* console.log(`y is ${res.docs[0].get('y')}.`);
* });
* ```
*/
select(...fieldPaths: Array<string | FieldPath>): Query;
/**
* Creates and returns a new [Query]{@link Query} that's additionally sorted
* by the specified field, optionally in descending order instead of
* ascending.
*
* This function returns a new (immutable) instance of the Query (rather than
* modify the existing instance) to impose the field mask.
*
* @param {string|FieldPath} fieldPath The field to sort by.
* @param {string=} directionStr Optional direction to sort by ('asc' or
* 'desc'). If not specified, order will be ascending.
* @returns {Query} The created Query.
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '>', 42);
*
* query.orderBy('foo', 'desc').get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
orderBy(fieldPath: string | firestore.FieldPath, directionStr?: firestore.OrderByDirection): Query<AppModelType, DbModelType>;
/**
* Creates and returns a new [Query]{@link Query} that only returns the
* first matching documents.
*
* This function returns a new (immutable) instance of the Query (rather than
* modify the existing instance) to impose the limit.
*
* @param {number} limit The maximum number of items to return.
* @returns {Query} The created Query.
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '>', 42);
*
* query.limit(1).get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
limit(limit: number): Query<AppModelType, DbModelType>;
/**
* Creates and returns a new [Query]{@link Query} that only returns the
* last matching documents.
*
* You must specify at least one orderBy clause for limitToLast queries,
* otherwise an exception will be thrown during execution.
*
* Results for limitToLast queries cannot be streamed via the `stream()` API.
*
* @param limit The maximum number of items to return.
* @return The created Query.
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '>', 42);
*
* query.limitToLast(1).get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Last matching document is ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
limitToLast(limit: number): Query<AppModelType, DbModelType>;
/**
* Specifies the offset of the returned results.
*
* This function returns a new (immutable) instance of the
* [Query]{@link Query} (rather than modify the existing instance)
* to impose the offset.
*
* @param {number} offset The offset to apply to the Query results
* @returns {Query} The created Query.
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '>', 42);
*
* query.limit(10).offset(20).get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
offset(offset: number): Query<AppModelType, DbModelType>;
/**
* Returns a query that counts the documents in the result set of this
* query.
*
* The returned query, when executed, counts the documents in the result set
* of this query without actually downloading the documents.
*
* Using the returned query to count the documents is efficient because only
* the final count, not the documents' data, is downloaded. The returned
* query can count the documents in cases where the result set is
* prohibitively large to download entirely (thousands of documents).
*
* @return a query that counts the documents in the result set of this
* query. The count can be retrieved from `snapshot.data().count`, where
* `snapshot` is the `AggregateQuerySnapshot` resulting from running the
* returned query.
*/
count(): AggregateQuery<{
count: firestore.AggregateField<number>;
}, AppModelType, DbModelType>;
/**
* Returns a query that can perform the given aggregations.
*
* The returned query, when executed, calculates the specified aggregations
* over the documents in the result set of this query without actually
* downloading the documents.
*
* Using the returned query to perform aggregations is efficient because only
* the final aggregation values, not the documents' data, is downloaded. The
* returned query can perform aggregations of the documents count the
* documents in cases where the result set is prohibitively large to download
* entirely (thousands of documents).
*
* @param aggregateSpec An `AggregateSpec` object that specifies the aggregates
* to perform over the result set. The AggregateSpec specifies aliases for each
* aggregate, which can be used to retrieve the aggregate result.
* @example
* ```typescript
* const aggregateQuery = col.aggregate(query, {
* countOfDocs: count(),
* totalHours: sum('hours'),
* averageScore: average('score')
* });
*
* const aggregateSnapshot = await aggregateQuery.get();
* const countOfDocs: number = aggregateSnapshot.data().countOfDocs;
* const totalHours: number = aggregateSnapshot.data().totalHours;
* const averageScore: number | null = aggregateSnapshot.data().averageScore;
* ```
*/
aggregate<T extends firestore.AggregateSpec>(aggregateSpec: T): AggregateQuery<T, AppModelType, DbModelType>;
/**
* Returns a query that can perform vector distance (similarity) search with given parameters.
*
* The returned query, when executed, performs a distance (similarity) search on the specified
* `vectorField` against the given `queryVector` and returns the top documents that are closest
* to the `queryVector`.
*
* Only documents whose `vectorField` field is a {@link VectorValue} of the same dimension as `queryVector`
* participate in the query, all other documents are ignored.
*
* @example
* ```
* // Returns the closest 10 documents whose Euclidean distance from their 'embedding' fields are closed to [41, 42].
* const vectorQuery = col.findNearest('embedding', [41, 42], {limit: 10, distanceMeasure: 'EUCLIDEAN'});
*
* const querySnapshot = await vectorQuery.get();
* querySnapshot.forEach(...);
* ```
*
* @param vectorField - A string or {@link FieldPath} specifying the vector field to search on.
* @param queryVector - The {@link VectorValue} used to measure the distance from `vectorField` values in the documents.
* @param options - Options control the vector query. `limit` specifies the upper bound of documents to return, must
* be a positive integer with a maximum value of 1000. `distanceMeasure` specifies what type of distance is calculated
* when performing the query.
*
* @deprecated Use the new {@link findNearest} implementation
* accepting a single `options` param.
*/
findNearest(vectorField: string | firestore.FieldPath, queryVector: firestore.VectorValue | Array<number>, options: {
limit: number;
distanceMeasure: 'EUCLIDEAN' | 'COSINE' | 'DOT_PRODUCT';
}): VectorQuery<AppModelType, DbModelType>;
/**
* Returns a query that can perform vector distance (similarity) search with given parameters.
*
* The returned query, when executed, performs a distance (similarity) search on the specified
* `vectorField` against the given `queryVector` and returns the top documents that are closest
* to the `queryVector`.
*
* Only documents whose `vectorField` field is a {@link VectorValue} of the same dimension as `queryVector`
* participate in the query, all other documents are ignored.
*
* @example
* ```
* // Returns the closest 10 documents whose Euclidean distance from their 'embedding' fields are closed to [41, 42].
* const vectorQuery = col.findNearest({
* vectorField: 'embedding',
* queryVector: [41, 42],
* limit: 10,
* distanceMeasure: 'EUCLIDEAN',
* distanceResultField: 'distance',
* distanceThreshold: 0.125
* });
*
* const querySnapshot = await aggregateQuery.get();
* querySnapshot.forEach(...);
* ```
* @param options - An argument specifying the behavior of the {@link VectorQuery} returned by this function.
* See {@link VectorQueryOptions}.
*/
findNearest(options: VectorQueryOptions): VectorQuery<AppModelType, DbModelType>;
_findNearest(options: VectorQueryOptions): VectorQuery<AppModelType, DbModelType>;
/**
* Returns true if this `Query` is equal to the provided value.
*
* @param {*} other The value to compare against.
* @return {boolean} true if this `Query` is equal to the provided value.
*/
isEqual(other: firestore.Query<AppModelType, DbModelType>): boolean;
/**
* Returns the sorted array of inequality filter fields used in this query.
*
* @return An array of inequality filter fields sorted lexicographically by FieldPath.
*/
private getInequalityFilterFields;
/**
* Computes the backend ordering semantics for DocumentSnapshot cursors.
*
* @private
* @internal
* @param cursorValuesOrDocumentSnapshot The snapshot of the document or the
* set of field values to use as the boundary.
* @returns The implicit ordering semantics.
*/
private createImplicitOrderBy;
/**
* Builds a Firestore 'Position' proto message.
*
* @private
* @internal
* @param {Array.<FieldOrder>} fieldOrders The field orders to use for this
* cursor.
* @param {Array.<DocumentSnapshot|*>} cursorValuesOrDocumentSnapshot The
* snapshot of the document or the set of field values to use as the boundary.
* @param before Whether the query boundary lies just before or after the
* provided data.
* @returns {Object} The proto message.
*/
private createCursor;
/**
* Validates that a value used with FieldValue.documentId() is either a
* string or a DocumentReference that is part of the query`s result set.
* Throws a validation error or returns a DocumentReference that can
* directly be used in the Query.
*
* @param val The value to validate.
* @throws If the value cannot be used for this query.
* @return If valid, returns a DocumentReference that can be used with the
* query.
* @private
* @internal
*/
private validateReference;
/**
* Creates and returns a new [Query]{@link Query} that starts at the provided
* set of field values relative to the order of the query. The order of the
* provided values must match the order of the order by clauses of the query.
*
* @param {...*|DocumentSnapshot} fieldValuesOrDocumentSnapshot The snapshot
* of the document the query results should start at or the field values to
* start this query at, in order of the query's order by.
* @returns {Query} A query with the new starting point.
*
* @example
* ```
* let query = firestore.collection('col');
*
* query.orderBy('foo').startAt(42).get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
startAt(...fieldValuesOrDocumentSnapshot: Array<unknown>): Query<AppModelType, DbModelType>;
/**
* Creates and returns a new [Query]{@link Query} that starts after the
* provided set of field values relative to the order of the query. The order
* of the provided values must match the order of the order by clauses of the
* query.
*
* @param {...*|DocumentSnapshot} fieldValuesOrDocumentSnapshot The snapshot
* of the document the query results should start after or the field values to
* start this query after, in order of the query's order by.
* @returns {Query} A query with the new starting point.
*
* @example
* ```
* let query = firestore.collection('col');
*
* query.orderBy('foo').startAfter(42).get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
startAfter(...fieldValuesOrDocumentSnapshot: Array<unknown>): Query<AppModelType, DbModelType>;
/**
* Creates and returns a new [Query]{@link Query} that ends before the set of
* field values relative to the order of the query. The order of the provided
* values must match the order of the order by clauses of the query.
*
* @param {...*|DocumentSnapshot} fieldValuesOrDocumentSnapshot The snapshot
* of the document the query results should end before or the field values to
* end this query before, in order of the query's order by.
* @returns {Query} A query with the new ending point.
*
* @example
* ```
* let query = firestore.collection('col');
*
* query.orderBy('foo').endBefore(42).get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
endBefore(...fieldValuesOrDocumentSnapshot: Array<unknown>): Query<AppModelType, DbModelType>;
/**
* Creates and returns a new [Query]{@link Query} that ends at the provided
* set of field values relative to the order of the query. The order of the
* provided values must match the order of the order by clauses of the query.
*
* @param {...*|DocumentSnapshot} fieldValuesOrDocumentSnapshot The snapshot
* of the document the query results should end at or the field values to end
* this query at, in order of the query's order by.
* @returns {Query} A query with the new ending point.
*
* @example
* ```
* let query = firestore.collection('col');
*
* query.orderBy('foo').endAt(42).get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
endAt(...fieldValuesOrDocumentSnapshot: Array<unknown>): Query<AppModelType, DbModelType>;
/**
* Executes the query and returns the results as a
* [QuerySnapshot]{@link QuerySnapshot}.
*
* @returns {Promise.<QuerySnapshot>} A Promise that resolves with the results
* of the Query.
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Found document at ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
get(): Promise<QuerySnapshot<AppModelType, DbModelType>>;
/**
* Plans and optionally executes this query. Returns a Promise that will be
* resolved with the planner information, statistics from the query execution (if any),
* and the query results (if any).
*
* @return A Promise that will be resolved with the planner information, statistics
* from the query execution (if any), and the query results (if any).
*/
explain(options?: firestore.ExplainOptions): Promise<ExplainResults<QuerySnapshot<AppModelType, DbModelType>>>;
/**
* Internal get() method that accepts an optional transaction options, and
* returns a query snapshot with transaction and explain metadata.
*
* @private
* @internal
* @param transactionOrReadTime A transaction ID, options to start a new
* transaction, or timestamp to use as read time.
*/
_get(transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions): Promise<QuerySnapshotResponse<QuerySnapshot<AppModelType, DbModelType>>>;
_getResponse(transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, explainOptions?: firestore.ExplainOptions): Promise<QueryResponse<QuerySnapshot<AppModelType, DbModelType>>>;
/**
* Executes the query and streams the results as
* [QueryDocumentSnapshots]{@link QueryDocumentSnapshot}.
*
* @returns {Stream.<QueryDocumentSnapshot>} A stream of
* QueryDocumentSnapshots.
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* let count = 0;
*
* query.stream().on('data', (documentSnapshot) => {
* console.log(`Found document with name '${documentSnapshot.id}'`);
* ++count;
* }).on('end', () => {
* console.log(`Total count is ${count}`);
* });
* ```
*/
stream(): NodeJS.ReadableStream;
/**
* Executes the query and streams the results as the following object:
* {document?: DocumentSnapshot, metrics?: ExplainMetrics}
*
* The stream surfaces documents one at a time as they are received from the
* server, and at the end, it will surface the metrics associated with
* executing the query.
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* let count = 0;
*
* query.explainStream({analyze: true}).on('data', (data) => {
* if (data.document) {
* // Use data.document which is a DocumentSnapshot instance.
* console.log(`Found document with name '${data.document.id}'`);
* ++count;
* }
* if (data.metrics) {
* // Use data.metrics which is an ExplainMetrics instance.
* }
* }).on('end', () => {
* console.log(`Received ${count} documents.`);
* });
* ```
*/
explainStream(explainOptions?: firestore.ExplainOptions): NodeJS.ReadableStream;
/**
* Converts a QueryCursor to its proto representation.
*
* @param cursor The original cursor value
* @private
* @internal
*/
private toCursor;
/**
* Internal method for serializing a query to its RunQuery proto
* representation with an optional transaction id or read time.
*
* @param transactionOrReadTime A transaction ID, options to start a new
* transaction, or timestamp to use as read time.
* @param explainOptions Options to use for explaining the query (if any).
* @private
* @internal
* @returns Serialized JSON for the query.
*/
toProto(transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, explainOptions?: firestore.ExplainOptions): api.IRunQueryRequest;
/**
* Converts current Query to an IBundledQuery.
*
* @private
* @internal
*/
_toBundledQuery(): protos.firestore.IBundledQuery;
private toStructuredQuery;
/**
* @internal
* @private
* This method exists solely to maintain backward compatability.
*/
_isPermanentRpcError(err: GoogleError, methodName: string): boolean;
/**
* @internal
* @private
* This method exists solely to maintain backward compatability.
*/
_hasRetryTimedOut(methodName: string, startTime: number): boolean;
/**
* Internal streaming method that accepts an optional transaction ID.
*
* BEWARE: If `transactionOrReadTime` is `ITransactionOptions`, then the first
* response in the stream will be a transaction response.
*
* @param transactionOrReadTime A transaction ID, options to start a new
* transaction, or timestamp to use as read time.
* @param explainOptions Options to use for explaining the query (if any).
* @private
* @internal
* @returns A stream of document results, optionally preceded by a transaction response.
*/
_stream(transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, explainOptions?: firestore.ExplainOptions): NodeJS.ReadableStream;
/**
* Attaches a listener for QuerySnapshot events.
*
* @param {querySnapshotCallback} onNext A callback to be called every time
* a new [QuerySnapshot]{@link QuerySnapshot} is available.
* @param {errorCallback=} onError A callback to be called if the listen
* fails or is cancelled. No further callbacks will occur.
*
* @returns {function()} An unsubscribe function that can be called to cancel
* the snapshot listener.
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* let unsubscribe = query.onSnapshot(querySnapshot => {
* console.log(`Received query snapshot of size ${querySnapshot.size}`);
* }, err => {
* console.log(`Encountered error: ${err}`);
* });
*
* // Remove this listener.
* unsubscribe();
* ```
*/
onSnapshot(onNext: (snapshot: QuerySnapshot<AppModelType, DbModelType>) => void, onError?: (error: Error) => void): () => void;
/**
* Returns a function that can be used to sort QueryDocumentSnapshots
* according to the sort criteria of this query.
*
* @private
* @internal
*/
comparator(): (s1: QueryDocumentSnapshot<AppModelType, DbModelType>, s2: QueryDocumentSnapshot<AppModelType, DbModelType>) => number;
withConverter(converter: null): Query;
withConverter<NewAppModelType, NewDbModelType extends firestore.DocumentData = firestore.DocumentData>(converter: firestore.FirestoreDataConverter<NewAppModelType, NewDbModelType>): Query<NewAppModelType, NewDbModelType>;
/**
* Construct the resulting snapshot for this query with given documents.
*
* @private
* @internal
*/
_createSnapshot(readTime: Timestamp, size: number, docs: () => Array<QueryDocumentSnapshot<AppModelType, DbModelType>>, changes: () => Array<DocumentChange<AppModelType, DbModelType>>): QuerySnapshot<AppModelType, DbModelType>;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as protos from '../../protos/firestore_v1_proto_api';
import api = protos.google.firestore.v1;
import { Timestamp } from '../timestamp';
import { ExplainMetrics } from '../query-profile';
import { QueryDocumentSnapshot } from '../document';
import * as firestore from '@google-cloud/firestore';
export interface QueryStreamElement<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> {
transaction?: Uint8Array;
readTime?: Timestamp;
explainMetrics?: ExplainMetrics;
document?: QueryDocumentSnapshot<AppModelType, DbModelType>;
}
export interface QueryResponse<TSnapshot> {
transaction?: Uint8Array;
explainMetrics?: ExplainMetrics;
result?: TSnapshot;
}
export interface QuerySnapshotResponse<TSnapshot> extends QueryResponse<TSnapshot> {
result: TSnapshot;
}
/** Internal representation of a query cursor before serialization. */
export interface QueryCursor {
before: boolean;
values: api.IValue[];
}
/*!
* Denotes whether a provided limit is applied to the beginning or the end of
* the result set.
*/
export declare enum LimitType {
First = 0,
Last = 1
}
/**
* onSnapshot() callback that receives a QuerySnapshot.
*
* @callback querySnapshotCallback
* @param {QuerySnapshot} snapshot A query snapshot.
*/
/**
* onSnapshot() callback that receives a DocumentSnapshot.
*
* @callback documentSnapshotCallback
* @param {DocumentSnapshot} snapshot A document snapshot.
*/
/**
* onSnapshot() callback that receives an error.
*
* @callback errorCallback
* @param {Error} err An error from a listen.
*/

View File

@@ -0,0 +1,46 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.LimitType = void 0;
/*!
* Denotes whether a provided limit is applied to the beginning or the end of
* the result set.
*/
var LimitType;
(function (LimitType) {
LimitType[LimitType["First"] = 0] = "First";
LimitType[LimitType["Last"] = 1] = "Last";
})(LimitType || (exports.LimitType = LimitType = {}));
/**
* onSnapshot() callback that receives a QuerySnapshot.
*
* @callback querySnapshotCallback
* @param {QuerySnapshot} snapshot A query snapshot.
*/
/**
* onSnapshot() callback that receives a DocumentSnapshot.
*
* @callback documentSnapshotCallback
* @param {DocumentSnapshot} snapshot A document snapshot.
*/
/**
* onSnapshot() callback that receives an error.
*
* @callback errorCallback
* @param {Error} err An error from a listen.
*/
//# sourceMappingURL=types.js.map

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
/**
* Specifies the behavior of the {@link VectorQuery} generated by a call to {@link Query.findNearest}.
*/
export interface VectorQueryOptions {
/**
* A string or {@link FieldPath} specifying the vector field to search on.
*/
vectorField: string | firestore.FieldPath;
/**
* The {@link VectorValue} used to measure the distance from `vectorField` values in the documents.
*/
queryVector: firestore.VectorValue | Array<number>;
/**
* Specifies the upper bound of documents to return, must be a positive integer with a maximum value of 1000.
*/
limit: number;
/**
* Specifies what type of distance is calculated when performing the query.
*/
distanceMeasure: 'EUCLIDEAN' | 'COSINE' | 'DOT_PRODUCT';
/**
* Optionally specifies the name of a field that will be set on each returned DocumentSnapshot,
* which will contain the computed distance for the document.
*/
distanceResultField?: string | firestore.FieldPath;
/**
* Specifies a threshold for which no less similar documents will be returned. The behavior
* of the specified `distanceMeasure` will affect the meaning of the distance threshold.
*
* - For `distanceMeasure: "EUCLIDEAN"`, the meaning of `distanceThreshold` is:
* SELECT docs WHERE euclidean_distance <= distanceThreshold
* - For `distanceMeasure: "COSINE"`, the meaning of `distanceThreshold` is:
* SELECT docs WHERE cosine_distance <= distanceThreshold
* - For `distanceMeasure: "DOT_PRODUCT"`, the meaning of `distanceThreshold` is:
* SELECT docs WHERE dot_product_distance >= distanceThreshold
*/
distanceThreshold?: number;
}

View File

@@ -0,0 +1,18 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=vector-query-options.js.map

View File

@@ -0,0 +1,191 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import { QueryDocumentSnapshot } from '../document';
import { DocumentChange } from '../document-change';
import { Timestamp } from '../timestamp';
import { VectorQuery } from './vector-query';
/**
* A `VectorQuerySnapshot` contains zero or more `QueryDocumentSnapshot` objects
* representing the results of a query. The documents can be accessed as an
* array via the `docs` property or enumerated using the `forEach` method. The
* number of documents can be determined via the `empty` and `size`
* properties.
*/
export declare class VectorQuerySnapshot<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> implements firestore.VectorQuerySnapshot<AppModelType, DbModelType> {
private readonly _query;
private readonly _readTime;
private readonly _size;
private _materializedDocs;
private _materializedChanges;
private _docs;
private _changes;
/**
* @private
* @internal
*
* @param _query - The originating query.
* @param _readTime - The time when this query snapshot was obtained.
* @param _size - The number of documents in the result set.
* @param docs - A callback returning a sorted array of documents matching
* this query
* @param changes - A callback returning a sorted array of document change
* events for this snapshot.
*/
constructor(_query: VectorQuery<AppModelType, DbModelType>, _readTime: Timestamp, _size: number, docs: () => Array<QueryDocumentSnapshot<AppModelType, DbModelType>>, changes: () => Array<DocumentChange<AppModelType, DbModelType>>);
/**
* The `VectorQuery` on which you called get() in order to get this
* `VectorQuerySnapshot`.
*
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.findNearest("embedding", [0, 0], {limit: 10, distanceMeasure: "EUCLIDEAN"})
* .get().then(querySnapshot => {
* console.log(`Returned first batch of results`);
* let query = querySnapshot.query;
* return query.offset(10).get();
* }).then(() => {
* console.log(`Returned second batch of results`);
* });
* ```
*/
get query(): VectorQuery<AppModelType, DbModelType>;
/**
* An array of all the documents in this `VectorQuerySnapshot`.
*
* @readonly
*
* @example
* ```
* let query = firestore.collection('col')
* .findNearest("embedding", [0, 0], {limit: 10, distanceMeasure: "EUCLIDEAN"});
*
* query.get().then(querySnapshot => {
* let docs = querySnapshot.docs;
* for (let doc of docs) {
* console.log(`Document found at path: ${doc.ref.path}`);
* }
* });
* ```
*/
get docs(): Array<QueryDocumentSnapshot<AppModelType, DbModelType>>;
/**
* `true` if there are no documents in the `VectorQuerySnapshot`.
*
* @readonly
*
* @example
* ```
* let query = firestore.collection('col')
* .findNearest("embedding", [0, 0], {limit: 10, distanceMeasure: "EUCLIDEAN"});
*
* query.get().then(querySnapshot => {
* if (querySnapshot.empty) {
* console.log('No documents found.');
* }
* });
* ```
*/
get empty(): boolean;
/**
* The number of documents in the `VectorQuerySnapshot`.
*
* @readonly
*
* @example
* ```
* let query = firestore.collection('col')
* .findNearest("embedding", [0, 0], {limit: 10, distanceMeasure: "EUCLIDEAN"});
*
* query.get().then(querySnapshot => {
* console.log(`Found ${querySnapshot.size} documents.`);
* });
* ```
*/
get size(): number;
/**
* The time this `VectorQuerySnapshot` was obtained.
*
* @example
* ```
* let query = firestore.collection('col')
* .findNearest("embedding", [0, 0], {limit: 10, distanceMeasure: "EUCLIDEAN"});
*
* query.get().then((querySnapshot) => {
* let readTime = querySnapshot.readTime;
* console.log(`Query results returned at '${readTime.toDate()}'`);
* });
* ```
*/
get readTime(): Timestamp;
/**
* Returns an array of the documents changes since the last snapshot. If
* this is the first snapshot, all documents will be in the list as added
* changes.
*
* @returns An array of the documents changes since the last snapshot.
*
* @example
* ```
* let query = firestore.collection('col')
* .findNearest("embedding", [0, 0], {limit: 10, distanceMeasure: "EUCLIDEAN"});
*
* query.get().then(querySnapshot => {
* let changes = querySnapshot.docChanges();
* for (let change of changes) {
* console.log(`A document was ${change.type}.`);
* }
* });
* ```
*/
docChanges(): Array<DocumentChange<AppModelType, DbModelType>>;
/**
* Enumerates all of the documents in the `VectorQuerySnapshot`. This is a convenience
* method for running the same callback on each {@link QueryDocumentSnapshot}
* that is returned.
*
* @param callback - A callback to be called with a
* {@link QueryDocumentSnapshot} for each document in
* the snapshot.
* @param thisArg - The `this` binding for the callback..
*
* @example
* ```
* let query = firestore.collection('col')
* .findNearest("embedding", [0, 0], {limit: 10, distanceMeasure: "EUCLIDEAN"});
*
* query.get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Document found at path: ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
forEach(callback: (result: firestore.QueryDocumentSnapshot<AppModelType, DbModelType>) => void, thisArg?: unknown): void;
/**
* Returns true if the document data in this `VectorQuerySnapshot` is equal to the
* provided value.
*
* @param other - The value to compare against.
* @returns true if this `VectorQuerySnapshot` is equal to the provided
* value.
*/
isEqual(other: firestore.VectorQuerySnapshot<AppModelType, DbModelType>): boolean;
}

View File

@@ -0,0 +1,246 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.VectorQuerySnapshot = void 0;
const validate_1 = require("../validate");
const util_1 = require("../util");
/**
* A `VectorQuerySnapshot` contains zero or more `QueryDocumentSnapshot` objects
* representing the results of a query. The documents can be accessed as an
* array via the `docs` property or enumerated using the `forEach` method. The
* number of documents can be determined via the `empty` and `size`
* properties.
*/
class VectorQuerySnapshot {
/**
* @private
* @internal
*
* @param _query - The originating query.
* @param _readTime - The time when this query snapshot was obtained.
* @param _size - The number of documents in the result set.
* @param docs - A callback returning a sorted array of documents matching
* this query
* @param changes - A callback returning a sorted array of document change
* events for this snapshot.
*/
constructor(_query, _readTime, _size, docs, changes) {
this._query = _query;
this._readTime = _readTime;
this._size = _size;
this._materializedDocs = null;
this._materializedChanges = null;
this._docs = null;
this._changes = null;
this._docs = docs;
this._changes = changes;
}
/**
* The `VectorQuery` on which you called get() in order to get this
* `VectorQuerySnapshot`.
*
* @readonly
*
* @example
* ```
* let query = firestore.collection('col').where('foo', '==', 'bar');
*
* query.findNearest("embedding", [0, 0], {limit: 10, distanceMeasure: "EUCLIDEAN"})
* .get().then(querySnapshot => {
* console.log(`Returned first batch of results`);
* let query = querySnapshot.query;
* return query.offset(10).get();
* }).then(() => {
* console.log(`Returned second batch of results`);
* });
* ```
*/
get query() {
return this._query;
}
/**
* An array of all the documents in this `VectorQuerySnapshot`.
*
* @readonly
*
* @example
* ```
* let query = firestore.collection('col')
* .findNearest("embedding", [0, 0], {limit: 10, distanceMeasure: "EUCLIDEAN"});
*
* query.get().then(querySnapshot => {
* let docs = querySnapshot.docs;
* for (let doc of docs) {
* console.log(`Document found at path: ${doc.ref.path}`);
* }
* });
* ```
*/
get docs() {
if (this._materializedDocs) {
return this._materializedDocs;
}
this._materializedDocs = this._docs();
this._docs = null;
return this._materializedDocs;
}
/**
* `true` if there are no documents in the `VectorQuerySnapshot`.
*
* @readonly
*
* @example
* ```
* let query = firestore.collection('col')
* .findNearest("embedding", [0, 0], {limit: 10, distanceMeasure: "EUCLIDEAN"});
*
* query.get().then(querySnapshot => {
* if (querySnapshot.empty) {
* console.log('No documents found.');
* }
* });
* ```
*/
get empty() {
return this._size === 0;
}
/**
* The number of documents in the `VectorQuerySnapshot`.
*
* @readonly
*
* @example
* ```
* let query = firestore.collection('col')
* .findNearest("embedding", [0, 0], {limit: 10, distanceMeasure: "EUCLIDEAN"});
*
* query.get().then(querySnapshot => {
* console.log(`Found ${querySnapshot.size} documents.`);
* });
* ```
*/
get size() {
return this._size;
}
/**
* The time this `VectorQuerySnapshot` was obtained.
*
* @example
* ```
* let query = firestore.collection('col')
* .findNearest("embedding", [0, 0], {limit: 10, distanceMeasure: "EUCLIDEAN"});
*
* query.get().then((querySnapshot) => {
* let readTime = querySnapshot.readTime;
* console.log(`Query results returned at '${readTime.toDate()}'`);
* });
* ```
*/
get readTime() {
return this._readTime;
}
/**
* Returns an array of the documents changes since the last snapshot. If
* this is the first snapshot, all documents will be in the list as added
* changes.
*
* @returns An array of the documents changes since the last snapshot.
*
* @example
* ```
* let query = firestore.collection('col')
* .findNearest("embedding", [0, 0], {limit: 10, distanceMeasure: "EUCLIDEAN"});
*
* query.get().then(querySnapshot => {
* let changes = querySnapshot.docChanges();
* for (let change of changes) {
* console.log(`A document was ${change.type}.`);
* }
* });
* ```
*/
docChanges() {
if (this._materializedChanges) {
return this._materializedChanges;
}
this._materializedChanges = this._changes();
this._changes = null;
return this._materializedChanges;
}
/**
* Enumerates all of the documents in the `VectorQuerySnapshot`. This is a convenience
* method for running the same callback on each {@link QueryDocumentSnapshot}
* that is returned.
*
* @param callback - A callback to be called with a
* {@link QueryDocumentSnapshot} for each document in
* the snapshot.
* @param thisArg - The `this` binding for the callback..
*
* @example
* ```
* let query = firestore.collection('col')
* .findNearest("embedding", [0, 0], {limit: 10, distanceMeasure: "EUCLIDEAN"});
*
* query.get().then(querySnapshot => {
* querySnapshot.forEach(documentSnapshot => {
* console.log(`Document found at path: ${documentSnapshot.ref.path}`);
* });
* });
* ```
*/
forEach(callback, thisArg) {
(0, validate_1.validateFunction)('callback', callback);
for (const doc of this.docs) {
callback.call(thisArg, doc);
}
}
/**
* Returns true if the document data in this `VectorQuerySnapshot` is equal to the
* provided value.
*
* @param other - The value to compare against.
* @returns true if this `VectorQuerySnapshot` is equal to the provided
* value.
*/
isEqual(other) {
// Since the read time is different on every query read, we explicitly
// ignore all metadata in this comparison.
if (this === other) {
return true;
}
if (!(other instanceof VectorQuerySnapshot)) {
return false;
}
if (this._size !== other._size) {
return false;
}
if (!this._query.isEqual(other._query)) {
return false;
}
if (this._materializedDocs && !this._materializedChanges) {
// If we have only materialized the documents, we compare them first.
return ((0, util_1.isArrayEqual)(this.docs, other.docs) &&
(0, util_1.isArrayEqual)(this.docChanges(), other.docChanges()));
}
// Otherwise, we compare the changes first as we expect there to be fewer.
return ((0, util_1.isArrayEqual)(this.docChanges(), other.docChanges()) &&
(0, util_1.isArrayEqual)(this.docs, other.docs));
}
}
exports.VectorQuerySnapshot = VectorQuerySnapshot;
//# sourceMappingURL=vector-query-snapshot.js.map

View File

@@ -0,0 +1,127 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import * as protos from '../../protos/firestore_v1_proto_api';
import api = protos.google.firestore.v1;
import * as firestore from '@google-cloud/firestore';
import { Timestamp } from '../timestamp';
import { QueryDocumentSnapshot } from '../document';
import { DocumentChange } from '../document-change';
import { QueryUtil } from './query-util';
import { Query } from './query';
import { VectorQueryOptions } from './vector-query-options';
import { VectorQuerySnapshot } from './vector-query-snapshot';
import { ExplainResults } from '../query-profile';
import { QueryResponse } from './types';
/**
* A query that finds the documents whose vector fields are closest to a certain query vector.
* Create an instance of `VectorQuery` with {@link Query.findNearest}.
*/
export declare class VectorQuery<AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData> implements firestore.VectorQuery<AppModelType, DbModelType> {
private readonly _query;
private readonly _options;
/**
* @internal
* @private
**/
readonly _queryUtil: QueryUtil<AppModelType, DbModelType, VectorQuery<AppModelType, DbModelType>>;
/**
* @private
* @internal
*/
constructor(_query: Query<AppModelType, DbModelType>, _options: VectorQueryOptions);
/** The query whose results participants in the vector search. Filtering
* performed by the query will apply before the vector search.
**/
get query(): Query<AppModelType, DbModelType>;
/**
* @private
* @internal
*/
private get _rawVectorField();
/**
* @private
* @internal
*/
private get _rawDistanceResultField();
/**
* @private
* @internal
*/
private get _rawQueryVector();
/**
* Plans and optionally executes this vector search query. Returns a Promise that will be
* resolved with the planner information, statistics from the query execution (if any),
* and the query results (if any).
*
* @return A Promise that will be resolved with the planner information, statistics
* from the query execution (if any), and the query results (if any).
*/
explain(options?: firestore.ExplainOptions): Promise<ExplainResults<VectorQuerySnapshot<AppModelType, DbModelType>>>;
/**
* Executes this vector search query.
*
* @returns A promise that will be resolved with the results of the query.
*/
get(): Promise<VectorQuerySnapshot<AppModelType, DbModelType>>;
_getResponse(explainOptions?: firestore.ExplainOptions): Promise<QueryResponse<VectorQuerySnapshot<AppModelType, DbModelType>>>;
/**
* Internal streaming method that accepts an optional transaction ID.
*
* @param transactionId - A transaction ID.
* @private
* @internal
* @returns A stream of document results.
*/
_stream(transactionId?: Uint8Array): NodeJS.ReadableStream;
/**
* Internal method for serializing a query to its proto
* representation with an optional transaction id.
*
* @private
* @internal
* @returns Serialized JSON for the query.
*/
toProto(transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, explainOptions?: firestore.ExplainOptions): api.IRunQueryRequest;
/**
* Construct the resulting vector snapshot for this query with given documents.
*
* @private
* @internal
*/
_createSnapshot(readTime: Timestamp, size: number, docs: () => Array<QueryDocumentSnapshot<AppModelType, DbModelType>>, changes: () => Array<DocumentChange<AppModelType, DbModelType>>): VectorQuerySnapshot<AppModelType, DbModelType>;
/**
* Construct a new vector query whose result will start after To support stream().
* This now throws an exception because cursors are not supported from the backend for vector queries yet.
*
* @private
* @internal
* @returns Serialized JSON for the query.
*/
startAfter(...fieldValuesOrDocumentSnapshot: Array<unknown>): VectorQuery<AppModelType, DbModelType>;
/**
* Compares this object with the given object for equality.
*
* This object is considered "equal" to the other object if and only if
* `other` performs the same vector distance search as this `VectorQuery` and
* the underlying Query of `other` compares equal to that of this object
* using `Query.isEqual()`.
*
* @param other - The object to compare to this object for equality.
* @returns `true` if this object is "equal" to the given object, as
* defined above, or `false` otherwise.
*/
isEqual(other: firestore.VectorQuery<AppModelType, DbModelType>): boolean;
}

View File

@@ -0,0 +1,210 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.VectorQuery = void 0;
const field_value_1 = require("../field-value");
const path_1 = require("../path");
const util_1 = require("../util");
const query_util_1 = require("./query-util");
const vector_query_snapshot_1 = require("./vector-query-snapshot");
const query_profile_1 = require("../query-profile");
/**
* A query that finds the documents whose vector fields are closest to a certain query vector.
* Create an instance of `VectorQuery` with {@link Query.findNearest}.
*/
class VectorQuery {
/**
* @private
* @internal
*/
constructor(_query, _options) {
this._query = _query;
this._options = _options;
this._queryUtil = new query_util_1.QueryUtil(_query._firestore, _query._queryOptions, _query._serializer);
}
/** The query whose results participants in the vector search. Filtering
* performed by the query will apply before the vector search.
**/
get query() {
return this._query;
}
/**
* @private
* @internal
*/
get _rawVectorField() {
return typeof this._options.vectorField === 'string'
? this._options.vectorField
: this._options.vectorField.toString();
}
/**
* @private
* @internal
*/
get _rawDistanceResultField() {
if (typeof this._options.distanceResultField === 'undefined')
return;
return typeof this._options.distanceResultField === 'string'
? this._options.distanceResultField
: this._options.distanceResultField.toString();
}
/**
* @private
* @internal
*/
get _rawQueryVector() {
return Array.isArray(this._options.queryVector)
? this._options.queryVector
: this._options.queryVector.toArray();
}
/**
* Plans and optionally executes this vector search query. Returns a Promise that will be
* resolved with the planner information, statistics from the query execution (if any),
* and the query results (if any).
*
* @return A Promise that will be resolved with the planner information, statistics
* from the query execution (if any), and the query results (if any).
*/
async explain(options) {
if (options === undefined) {
options = {};
}
const { result, explainMetrics } = await this._getResponse(options);
if (!explainMetrics) {
throw new Error('No explain results');
}
return new query_profile_1.ExplainResults(explainMetrics, result || null);
}
/**
* Executes this vector search query.
*
* @returns A promise that will be resolved with the results of the query.
*/
async get() {
const { result } = await this._getResponse();
if (!result) {
throw new Error('No VectorQuerySnapshot result');
}
return result;
}
_getResponse(explainOptions) {
return this._queryUtil._getResponse(this,
/*transactionOrReadTime*/ undefined,
// VectorQuery cannot be retried with cursors as they do not support cursors yet.
/*retryWithCursor*/ false, explainOptions);
}
/**
* Internal streaming method that accepts an optional transaction ID.
*
* @param transactionId - A transaction ID.
* @private
* @internal
* @returns A stream of document results.
*/
_stream(transactionId) {
return this._queryUtil._stream(this, transactionId,
/*retryWithCursor*/ false);
}
/**
* Internal method for serializing a query to its proto
* representation with an optional transaction id.
*
* @private
* @internal
* @returns Serialized JSON for the query.
*/
toProto(transactionOrReadTime, explainOptions) {
var _a, _b, _c;
const queryProto = this._query.toProto(transactionOrReadTime);
const queryVector = Array.isArray(this._options.queryVector)
? new field_value_1.VectorValue(this._options.queryVector)
: this._options.queryVector;
queryProto.structuredQuery.findNearest = {
limit: { value: this._options.limit },
distanceMeasure: this._options.distanceMeasure,
vectorField: {
fieldPath: path_1.FieldPath.fromArgument(this._options.vectorField)
.formattedName,
},
queryVector: queryVector._toProto(this._query._serializer),
distanceResultField: ((_a = this._options) === null || _a === void 0 ? void 0 : _a.distanceResultField)
? path_1.FieldPath.fromArgument(this._options.distanceResultField)
.formattedName
: undefined,
distanceThreshold: ((_b = this._options) === null || _b === void 0 ? void 0 : _b.distanceThreshold)
? { value: (_c = this._options) === null || _c === void 0 ? void 0 : _c.distanceThreshold }
: undefined,
};
if (explainOptions) {
queryProto.explainOptions = explainOptions;
}
return queryProto;
}
/**
* Construct the resulting vector snapshot for this query with given documents.
*
* @private
* @internal
*/
_createSnapshot(readTime, size, docs, changes) {
return new vector_query_snapshot_1.VectorQuerySnapshot(this, readTime, size, docs, changes);
}
/**
* Construct a new vector query whose result will start after To support stream().
* This now throws an exception because cursors are not supported from the backend for vector queries yet.
*
* @private
* @internal
* @returns Serialized JSON for the query.
*/
startAfter(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
...fieldValuesOrDocumentSnapshot) {
throw new Error('Unimplemented: Vector query does not support cursors yet.');
}
/**
* Compares this object with the given object for equality.
*
* This object is considered "equal" to the other object if and only if
* `other` performs the same vector distance search as this `VectorQuery` and
* the underlying Query of `other` compares equal to that of this object
* using `Query.isEqual()`.
*
* @param other - The object to compare to this object for equality.
* @returns `true` if this object is "equal" to the given object, as
* defined above, or `false` otherwise.
*/
isEqual(other) {
if (this === other) {
return true;
}
if (!(other instanceof VectorQuery)) {
return false;
}
if (!this.query.isEqual(other.query)) {
return false;
}
return (this._rawVectorField === other._rawVectorField &&
(0, util_1.isPrimitiveArrayEqual)(this._rawQueryVector, other._rawQueryVector) &&
this._options.limit === other._options.limit &&
this._options.distanceMeasure === other._options.distanceMeasure &&
this._options.distanceThreshold === other._options.distanceThreshold &&
this._rawDistanceResultField === other._rawDistanceResultField);
}
}
exports.VectorQuery = VectorQuery;
//# sourceMappingURL=vector-query.js.map

View File

@@ -0,0 +1,117 @@
/*!
* Copyright 2019 Google Inc. All Rights Reserved.
*
* 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.
*/
import { DocumentData } from '@google-cloud/firestore';
import * as proto from '../protos/firestore_v1_proto_api';
import { Firestore } from './index';
import { FieldPath } from './path';
import { ApiMapValue, ValidationOptions } from './types';
import api = proto.google.firestore.v1;
/**
* An interface for Firestore types that can be serialized to Protobuf.
*
* @private
* @internal
*/
export interface Serializable {
toProto(): api.IValue;
}
/**
* Serializer that is used to convert between JavaScript types and their
* Firestore Protobuf representation.
*
* @private
* @internal
*/
export declare class Serializer {
private allowUndefined;
private createReference;
private createInteger;
constructor(firestore: Firestore);
/**
* Encodes a JavaScript object into the Firestore 'Fields' representation.
*
* @private
* @internal
* @param obj The object to encode.
* @returns The Firestore 'Fields' representation
*/
encodeFields(obj: DocumentData): ApiMapValue;
/**
* Encodes a JavaScript value into the Firestore 'Value' representation.
*
* @private
* @internal
* @param val The object to encode
* @returns The Firestore Proto or null if we are deleting a field.
*/
encodeValue(val: unknown): api.IValue | null;
/**
* @private
*/
encodeVector(rawVector: number[]): api.IValue;
/**
* Decodes a single Firestore 'Value' Protobuf.
*
* @private
* @internal
* @param proto A Firestore 'Value' Protobuf.
* @returns The converted JS type.
*/
decodeValue(proto: api.IValue): unknown;
/**
* Decodes a google.protobuf.Value
*
* @private
* @internal
* @param proto A Google Protobuf 'Value'.
* @returns The converted JS type.
*/
decodeGoogleProtobufValue(proto: proto.google.protobuf.IValue): unknown;
/**
* Decodes a google.protobuf.ListValue
*
* @private
* @internal
* @param proto A Google Protobuf 'ListValue'.
* @returns The converted JS type.
*/
decodeGoogleProtobufList(proto: proto.google.protobuf.IListValue | null | undefined): unknown[];
/**
* Decodes a google.protobuf.Struct
*
* @private
* @internal
* @param proto A Google Protobuf 'Struct'.
* @returns The converted JS type.
*/
decodeGoogleProtobufStruct(proto: proto.google.protobuf.IStruct | null | undefined): Record<string, unknown>;
}
/**
* Validates a JavaScript value for usage as a Firestore value.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param value JavaScript value to validate.
* @param desc A description of the expected type.
* @param path The field path to validate.
* @param options Validation options
* @param level The current depth of the traversal. This is used to decide
* whether undefined values or deletes are allowed.
* @param inArray Whether we are inside an array.
* @throws when the object is invalid.
*/
export declare function validateUserInput(arg: string | number, value: unknown, desc: string, options: ValidationOptions, path?: FieldPath, level?: number, inArray?: boolean): void;

View File

@@ -0,0 +1,456 @@
"use strict";
/*!
* Copyright 2019 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Serializer = void 0;
exports.validateUserInput = validateUserInput;
const field_value_1 = require("./field-value");
const convert_1 = require("./convert");
const geo_point_1 = require("./geo-point");
const index_1 = require("./index");
const path_1 = require("./path");
const timestamp_1 = require("./timestamp");
const util_1 = require("./util");
const validate_1 = require("./validate");
const map_type_1 = require("./map-type");
/**
* The maximum depth of a Firestore object.
*
* @private
* @internal
*/
const MAX_DEPTH = 20;
/**
* Serializer that is used to convert between JavaScript types and their
* Firestore Protobuf representation.
*
* @private
* @internal
*/
class Serializer {
constructor(firestore) {
// Instead of storing the `firestore` object, we store just a reference to
// its `.doc()` method. This avoid a circular reference, which breaks
// JSON.stringify().
this.createReference = path => firestore.doc(path);
this.createInteger = n => firestore._settings.useBigInt ? BigInt(n) : Number(n);
this.allowUndefined = !!firestore._settings.ignoreUndefinedProperties;
}
/**
* Encodes a JavaScript object into the Firestore 'Fields' representation.
*
* @private
* @internal
* @param obj The object to encode.
* @returns The Firestore 'Fields' representation
*/
encodeFields(obj) {
const fields = {};
for (const prop of Object.keys(obj)) {
const val = this.encodeValue(obj[prop]);
if (val) {
fields[prop] = val;
}
}
return fields;
}
/**
* Encodes a JavaScript value into the Firestore 'Value' representation.
*
* @private
* @internal
* @param val The object to encode
* @returns The Firestore Proto or null if we are deleting a field.
*/
encodeValue(val) {
if (val instanceof field_value_1.FieldTransform) {
return null;
}
if (typeof val === 'string') {
return {
stringValue: val,
};
}
if (typeof val === 'boolean') {
return {
booleanValue: val,
};
}
if (typeof val === 'number') {
const isNegativeZero = val === 0 && 1 / val === 1 / -0;
if (Number.isSafeInteger(val) && !isNegativeZero) {
return {
integerValue: val,
};
}
else {
return {
doubleValue: val,
};
}
}
if (typeof val === 'bigint') {
return {
integerValue: val.toString(),
};
}
if (val instanceof Date) {
const timestamp = timestamp_1.Timestamp.fromDate(val);
return {
timestampValue: {
seconds: timestamp.seconds,
nanos: timestamp.nanoseconds,
},
};
}
if (isMomentJsType(val)) {
const timestamp = timestamp_1.Timestamp.fromDate(val.toDate());
return {
timestampValue: {
seconds: timestamp.seconds,
nanos: timestamp.nanoseconds,
},
};
}
if (val === null) {
return {
nullValue: 'NULL_VALUE',
};
}
if (val instanceof Buffer || val instanceof Uint8Array) {
return {
bytesValue: val,
};
}
if (val instanceof field_value_1.VectorValue) {
return val._toProto(this);
}
if ((0, util_1.isObject)(val)) {
const toProto = val['toProto'];
if (typeof toProto === 'function') {
return toProto.bind(val)();
}
}
if (Array.isArray(val)) {
const array = {
arrayValue: {},
};
if (val.length > 0) {
array.arrayValue.values = [];
for (let i = 0; i < val.length; ++i) {
const enc = this.encodeValue(val[i]);
if (enc) {
array.arrayValue.values.push(enc);
}
}
}
return array;
}
if (typeof val === 'object' && (0, util_1.isPlainObject)(val)) {
const map = {
mapValue: {},
};
// If we encounter an empty object, we always need to send it to make sure
// the server creates a map entry.
if (!(0, util_1.isEmpty)(val)) {
map.mapValue.fields = this.encodeFields(val);
if ((0, util_1.isEmpty)(map.mapValue.fields)) {
return null;
}
}
return map;
}
if (val === undefined && this.allowUndefined) {
return null;
}
throw new Error(`Cannot encode value: ${val}`);
}
/**
* @private
*/
encodeVector(rawVector) {
// A Firestore Vector is a map with reserved key/value pairs.
return {
mapValue: {
fields: {
[map_type_1.RESERVED_MAP_KEY]: {
stringValue: map_type_1.RESERVED_MAP_KEY_VECTOR_VALUE,
},
[map_type_1.VECTOR_MAP_VECTORS_KEY]: {
arrayValue: {
values: rawVector.map(value => {
return {
doubleValue: value,
};
}),
},
},
},
},
};
}
/**
* Decodes a single Firestore 'Value' Protobuf.
*
* @private
* @internal
* @param proto A Firestore 'Value' Protobuf.
* @returns The converted JS type.
*/
decodeValue(proto) {
const valueType = (0, convert_1.detectValueType)(proto);
switch (valueType) {
case 'stringValue': {
return proto.stringValue;
}
case 'booleanValue': {
return proto.booleanValue;
}
case 'integerValue': {
return this.createInteger(proto.integerValue);
}
case 'doubleValue': {
return proto.doubleValue;
}
case 'timestampValue': {
return timestamp_1.Timestamp.fromProto(proto.timestampValue);
}
case 'referenceValue': {
const resourcePath = path_1.QualifiedResourcePath.fromSlashSeparatedString(proto.referenceValue);
return this.createReference(resourcePath.relativeName);
}
case 'arrayValue': {
const array = [];
if (Array.isArray(proto.arrayValue.values)) {
for (const value of proto.arrayValue.values) {
array.push(this.decodeValue(value));
}
}
return array;
}
case 'nullValue': {
return null;
}
case 'mapValue': {
const fields = proto.mapValue.fields;
if (fields) {
const obj = {};
for (const prop of Object.keys(fields)) {
obj[prop] = this.decodeValue(fields[prop]);
}
return obj;
}
else {
return {};
}
}
case 'vectorValue': {
const fields = proto.mapValue.fields;
return field_value_1.VectorValue._fromProto(fields[map_type_1.VECTOR_MAP_VECTORS_KEY]);
}
case 'geoPointValue': {
return geo_point_1.GeoPoint.fromProto(proto.geoPointValue);
}
case 'bytesValue': {
return proto.bytesValue;
}
default: {
throw new Error('Cannot decode type from Firestore Value: ' + JSON.stringify(proto));
}
}
}
/**
* Decodes a google.protobuf.Value
*
* @private
* @internal
* @param proto A Google Protobuf 'Value'.
* @returns The converted JS type.
*/
decodeGoogleProtobufValue(proto) {
switch ((0, convert_1.detectGoogleProtobufValueType)(proto)) {
case 'nullValue': {
return null;
}
case 'numberValue': {
return proto.numberValue;
}
case 'stringValue': {
return proto.stringValue;
}
case 'boolValue': {
return proto.boolValue;
}
case 'listValue': {
return this.decodeGoogleProtobufList(proto.listValue);
}
case 'structValue': {
return this.decodeGoogleProtobufStruct(proto.structValue);
}
default: {
throw new Error('Cannot decode type from google.protobuf.Value: ' +
JSON.stringify(proto));
}
}
}
/**
* Decodes a google.protobuf.ListValue
*
* @private
* @internal
* @param proto A Google Protobuf 'ListValue'.
* @returns The converted JS type.
*/
decodeGoogleProtobufList(proto) {
const result = [];
if (proto && proto.values && Array.isArray(proto.values)) {
for (const value of proto.values) {
result.push(this.decodeGoogleProtobufValue(value));
}
}
return result;
}
/**
* Decodes a google.protobuf.Struct
*
* @private
* @internal
* @param proto A Google Protobuf 'Struct'.
* @returns The converted JS type.
*/
decodeGoogleProtobufStruct(proto) {
const result = {};
if (proto && proto.fields) {
for (const prop of Object.keys(proto.fields)) {
result[prop] = this.decodeGoogleProtobufValue(proto.fields[prop]);
}
}
return result;
}
}
exports.Serializer = Serializer;
/**
* Validates a JavaScript value for usage as a Firestore value.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param value JavaScript value to validate.
* @param desc A description of the expected type.
* @param path The field path to validate.
* @param options Validation options
* @param level The current depth of the traversal. This is used to decide
* whether undefined values or deletes are allowed.
* @param inArray Whether we are inside an array.
* @throws when the object is invalid.
*/
function validateUserInput(arg, value, desc, options, path, level, inArray) {
if (path && path.size - 1 > MAX_DEPTH) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, desc)} Input object is deeper than ${MAX_DEPTH} levels or contains a cycle.`);
}
level = level || 0;
inArray = inArray || false;
const fieldPathMessage = path ? ` (found in field "${path}")` : '';
if (Array.isArray(value)) {
for (let i = 0; i < value.length; ++i) {
validateUserInput(arg, value[i], desc, options, path ? path.append(String(i)) : new path_1.FieldPath(String(i)), level + 1,
/* inArray= */ true);
}
}
else if ((0, util_1.isPlainObject)(value)) {
for (const prop of Object.keys(value)) {
validateUserInput(arg, value[prop], desc, options, path ? path.append(new path_1.FieldPath(prop)) : new path_1.FieldPath(prop), level + 1, inArray);
}
}
else if (value === undefined) {
if (options.allowUndefined && level === 0) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, desc)} "undefined" values are only ignored inside of objects.`);
}
else if (!options.allowUndefined) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, desc)} Cannot use "undefined" as a Firestore value${fieldPathMessage}. ` +
'If you want to ignore undefined values, enable `ignoreUndefinedProperties`.');
}
}
else if (value instanceof field_value_1.VectorValue) {
// OK
}
else if (value instanceof field_value_1.DeleteTransform) {
if (inArray) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, desc)} ${value.methodName}() cannot be used inside of an array${fieldPathMessage}.`);
}
else if (options.allowDeletes === 'none') {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, desc)} ${value.methodName}() must appear at the top-level and can only be used in update() ` +
`or set() with {merge:true}${fieldPathMessage}.`);
}
else if (options.allowDeletes === 'root') {
if (level === 0) {
// Ok (update() with UpdateData).
}
else if (level === 1 && (path === null || path === void 0 ? void 0 : path.size) === 1) {
// Ok (update with varargs).
}
else {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, desc)} ${value.methodName}() must appear at the top-level and can only be used in update() ` +
`or set() with {merge:true}${fieldPathMessage}.`);
}
}
}
else if (value instanceof field_value_1.FieldTransform) {
if (inArray) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, desc)} ${value.methodName}() cannot be used inside of an array${fieldPathMessage}.`);
}
else if (!options.allowTransforms) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, desc)} ${value.methodName}() can only be used in set(), create() or update()${fieldPathMessage}.`);
}
}
else if (value instanceof path_1.FieldPath) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, desc)} Cannot use object of type "FieldPath" as a Firestore value${fieldPathMessage}.`);
}
else if (value instanceof index_1.DocumentReference) {
// Ok.
}
else if (value instanceof geo_point_1.GeoPoint) {
// Ok.
}
else if (value instanceof timestamp_1.Timestamp || value instanceof Date) {
// Ok.
}
else if (isMomentJsType(value)) {
// Ok.
}
else if (value instanceof Buffer || value instanceof Uint8Array) {
// Ok.
}
else if (value === null) {
// Ok.
}
else if (typeof value === 'object') {
throw new Error((0, validate_1.customObjectMessage)(arg, value, path));
}
}
/**
* Returns true if value is a MomentJs date object.
* @private
* @internal
*/
function isMomentJsType(value) {
return (typeof value === 'object' &&
value !== null &&
value.constructor &&
value.constructor.name === 'Moment' &&
typeof value.toDate === 'function');
}
//# sourceMappingURL=serializer.js.map

View File

@@ -0,0 +1,38 @@
/**
* Copyright 2021 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.
*/
/**
* Internal copy of GRPC status code. Copied to prevent loading of google-gax
* at SDK startup.
*/
export declare const enum StatusCode {
OK = 0,
CANCELLED = 1,
UNKNOWN = 2,
INVALID_ARGUMENT = 3,
DEADLINE_EXCEEDED = 4,
NOT_FOUND = 5,
ALREADY_EXISTS = 6,
PERMISSION_DENIED = 7,
RESOURCE_EXHAUSTED = 8,
FAILED_PRECONDITION = 9,
ABORTED = 10,
OUT_OF_RANGE = 11,
UNIMPLEMENTED = 12,
INTERNAL = 13,
UNAVAILABLE = 14,
DATA_LOSS = 15,
UNAUTHENTICATED = 16
}

View File

@@ -0,0 +1,18 @@
"use strict";
/**
* Copyright 2021 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=status-code.js.map

View File

@@ -0,0 +1,27 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import { Attributes, TraceUtil } from './trace-util';
import { Span } from './span';
/**
* @private
* @internal
*/
export declare class DisabledTraceUtil implements TraceUtil {
startSpan(name: string): Span;
startActiveSpan<F extends (span: Span) => unknown>(name: string, fn: F, attributes?: Attributes): ReturnType<F>;
currentSpan(): Span;
recordProjectId(projectId: string): void;
}

View File

@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DisabledTraceUtil = void 0;
const span_1 = require("./span");
/**
* @private
* @internal
*/
class DisabledTraceUtil {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
startSpan(name) {
return new span_1.Span();
}
startActiveSpan(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
name, fn,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
attributes) {
const emptySpan = new span_1.Span();
return fn(emptySpan);
}
currentSpan() {
return new span_1.Span();
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
recordProjectId(projectId) { }
}
exports.DisabledTraceUtil = DisabledTraceUtil;
//# sourceMappingURL=disabled-trace-util.js.map

View File

@@ -0,0 +1,36 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import { Settings } from '@google-cloud/firestore';
import { Span as OpenTelemetrySpan, TracerProvider } from '@opentelemetry/api';
import { Span } from './span';
import { Attributes, TraceUtil } from './trace-util';
/**
* @private
* @internal
*/
export declare class EnabledTraceUtil implements TraceUtil {
private tracer;
private settingsAttributes;
tracerProvider: TracerProvider;
constructor(settings: Settings);
recordProjectId(projectId: string): void;
private millisToSecondString;
private endSpan;
startActiveSpan<F extends (span: Span) => unknown>(name: string, fn: F, attributes?: Attributes): ReturnType<F>;
startSpan(name: string): Span;
currentSpan(): Span;
addCommonAttributes(otelSpan: OpenTelemetrySpan): void;
}

View File

@@ -0,0 +1,148 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.EnabledTraceUtil = void 0;
const api_1 = require("@opentelemetry/api");
const span_1 = require("./span");
const trace_util_1 = require("./trace-util");
const firestore_client_config_json_1 = require("../v1/firestore_client_config.json");
const v1_1 = require("../v1");
const path_1 = require("../path");
const index_1 = require("../index");
const serviceConfig = firestore_client_config_json_1.interfaces['google.firestore.v1.Firestore'];
/**
* @private
* @internal
*/
class EnabledTraceUtil {
constructor(settings) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
let provider = (_a = settings.openTelemetry) === null || _a === void 0 ? void 0 : _a.tracerProvider;
// If a TracerProvider has not been given to us, we try to use the global one.
if (!provider) {
const { trace } = require('@opentelemetry/api');
provider = trace.getTracerProvider();
}
// At this point provider is guaranteed to be defined because
// `trace.getTracerProvider()` does not return null or undefined.
this.tracerProvider = provider;
const libVersion = require('../../../package.json').version;
const libName = require('../../../package.json').name;
try {
this.tracer = this.tracerProvider.getTracer(libName, libVersion);
}
catch (e) {
throw new Error("The object provided for 'tracerProvider' does not conform to the TracerProvider interface.");
}
this.settingsAttributes = {};
this.settingsAttributes['otel.scope.name'] = libName;
this.settingsAttributes['otel.scope.version'] = libVersion;
if (settings.projectId) {
this.settingsAttributes[`${trace_util_1.ATTRIBUTE_SETTINGS_PREFIX}.project_id`] =
settings.projectId;
}
this.settingsAttributes[`${trace_util_1.ATTRIBUTE_SETTINGS_PREFIX}.database_id`] =
settings.databaseId || path_1.DEFAULT_DATABASE_ID;
const host = (_c = (_b = settings.servicePath) !== null && _b !== void 0 ? _b : settings.host) !== null && _c !== void 0 ? _c : 'firestore.googleapis.com';
const port = (_d = settings.port) !== null && _d !== void 0 ? _d : v1_1.FirestoreClient.port;
this.settingsAttributes[`${trace_util_1.ATTRIBUTE_SETTINGS_PREFIX}.host`] =
`${host}:${port}`;
if (settings.preferRest !== undefined) {
this.settingsAttributes[`${trace_util_1.ATTRIBUTE_SETTINGS_PREFIX}.prefer_REST`] =
settings.preferRest;
}
this.settingsAttributes[`${trace_util_1.ATTRIBUTE_SETTINGS_PREFIX}.max_idle_channels`] =
(_e = settings.maxIdleChannels) !== null && _e !== void 0 ? _e : index_1.DEFAULT_MAX_IDLE_CHANNELS;
const defaultRetrySettings = serviceConfig.retry_params.default;
const customRetrySettings = (_j = (_h = (_g = (_f = settings.clientConfig) === null || _f === void 0 ? void 0 : _f.interfaces) === null || _g === void 0 ? void 0 : _g['google.firestore.v1.Firestore']) === null || _h === void 0 ? void 0 : _h['retry_params']) === null || _j === void 0 ? void 0 : _j['default'];
this.settingsAttributes[`${trace_util_1.ATTRIBUTE_SETTINGS_PREFIX}.initial_retry_delay`] = this.millisToSecondString((_k = customRetrySettings === null || customRetrySettings === void 0 ? void 0 : customRetrySettings.initial_retry_delay_millis) !== null && _k !== void 0 ? _k : defaultRetrySettings.initial_retry_delay_millis);
this.settingsAttributes[`${trace_util_1.ATTRIBUTE_SETTINGS_PREFIX}.initial_rpc_timeout`] = this.millisToSecondString((_l = customRetrySettings === null || customRetrySettings === void 0 ? void 0 : customRetrySettings.initial_rpc_timeout_millis) !== null && _l !== void 0 ? _l : defaultRetrySettings.initial_rpc_timeout_millis);
this.settingsAttributes[`${trace_util_1.ATTRIBUTE_SETTINGS_PREFIX}.total_timeout`] =
this.millisToSecondString((_m = customRetrySettings === null || customRetrySettings === void 0 ? void 0 : customRetrySettings.total_timeout_millis) !== null && _m !== void 0 ? _m : defaultRetrySettings.total_timeout_millis);
this.settingsAttributes[`${trace_util_1.ATTRIBUTE_SETTINGS_PREFIX}.max_retry_delay`] =
this.millisToSecondString((_o = customRetrySettings === null || customRetrySettings === void 0 ? void 0 : customRetrySettings.max_retry_delay_millis) !== null && _o !== void 0 ? _o : defaultRetrySettings.max_retry_delay_millis);
this.settingsAttributes[`${trace_util_1.ATTRIBUTE_SETTINGS_PREFIX}.max_rpc_timeout`] =
this.millisToSecondString((_p = customRetrySettings === null || customRetrySettings === void 0 ? void 0 : customRetrySettings.max_rpc_timeout_millis) !== null && _p !== void 0 ? _p : defaultRetrySettings.max_rpc_timeout_millis);
this.settingsAttributes[`${trace_util_1.ATTRIBUTE_SETTINGS_PREFIX}.retry_delay_multiplier`] =
(_q = customRetrySettings === null || customRetrySettings === void 0 ? void 0 : customRetrySettings.retry_delay_multiplier.toString()) !== null && _q !== void 0 ? _q : defaultRetrySettings.retry_delay_multiplier.toString();
this.settingsAttributes[`${trace_util_1.ATTRIBUTE_SETTINGS_PREFIX}.rpc_timeout_multiplier`] =
(_r = customRetrySettings === null || customRetrySettings === void 0 ? void 0 : customRetrySettings.rpc_timeout_multiplier.toString()) !== null && _r !== void 0 ? _r : defaultRetrySettings.rpc_timeout_multiplier.toString();
}
recordProjectId(projectId) {
this.settingsAttributes[`${trace_util_1.ATTRIBUTE_SETTINGS_PREFIX}.project_id`] =
projectId;
this.currentSpan().setAttributes(this.settingsAttributes);
}
millisToSecondString(millis) {
return `${millis / 1000}s`;
}
endSpan(otelSpan, error) {
otelSpan.setStatus({
code: api_1.SpanStatusCode.ERROR,
message: error.message,
});
otelSpan.recordException(error);
otelSpan.end();
}
startActiveSpan(name, fn, attributes) {
return this.tracer.startActiveSpan(name, {
attributes: attributes,
}, (otelSpan) => {
this.addCommonAttributes(otelSpan);
// Note that if `fn` returns a `Promise`, we want the otelSpan to end
// after the `Promise` has resolved, NOT after the `fn` has returned.
// Therefore, we should not use a `finally` clause to end the otelSpan.
try {
let result = fn(new span_1.Span(otelSpan));
if (result instanceof Promise) {
result = result
.then(value => {
otelSpan.end();
return value;
})
.catch(error => {
this.endSpan(otelSpan, error);
// Returns a Promise.reject the same as the underlying function.
return Promise.reject(error);
});
}
else {
otelSpan.end();
}
return result;
}
catch (error) {
this.endSpan(otelSpan, error);
// Re-throw the exception to maintain normal error handling.
throw error;
}
});
}
startSpan(name) {
const otelSpan = this.tracer.startSpan(name, undefined, api_1.context.active());
this.addCommonAttributes(otelSpan);
return new span_1.Span(otelSpan);
}
currentSpan() {
return new span_1.Span(api_1.trace.getActiveSpan());
}
addCommonAttributes(otelSpan) {
otelSpan.setAttributes(this.settingsAttributes);
}
}
exports.EnabledTraceUtil = EnabledTraceUtil;
//# sourceMappingURL=enabled-trace-util.js.map

View File

@@ -0,0 +1,28 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import { Span as OpenTelemetrySpan } from '@opentelemetry/api';
import { Attributes } from './trace-util';
/**
* @private
* @internal
*/
export declare class Span {
private span?;
constructor(span?: OpenTelemetrySpan | undefined);
end(): void;
addEvent(name: string, attributes?: Attributes): this;
setAttributes(attributes: Attributes): this;
}

View File

@@ -0,0 +1,43 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Span = void 0;
/**
* @private
* @internal
*/
class Span {
constructor(span) {
this.span = span;
}
end() {
var _a;
(_a = this.span) === null || _a === void 0 ? void 0 : _a.end();
}
addEvent(name, attributes) {
var _a;
this.span = (_a = this.span) === null || _a === void 0 ? void 0 : _a.addEvent(name, attributes);
return this;
}
setAttributes(attributes) {
var _a;
this.span = (_a = this.span) === null || _a === void 0 ? void 0 : _a.setAttributes(attributes);
return this;
}
}
exports.Span = Span;
//# sourceMappingURL=span.js.map

View File

@@ -0,0 +1,74 @@
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
import { Span } from './span';
/**
* @private
* @internal
*/
export interface Attributes {
[attributeKey: string]: AttributeValue | undefined;
}
/**
* @private
* @internal
*/
export declare type AttributeValue = string | number | boolean | Array<string> | Array<number> | Array<boolean>;
/**
* Span names for instrumented operations.
*/
export declare const SERVICE = "google.firestore.v1.Firestore/";
export declare const SPAN_NAME_BATCH_GET_DOCUMENTS = "BatchGetDocuments";
export declare const SPAN_NAME_RUN_QUERY = "RunQuery";
export declare const SPAN_NAME_RUN_AGGREGATION_QUERY = "RunAggregationQuery";
export declare const SPAN_NAME_DOC_REF_CREATE = "DocumentReference.Create";
export declare const SPAN_NAME_DOC_REF_SET = "DocumentReference.Set";
export declare const SPAN_NAME_DOC_REF_UPDATE = "DocumentReference.Update";
export declare const SPAN_NAME_DOC_REF_DELETE = "DocumentReference.Delete";
export declare const SPAN_NAME_DOC_REF_GET = "DocumentReference.Get";
export declare const SPAN_NAME_DOC_REF_LIST_COLLECTIONS = "DocumentReference.ListCollections";
export declare const SPAN_NAME_COL_REF_ADD = "CollectionReference.Add";
export declare const SPAN_NAME_COL_REF_LIST_DOCUMENTS = "CollectionReference.ListDocuments";
export declare const SPAN_NAME_QUERY_GET = "Query.Get";
export declare const SPAN_NAME_AGGREGATION_QUERY_GET = "AggregationQuery.Get";
export declare const SPAN_NAME_TRANSACTION_RUN = "Transaction.Run";
export declare const SPAN_NAME_TRANSACTION_GET_QUERY = "Transaction.Get.Query";
export declare const SPAN_NAME_TRANSACTION_GET_AGGREGATION_QUERY = "Transaction.Get.AggregationQuery";
export declare const SPAN_NAME_TRANSACTION_GET_DOCUMENT = "Transaction.Get.Document";
export declare const SPAN_NAME_TRANSACTION_GET_DOCUMENTS = "Transaction.Get.Documents";
export declare const SPAN_NAME_TRANSACTION_ROLLBACK = "Transaction.Rollback";
export declare const SPAN_NAME_TRANSACTION_COMMIT = "Transaction.Commit";
export declare const SPAN_NAME_BATCH_COMMIT = "Batch.Commit";
export declare const SPAN_NAME_PARTITION_QUERY = "PartitionQuery";
export declare const SPAN_NAME_BULK_WRITER_COMMIT = "BulkWriter.Commit";
export declare const ATTRIBUTE_SERVICE_PREFIX = "gcp.firestore";
export declare const ATTRIBUTE_SETTINGS_PREFIX = "gcp.firestore.settings";
export declare const ATTRIBUTE_KEY_DOC_COUNT = "doc_count";
export declare const ATTRIBUTE_KEY_IS_TRANSACTIONAL = "transactional";
export declare const ATTRIBUTE_KEY_NUM_RESPONSES = "response_count";
export declare const ATTRIBUTE_KEY_IS_RETRY_WITH_CURSOR = "retry_query_with_cursor";
export declare const ATTRIBUTE_KEY_TRANSACTION_TYPE = "transaction_type";
export declare const ATTRIBUTE_KEY_ATTEMPTS_ALLOWED = "attempts_allowed";
export declare const ATTRIBUTE_KEY_ATTEMPTS_REMAINING = "attempts_remaining";
/**
* @private
* @internal
*/
export interface TraceUtil {
startActiveSpan<F extends (span: Span) => unknown>(name: string, fn: F, attributes?: Attributes): ReturnType<F>;
startSpan(name: string): Span;
currentSpan(): Span;
recordProjectId(projectId: string): void;
}

View File

@@ -0,0 +1,55 @@
"use strict";
/**
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ATTRIBUTE_KEY_ATTEMPTS_REMAINING = exports.ATTRIBUTE_KEY_ATTEMPTS_ALLOWED = exports.ATTRIBUTE_KEY_TRANSACTION_TYPE = exports.ATTRIBUTE_KEY_IS_RETRY_WITH_CURSOR = exports.ATTRIBUTE_KEY_NUM_RESPONSES = exports.ATTRIBUTE_KEY_IS_TRANSACTIONAL = exports.ATTRIBUTE_KEY_DOC_COUNT = exports.ATTRIBUTE_SETTINGS_PREFIX = exports.ATTRIBUTE_SERVICE_PREFIX = exports.SPAN_NAME_BULK_WRITER_COMMIT = exports.SPAN_NAME_PARTITION_QUERY = exports.SPAN_NAME_BATCH_COMMIT = exports.SPAN_NAME_TRANSACTION_COMMIT = exports.SPAN_NAME_TRANSACTION_ROLLBACK = exports.SPAN_NAME_TRANSACTION_GET_DOCUMENTS = exports.SPAN_NAME_TRANSACTION_GET_DOCUMENT = exports.SPAN_NAME_TRANSACTION_GET_AGGREGATION_QUERY = exports.SPAN_NAME_TRANSACTION_GET_QUERY = exports.SPAN_NAME_TRANSACTION_RUN = exports.SPAN_NAME_AGGREGATION_QUERY_GET = exports.SPAN_NAME_QUERY_GET = exports.SPAN_NAME_COL_REF_LIST_DOCUMENTS = exports.SPAN_NAME_COL_REF_ADD = exports.SPAN_NAME_DOC_REF_LIST_COLLECTIONS = exports.SPAN_NAME_DOC_REF_GET = exports.SPAN_NAME_DOC_REF_DELETE = exports.SPAN_NAME_DOC_REF_UPDATE = exports.SPAN_NAME_DOC_REF_SET = exports.SPAN_NAME_DOC_REF_CREATE = exports.SPAN_NAME_RUN_AGGREGATION_QUERY = exports.SPAN_NAME_RUN_QUERY = exports.SPAN_NAME_BATCH_GET_DOCUMENTS = exports.SERVICE = void 0;
/**
* Span names for instrumented operations.
*/
exports.SERVICE = 'google.firestore.v1.Firestore/';
exports.SPAN_NAME_BATCH_GET_DOCUMENTS = 'BatchGetDocuments';
exports.SPAN_NAME_RUN_QUERY = 'RunQuery';
exports.SPAN_NAME_RUN_AGGREGATION_QUERY = 'RunAggregationQuery';
exports.SPAN_NAME_DOC_REF_CREATE = 'DocumentReference.Create';
exports.SPAN_NAME_DOC_REF_SET = 'DocumentReference.Set';
exports.SPAN_NAME_DOC_REF_UPDATE = 'DocumentReference.Update';
exports.SPAN_NAME_DOC_REF_DELETE = 'DocumentReference.Delete';
exports.SPAN_NAME_DOC_REF_GET = 'DocumentReference.Get';
exports.SPAN_NAME_DOC_REF_LIST_COLLECTIONS = 'DocumentReference.ListCollections';
exports.SPAN_NAME_COL_REF_ADD = 'CollectionReference.Add';
exports.SPAN_NAME_COL_REF_LIST_DOCUMENTS = 'CollectionReference.ListDocuments';
exports.SPAN_NAME_QUERY_GET = 'Query.Get';
exports.SPAN_NAME_AGGREGATION_QUERY_GET = 'AggregationQuery.Get';
exports.SPAN_NAME_TRANSACTION_RUN = 'Transaction.Run';
exports.SPAN_NAME_TRANSACTION_GET_QUERY = 'Transaction.Get.Query';
exports.SPAN_NAME_TRANSACTION_GET_AGGREGATION_QUERY = 'Transaction.Get.AggregationQuery';
exports.SPAN_NAME_TRANSACTION_GET_DOCUMENT = 'Transaction.Get.Document';
exports.SPAN_NAME_TRANSACTION_GET_DOCUMENTS = 'Transaction.Get.Documents';
exports.SPAN_NAME_TRANSACTION_ROLLBACK = 'Transaction.Rollback';
exports.SPAN_NAME_TRANSACTION_COMMIT = 'Transaction.Commit';
exports.SPAN_NAME_BATCH_COMMIT = 'Batch.Commit';
exports.SPAN_NAME_PARTITION_QUERY = 'PartitionQuery';
exports.SPAN_NAME_BULK_WRITER_COMMIT = 'BulkWriter.Commit';
exports.ATTRIBUTE_SERVICE_PREFIX = 'gcp.firestore';
exports.ATTRIBUTE_SETTINGS_PREFIX = `${exports.ATTRIBUTE_SERVICE_PREFIX}.settings`;
exports.ATTRIBUTE_KEY_DOC_COUNT = 'doc_count';
exports.ATTRIBUTE_KEY_IS_TRANSACTIONAL = 'transactional';
exports.ATTRIBUTE_KEY_NUM_RESPONSES = 'response_count';
exports.ATTRIBUTE_KEY_IS_RETRY_WITH_CURSOR = 'retry_query_with_cursor';
exports.ATTRIBUTE_KEY_TRANSACTION_TYPE = 'transaction_type';
exports.ATTRIBUTE_KEY_ATTEMPTS_ALLOWED = 'attempts_allowed';
exports.ATTRIBUTE_KEY_ATTEMPTS_REMAINING = 'attempts_remaining';
//# sourceMappingURL=trace-util.js.map

View File

@@ -0,0 +1,206 @@
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import { google } from '../protos/firestore_v1_proto_api';
import api = google.firestore.v1;
/**
* 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.
*
* @see https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto
*/
export declare class Timestamp implements firestore.Timestamp {
private readonly _seconds;
private readonly _nanoseconds;
/**
* Creates a new timestamp with the current date, with millisecond precision.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.set({ updateTime:Firestore.Timestamp.now() });
*
* ```
* @return {Timestamp} A new `Timestamp` representing the current date.
*/
static now(): Timestamp;
/**
* Creates a new timestamp from the given date.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* let date = Date.parse('01 Jan 2000 00:00:00 GMT');
* documentRef.set({ startTime:Firestore.Timestamp.fromDate(date) });
*
* ```
* @param {Date} date The date to initialize the `Timestamp` from.
* @return {Timestamp} A new `Timestamp` representing the same point in time
* as the given date.
*/
static fromDate(date: Date): Timestamp;
/**
* Creates a new timestamp from the given number of milliseconds.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.set({ startTime:Firestore.Timestamp.fromMillis(42) });
*
* ```
* @param {number} milliseconds Number of milliseconds since Unix epoch
* 1970-01-01T00:00:00Z.
* @return {Timestamp} A new `Timestamp` representing the same point in time
* as the given number of milliseconds.
*/
static fromMillis(milliseconds: number): Timestamp;
/**
* Generates a `Timestamp` object from a Timestamp proto.
*
* @private
* @internal
* @param {Object} timestamp The `Timestamp` Protobuf object.
*/
static fromProto(timestamp: google.protobuf.ITimestamp): Timestamp;
/**
* Creates a new timestamp.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.set({ startTime:new Firestore.Timestamp(42, 0) });
*
* ```
* @param {number} 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 {number} 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(seconds: number, nanoseconds: number);
/**
* The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(snap => {
* let updated = snap.updateTime;
* console.log(`Updated at ${updated.seconds}s ${updated.nanoseconds}ns`);
* });
*
* ```
* @type {number}
*/
get seconds(): number;
/**
* The non-negative fractions of a second at nanosecond resolution.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(snap => {
* let updated = snap.updateTime;
* console.log(`Updated at ${updated.seconds}s ${updated.nanoseconds}ns`);
* });
*
* ```
* @type {number}
*/
get nanoseconds(): number;
/**
* Returns a new `Date` corresponding to this timestamp. This may lose
* precision.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(snap => {
* console.log(`Document updated at: ${snap.updateTime.toDate()}`);
* });
*
* ```
* @return {Date} JavaScript `Date` object representing the same point in time
* as this `Timestamp`, with millisecond precision.
*/
toDate(): Date;
/**
* Returns the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(snap => {
* let startTime = snap.get('startTime');
* let endTime = snap.get('endTime');
* console.log(`Duration: ${endTime - startTime}`);
* });
*
* ```
* @return {number} The point in time corresponding to this timestamp,
* represented as the number of milliseconds since Unix epoch
* 1970-01-01T00:00:00Z.
*/
toMillis(): number;
/**
* Returns 'true' if this `Timestamp` is equal to the provided one.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(snap => {
* if (snap.createTime.isEqual(snap.updateTime)) {
* console.log('Document is in its initial state.');
* }
* });
*
* ```
* @param {any} other The `Timestamp` to compare against.
* @return {boolean} 'true' if this `Timestamp` is equal to the provided one.
*/
isEqual(other: firestore.Timestamp): boolean;
/**
* Generates the Protobuf `Timestamp` object for this timestamp.
*
* @private
* @internal
* @returns {Object} The `Timestamp` Protobuf object.
*/
toProto(): api.IValue;
/**
* Converts this object to a primitive `string`, which allows `Timestamp` objects to be compared
* using the `>`, `<=`, `>=` and `>` operators.
*
* @return {string} a string encoding of this object.
*/
valueOf(): string;
}

View File

@@ -0,0 +1,284 @@
"use strict";
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Timestamp = void 0;
const validate_1 = require("./validate");
/*!
* Number of nanoseconds in a millisecond.
*
* @type {number}
*/
const MS_TO_NANOS = 1000000;
/*!
* The minimum legal value for the "seconds" property of a Timestamp object.
*
* This value corresponds to 0001-01-01T00:00:00Z.
*
* @type {number}
*/
const MIN_SECONDS = -62135596800;
/*!
* The maximum legal value for the "seconds" property of a Timestamp object.
*
* This value corresponds to 9999-12-31T23:59:59.999999999Z.
*
* @type {number}
*/
const MAX_SECONDS = 253402300799;
/**
* 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.
*
* @see https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto
*/
class Timestamp {
/**
* Creates a new timestamp with the current date, with millisecond precision.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.set({ updateTime:Firestore.Timestamp.now() });
*
* ```
* @return {Timestamp} A new `Timestamp` representing the current date.
*/
static now() {
return Timestamp.fromMillis(Date.now());
}
/**
* Creates a new timestamp from the given date.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* let date = Date.parse('01 Jan 2000 00:00:00 GMT');
* documentRef.set({ startTime:Firestore.Timestamp.fromDate(date) });
*
* ```
* @param {Date} date The date to initialize the `Timestamp` from.
* @return {Timestamp} 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.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.set({ startTime:Firestore.Timestamp.fromMillis(42) });
*
* ```
* @param {number} milliseconds Number of milliseconds since Unix epoch
* 1970-01-01T00:00:00Z.
* @return {Timestamp} 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);
}
/**
* Generates a `Timestamp` object from a Timestamp proto.
*
* @private
* @internal
* @param {Object} timestamp The `Timestamp` Protobuf object.
*/
static fromProto(timestamp) {
return new Timestamp(Number(timestamp.seconds || 0), timestamp.nanos || 0);
}
/**
* Creates a new timestamp.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.set({ startTime:new Firestore.Timestamp(42, 0) });
*
* ```
* @param {number} 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 {number} 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(seconds, nanoseconds) {
(0, validate_1.validateInteger)('seconds', seconds, {
minValue: MIN_SECONDS,
maxValue: MAX_SECONDS,
});
(0, validate_1.validateInteger)('nanoseconds', nanoseconds, {
minValue: 0,
maxValue: 999999999,
});
this._seconds = seconds;
this._nanoseconds = nanoseconds;
}
/**
* The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(snap => {
* let updated = snap.updateTime;
* console.log(`Updated at ${updated.seconds}s ${updated.nanoseconds}ns`);
* });
*
* ```
* @type {number}
*/
get seconds() {
return this._seconds;
}
/**
* The non-negative fractions of a second at nanosecond resolution.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(snap => {
* let updated = snap.updateTime;
* console.log(`Updated at ${updated.seconds}s ${updated.nanoseconds}ns`);
* });
*
* ```
* @type {number}
*/
get nanoseconds() {
return this._nanoseconds;
}
/**
* Returns a new `Date` corresponding to this timestamp. This may lose
* precision.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(snap => {
* console.log(`Document updated at: ${snap.updateTime.toDate()}`);
* });
*
* ```
* @return {Date} JavaScript `Date` object representing the same point in time
* as this `Timestamp`, with millisecond precision.
*/
toDate() {
return new Date(this._seconds * 1000 + Math.round(this._nanoseconds / MS_TO_NANOS));
}
/**
* Returns the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(snap => {
* let startTime = snap.get('startTime');
* let endTime = snap.get('endTime');
* console.log(`Duration: ${endTime - startTime}`);
* });
*
* ```
* @return {number} 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 + Math.floor(this._nanoseconds / MS_TO_NANOS);
}
/**
* Returns 'true' if this `Timestamp` is equal to the provided one.
*
* @example
* ```
* let documentRef = firestore.doc('col/doc');
*
* documentRef.get().then(snap => {
* if (snap.createTime.isEqual(snap.updateTime)) {
* console.log('Document is in its initial state.');
* }
* });
*
* ```
* @param {any} other The `Timestamp` to compare against.
* @return {boolean} 'true' if this `Timestamp` is equal to the provided one.
*/
isEqual(other) {
return (this === other ||
(other instanceof Timestamp &&
this._seconds === other.seconds &&
this._nanoseconds === other.nanoseconds));
}
/**
* Generates the Protobuf `Timestamp` object for this timestamp.
*
* @private
* @internal
* @returns {Object} The `Timestamp` Protobuf object.
*/
toProto() {
const timestamp = {};
if (this.seconds) {
timestamp.seconds = this.seconds.toString();
}
if (this.nanoseconds) {
timestamp.nanos = this.nanoseconds;
}
return { timestampValue: timestamp };
}
/**
* Converts this object to a primitive `string`, which allows `Timestamp` objects to be compared
* using the `>`, `<=`, `>=` and `>` operators.
*
* @return {string} a string encoding of this object.
*/
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 lexicographical
// 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 lexicographical 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;
}
}
exports.Timestamp = Timestamp;
//# sourceMappingURL=timestamp.js.map

View File

@@ -0,0 +1,262 @@
/*!
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.
*/
import * as firestore from '@google-cloud/firestore';
import { DocumentSnapshot } from './document';
import { Firestore } from './index';
import { FieldPath } from './path';
import { AggregateQuerySnapshot } from './reference/aggregate-query-snapshot';
import { DocumentReference } from './reference/document-reference';
import { QuerySnapshot } from './reference/query-snapshot';
/**
* A reference to a transaction.
*
* The Transaction object passed to a transaction's updateFunction provides
* the methods to read and write data within the transaction context. See
* [runTransaction()]{@link Firestore#runTransaction}.
*
* @class Transaction
*/
export declare class Transaction implements firestore.Transaction {
private readonly _firestore;
private readonly _maxAttempts;
private readonly _requestTag;
/** Optional, could be set only if transaction is read only */
private readonly _readOnlyReadTime;
/** `undefined` if transaction is read only */
private readonly _writeBatch;
/** `undefined` if transaction is read only */
private readonly _backoff;
/**
* Promise that resolves to the transaction ID of the current attempt.
* It is lazily initialised upon the first read. Upon retry, it is reset and
* `_prevTransactionId` is set
*/
private _transactionIdPromise?;
private _prevTransactionId?;
/**
* @private
*
* @param firestore The Firestore Database client.
* @param requestTag A unique client-assigned identifier for the scope of
* this transaction.
* @param transactionOptions The user-defined options for this transaction.
*/
constructor(firestore: Firestore, requestTag: string, transactionOptions?: firestore.ReadWriteTransactionOptions | firestore.ReadOnlyTransactionOptions);
/**
* Retrieves a query result. Holds a pessimistic lock on all returned
* documents.
*
* @param {Query} query A query to execute.
* @return {Promise<QuerySnapshot>} A QuerySnapshot for the retrieved data.
*/
get<AppModelType, DbModelType extends firestore.DocumentData>(query: firestore.Query<AppModelType, DbModelType>): Promise<QuerySnapshot<AppModelType, DbModelType>>;
/**
* Reads the document referenced by the provided `DocumentReference.`
* Holds a pessimistic lock on the returned document.
*
* @param {DocumentReference} documentRef A reference to the document to be read.
* @return {Promise<DocumentSnapshot>} A DocumentSnapshot for the read data.
*/
get<AppModelType, DbModelType extends firestore.DocumentData>(documentRef: firestore.DocumentReference<AppModelType, DbModelType>): Promise<DocumentSnapshot<AppModelType, DbModelType>>;
/**
* Retrieves an aggregate query result. Holds a pessimistic lock on all
* documents that were matched by the underlying query.
*
* @param aggregateQuery An aggregate query to execute.
* @return An AggregateQuerySnapshot for the retrieved data.
*/
get<AppModelType, DbModelType extends firestore.DocumentData, AggregateSpecType extends firestore.AggregateSpec>(aggregateQuery: firestore.AggregateQuery<AggregateSpecType, AppModelType, DbModelType>): Promise<AggregateQuerySnapshot<AggregateSpecType, AppModelType, DbModelType>>;
/**
* Retrieves multiple documents from Firestore. Holds a pessimistic lock on
* all returned documents.
*
* The first argument is required and must be of type `DocumentReference`
* followed by any additional `DocumentReference` documents. If used, the
* optional `ReadOptions` must be the last argument.
*
* @param {...DocumentReference|ReadOptions} documentRefsOrReadOptions The
* `DocumentReferences` to receive, followed by an optional field mask.
* @returns {Promise<Array.<DocumentSnapshot>>} A Promise that
* contains an array with the resulting document snapshots.
*
* @example
* ```
* let firstDoc = firestore.doc('col/doc1');
* let secondDoc = firestore.doc('col/doc2');
* let resultDoc = firestore.doc('col/doc3');
*
* firestore.runTransaction(transaction => {
* return transaction.getAll(firstDoc, secondDoc).then(docs => {
* transaction.set(resultDoc, {
* sum: docs[0].get('count') + docs[1].get('count')
* });
* });
* });
* ```
*/
getAll<AppModelType, DbModelType extends firestore.DocumentData>(...documentRefsOrReadOptions: Array<firestore.DocumentReference<AppModelType, DbModelType> | firestore.ReadOptions>): Promise<Array<DocumentSnapshot<AppModelType, DbModelType>>>;
/**
* Create the document referred to by the provided
* [DocumentReference]{@link DocumentReference}. The operation will
* fail the transaction if a document exists at the specified location.
*
* @param {DocumentReference} documentRef A reference to the document to be
* created.
* @param {DocumentData} data The object data to serialize as the document.
* @returns {Transaction} This Transaction instance. Used for
* chaining method calls.
*
* @example
* ```
* firestore.runTransaction(transaction => {
* let documentRef = firestore.doc('col/doc');
* return transaction.get(documentRef).then(doc => {
* if (!doc.exists) {
* transaction.create(documentRef, { foo: 'bar' });
* }
* });
* });
* ```
*/
create<AppModelType, DbModelType extends firestore.DocumentData>(documentRef: firestore.DocumentReference<AppModelType, DbModelType>, data: firestore.WithFieldValue<AppModelType>): Transaction;
set<AppModelType, DbModelType extends firestore.DocumentData>(documentRef: firestore.DocumentReference<AppModelType, DbModelType>, data: firestore.PartialWithFieldValue<AppModelType>, options: firestore.SetOptions): Transaction;
set<AppModelType, DbModelType extends firestore.DocumentData>(documentRef: firestore.DocumentReference<AppModelType, DbModelType>, data: firestore.WithFieldValue<AppModelType>): Transaction;
/**
* Updates fields in the document referred to by the provided
* [DocumentReference]{@link DocumentReference}. The update will
* fail if applied to a document that does not exist.
*
* The update() method accepts either an object with field paths encoded as
* keys and field values encoded as values, or a variable number of arguments
* that alternate between field paths and field values. Nested fields can be
* updated by providing dot-separated field path strings or by providing
* FieldPath objects.
*
* A Precondition restricting this update can be specified as the last
* argument.
*
* @param {DocumentReference} documentRef A reference to the document to be
* updated.
* @param {UpdateData|string|FieldPath} dataOrField An object
* containing the fields and values with which to update the document
* or the path of the first field to update.
* @param {
* ...(Precondition|*|string|FieldPath)} preconditionOrValues -
* An alternating list of field paths and values to update or a Precondition
* to to enforce on this update.
* @throws {Error} If the provided input is not valid Firestore data.
* @returns {Transaction} This Transaction instance. Used for
* chaining method calls.
*
* @example
* ```
* firestore.runTransaction(transaction => {
* let documentRef = firestore.doc('col/doc');
* return transaction.get(documentRef).then(doc => {
* if (doc.exists) {
* transaction.update(documentRef, { count: doc.get('count') + 1 });
* } else {
* transaction.create(documentRef, { count: 1 });
* }
* });
* });
* ```
*/
update<AppModelType, DbModelType extends firestore.DocumentData>(documentRef: firestore.DocumentReference<AppModelType, DbModelType>, dataOrField: firestore.UpdateData<DbModelType> | string | firestore.FieldPath, ...preconditionOrValues: Array<firestore.Precondition | unknown | string | firestore.FieldPath>): Transaction;
/**
* Deletes the document referred to by the provided [DocumentReference]
* {@link DocumentReference}.
*
* @param {DocumentReference} documentRef A reference to the document to be
* deleted.
* @param {Precondition=} precondition A precondition to enforce for this
* delete.
* @param {Timestamp=} precondition.lastUpdateTime If set, enforces that the
* document was last updated at lastUpdateTime. Fails the transaction if the
* document doesn't exist or was last updated at a different time.
* @param {boolean=} precondition.exists If set, enforces that the target
* document must or must not exist.
* @returns {Transaction} This Transaction instance. Used for
* chaining method calls.
*
* @example
* ```
* firestore.runTransaction(transaction => {
* let documentRef = firestore.doc('col/doc');
* transaction.delete(documentRef);
* return Promise.resolve();
* });
* ```
*/
delete(documentRef: DocumentReference<any, any>, precondition?: firestore.Precondition): this;
/**
* Commits all queued-up changes in this transaction and releases all locks.
*
* @private
* @internal
*/
commit(): Promise<void>;
/**
* Releases all locks and rolls back this transaction. The rollback process
* is completed asynchronously and this function resolves before the operation
* is completed.
*
* @private
* @internal
*/
rollback(): Promise<void>;
/**
* Executes `updateFunction()` and commits the transaction with retry.
*
* @private
* @internal
* @param updateFunction The user function to execute within the transaction
* context.
*/
runTransaction<T>(updateFunction: (transaction: Transaction) => Promise<T>): Promise<T>;
/**
* Make single attempt to execute `updateFunction()` and commit the
* transaction. Will rollback upon error.
*
* @private
* @internal
* @param updateFunction The user function to execute within the transaction
* context.
*/
runTransactionOnce<T>(updateFunction: (transaction: Transaction) => Promise<T>): Promise<T>;
/**
* Given a function that performs a read operation, ensures that the first one
* is provided with new transaction options and all subsequent ones are queued
* upon the resulting transaction ID.
*/
private withLazyStartedTransaction;
private getSingleFn;
private getBatchFn;
private getQueryFn;
}
/**
* Parses the arguments for the `getAll()` call supported by both the Firestore
* and Transaction class.
*
* @private
* @internal
* @param documentRefsOrReadOptions An array of document references followed by
* an optional ReadOptions object.
*/
export declare function parseGetAllArguments<AppModelType, DbModelType extends firestore.DocumentData>(documentRefsOrReadOptions: Array<firestore.DocumentReference<AppModelType, DbModelType> | firestore.ReadOptions>): {
documents: Array<DocumentReference<AppModelType, DbModelType>>;
fieldMask: FieldPath[] | undefined;
};

View File

@@ -0,0 +1,619 @@
"use strict";
/*!
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Transaction = void 0;
exports.parseGetAllArguments = parseGetAllArguments;
const backoff_1 = require("./backoff");
const index_1 = require("./index");
const logger_1 = require("./logger");
const path_1 = require("./path");
const aggregate_query_1 = require("./reference/aggregate-query");
const document_reference_1 = require("./reference/document-reference");
const query_1 = require("./reference/query");
const helpers_1 = require("./reference/helpers");
const util_1 = require("./util");
const validate_1 = require("./validate");
const document_reader_1 = require("./document-reader");
const trace_util_1 = require("./telemetry/trace-util");
/*!
* Error message for transactional reads that were executed after performing
* writes.
*/
const READ_AFTER_WRITE_ERROR_MSG = 'Firestore transactions require all reads to be executed before all writes.';
const READ_ONLY_WRITE_ERROR_MSG = 'Firestore read-only transactions cannot execute writes.';
/**
* A reference to a transaction.
*
* The Transaction object passed to a transaction's updateFunction provides
* the methods to read and write data within the transaction context. See
* [runTransaction()]{@link Firestore#runTransaction}.
*
* @class Transaction
*/
class Transaction {
/**
* @private
*
* @param firestore The Firestore Database client.
* @param requestTag A unique client-assigned identifier for the scope of
* this transaction.
* @param transactionOptions The user-defined options for this transaction.
*/
constructor(firestore, requestTag, transactionOptions) {
this._maxAttempts = index_1.DEFAULT_MAX_TRANSACTION_ATTEMPTS;
this._firestore = firestore;
this._requestTag = requestTag;
if (transactionOptions === null || transactionOptions === void 0 ? void 0 : transactionOptions.readOnly) {
// Avoid initialising write batch and backoff unnecessarily for read-only transactions
this._maxAttempts = 1;
this._readOnlyReadTime = transactionOptions.readTime;
}
else {
this._maxAttempts =
(transactionOptions === null || transactionOptions === void 0 ? void 0 : transactionOptions.maxAttempts) || index_1.DEFAULT_MAX_TRANSACTION_ATTEMPTS;
this._writeBatch = firestore.batch();
this._backoff = new backoff_1.ExponentialBackoff();
}
}
/**
* Retrieve a document or a query result from the database. Holds a
* pessimistic lock on all returned documents.
*
* @param {DocumentReference|Query} refOrQuery The document or query to
* return.
* @returns {Promise} A Promise that resolves with a DocumentSnapshot or
* QuerySnapshot for the returned documents.
*
* @example
* ```
* firestore.runTransaction(transaction => {
* let documentRef = firestore.doc('col/doc');
* return transaction.get(documentRef).then(doc => {
* if (doc.exists) {
* transaction.update(documentRef, { count: doc.get('count') + 1 });
* } else {
* transaction.create(documentRef, { count: 1 });
* }
* });
* });
* ```
*/
get(refOrQuery) {
if (this._writeBatch && !this._writeBatch.isEmpty) {
throw new Error(READ_AFTER_WRITE_ERROR_MSG);
}
if (refOrQuery instanceof document_reference_1.DocumentReference) {
return this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_TRANSACTION_GET_DOCUMENT, () => {
return this.withLazyStartedTransaction(refOrQuery, this.getSingleFn);
});
}
if (refOrQuery instanceof query_1.Query || refOrQuery instanceof aggregate_query_1.AggregateQuery) {
return this._firestore._traceUtil.startActiveSpan(refOrQuery instanceof query_1.Query
? trace_util_1.SPAN_NAME_TRANSACTION_GET_QUERY
: trace_util_1.SPAN_NAME_TRANSACTION_GET_AGGREGATION_QUERY, () => {
return this.withLazyStartedTransaction(refOrQuery, this.getQueryFn);
});
}
throw new Error('Value for argument "refOrQuery" must be a DocumentReference, Query, or AggregateQuery.');
}
/**
* Retrieves multiple documents from Firestore. Holds a pessimistic lock on
* all returned documents.
*
* The first argument is required and must be of type `DocumentReference`
* followed by any additional `DocumentReference` documents. If used, the
* optional `ReadOptions` must be the last argument.
*
* @param {...DocumentReference|ReadOptions} documentRefsOrReadOptions The
* `DocumentReferences` to receive, followed by an optional field mask.
* @returns {Promise<Array.<DocumentSnapshot>>} A Promise that
* contains an array with the resulting document snapshots.
*
* @example
* ```
* let firstDoc = firestore.doc('col/doc1');
* let secondDoc = firestore.doc('col/doc2');
* let resultDoc = firestore.doc('col/doc3');
*
* firestore.runTransaction(transaction => {
* return transaction.getAll(firstDoc, secondDoc).then(docs => {
* transaction.set(resultDoc, {
* sum: docs[0].get('count') + docs[1].get('count')
* });
* });
* });
* ```
*/
getAll(...documentRefsOrReadOptions) {
if (this._writeBatch && !this._writeBatch.isEmpty) {
throw new Error(READ_AFTER_WRITE_ERROR_MSG);
}
(0, validate_1.validateMinNumberOfArguments)('Transaction.getAll', documentRefsOrReadOptions, 1);
return this.withLazyStartedTransaction(parseGetAllArguments(documentRefsOrReadOptions), this.getBatchFn);
}
/**
* Create the document referred to by the provided
* [DocumentReference]{@link DocumentReference}. The operation will
* fail the transaction if a document exists at the specified location.
*
* @param {DocumentReference} documentRef A reference to the document to be
* created.
* @param {DocumentData} data The object data to serialize as the document.
* @returns {Transaction} This Transaction instance. Used for
* chaining method calls.
*
* @example
* ```
* firestore.runTransaction(transaction => {
* let documentRef = firestore.doc('col/doc');
* return transaction.get(documentRef).then(doc => {
* if (!doc.exists) {
* transaction.create(documentRef, { foo: 'bar' });
* }
* });
* });
* ```
*/
create(documentRef, data) {
if (!this._writeBatch) {
throw new Error(READ_ONLY_WRITE_ERROR_MSG);
}
this._writeBatch.create(documentRef, data);
return this;
}
/**
* Writes to the document referred to by the provided
* [DocumentReference]{@link DocumentReference}. If the document
* does not exist yet, it will be created. If you pass
* [SetOptions]{@link SetOptions}, the provided data can be merged into the
* existing document.
*
* @param {DocumentReference} documentRef A reference to the document to be
* set.
* @param {T|Partial<T>} data The object to serialize as the document.
* @param {SetOptions=} options An object to configure the set behavior.
* @param {boolean=} options.merge - If true, set() merges the values
* specified in its data argument. Fields omitted from this set() call remain
* untouched. If your input sets any field to an empty map, all nested fields
* are overwritten.
* @param {Array.<string|FieldPath>=} options.mergeFields - If provided,
* set() only replaces the specified field paths. Any field path that is not
* specified is ignored and remains untouched. If your input sets any field to
* an empty map, all nested fields are overwritten.
* @throws {Error} If the provided input is not a valid Firestore document.
* @returns {Transaction} This Transaction instance. Used for
* chaining method calls.
*
* @example
* ```
* firestore.runTransaction(transaction => {
* let documentRef = firestore.doc('col/doc');
* transaction.set(documentRef, { foo: 'bar' });
* return Promise.resolve();
* });
* ```
*/
set(documentRef, data, options) {
if (!this._writeBatch) {
throw new Error(READ_ONLY_WRITE_ERROR_MSG);
}
if (options) {
this._writeBatch.set(documentRef, data, options);
}
else {
this._writeBatch.set(documentRef, data);
}
return this;
}
/**
* Updates fields in the document referred to by the provided
* [DocumentReference]{@link DocumentReference}. The update will
* fail if applied to a document that does not exist.
*
* The update() method accepts either an object with field paths encoded as
* keys and field values encoded as values, or a variable number of arguments
* that alternate between field paths and field values. Nested fields can be
* updated by providing dot-separated field path strings or by providing
* FieldPath objects.
*
* A Precondition restricting this update can be specified as the last
* argument.
*
* @param {DocumentReference} documentRef A reference to the document to be
* updated.
* @param {UpdateData|string|FieldPath} dataOrField An object
* containing the fields and values with which to update the document
* or the path of the first field to update.
* @param {
* ...(Precondition|*|string|FieldPath)} preconditionOrValues -
* An alternating list of field paths and values to update or a Precondition
* to to enforce on this update.
* @throws {Error} If the provided input is not valid Firestore data.
* @returns {Transaction} This Transaction instance. Used for
* chaining method calls.
*
* @example
* ```
* firestore.runTransaction(transaction => {
* let documentRef = firestore.doc('col/doc');
* return transaction.get(documentRef).then(doc => {
* if (doc.exists) {
* transaction.update(documentRef, { count: doc.get('count') + 1 });
* } else {
* transaction.create(documentRef, { count: 1 });
* }
* });
* });
* ```
*/
update(documentRef, dataOrField, ...preconditionOrValues) {
if (!this._writeBatch) {
throw new Error(READ_ONLY_WRITE_ERROR_MSG);
}
// eslint-disable-next-line prefer-rest-params
(0, validate_1.validateMinNumberOfArguments)('Transaction.update', arguments, 2);
this._writeBatch.update(documentRef, dataOrField, ...preconditionOrValues);
return this;
}
/**
* Deletes the document referred to by the provided [DocumentReference]
* {@link DocumentReference}.
*
* @param {DocumentReference} documentRef A reference to the document to be
* deleted.
* @param {Precondition=} precondition A precondition to enforce for this
* delete.
* @param {Timestamp=} precondition.lastUpdateTime If set, enforces that the
* document was last updated at lastUpdateTime. Fails the transaction if the
* document doesn't exist or was last updated at a different time.
* @param {boolean=} precondition.exists If set, enforces that the target
* document must or must not exist.
* @returns {Transaction} This Transaction instance. Used for
* chaining method calls.
*
* @example
* ```
* firestore.runTransaction(transaction => {
* let documentRef = firestore.doc('col/doc');
* transaction.delete(documentRef);
* return Promise.resolve();
* });
* ```
*/
delete(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
documentRef, precondition) {
if (!this._writeBatch) {
throw new Error(READ_ONLY_WRITE_ERROR_MSG);
}
this._writeBatch.delete(documentRef, precondition);
return this;
}
/**
* Commits all queued-up changes in this transaction and releases all locks.
*
* @private
* @internal
*/
async commit() {
var _a;
return this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_TRANSACTION_COMMIT, async () => {
if (!this._writeBatch) {
throw new Error(READ_ONLY_WRITE_ERROR_MSG);
}
// If we have not performed any reads in this particular attempt
// then the writes will be atomically committed without a transaction ID
let transactionId;
if (this._transactionIdPromise) {
transactionId = await this._transactionIdPromise;
}
else if (this._writeBatch.isEmpty) {
// If we have not started a transaction (no reads) and we have no writes
// then the commit is a no-op (success)
return;
}
await this._writeBatch._commit({
transactionId,
requestTag: this._requestTag,
});
this._transactionIdPromise = undefined;
this._prevTransactionId = transactionId;
}, {
[trace_util_1.ATTRIBUTE_KEY_IS_TRANSACTIONAL]: true,
[trace_util_1.ATTRIBUTE_KEY_DOC_COUNT]: (_a = this._writeBatch) === null || _a === void 0 ? void 0 : _a._opCount,
});
}
/**
* Releases all locks and rolls back this transaction. The rollback process
* is completed asynchronously and this function resolves before the operation
* is completed.
*
* @private
* @internal
*/
async rollback() {
return this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_TRANSACTION_ROLLBACK, async () => {
// No need to roll back if we have not lazily started the transaction
// or if we are read only
if (!this._transactionIdPromise || !this._writeBatch) {
return;
}
let transactionId;
try {
transactionId = await this._transactionIdPromise;
}
catch (_a) {
// This means the initial read operation rejected
// and we do not have a transaction ID to roll back
this._transactionIdPromise = undefined;
return;
}
const request = {
database: this._firestore.formattedName,
transaction: transactionId,
};
this._transactionIdPromise = undefined;
this._prevTransactionId = transactionId;
// We don't need to wait for rollback to completed before continuing.
// If there are any locks held, then rollback will eventually release them.
// Rollback can be done concurrently thereby reducing latency caused by
// otherwise blocking.
this._firestore
.request('rollback', request, this._requestTag)
.catch(err => {
(0, logger_1.logger)('Firestore.runTransaction', this._requestTag, 'Best effort to rollback failed with error:', err);
});
});
}
/**
* Executes `updateFunction()` and commits the transaction with retry.
*
* @private
* @internal
* @param updateFunction The user function to execute within the transaction
* context.
*/
async runTransaction(updateFunction) {
return this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_TRANSACTION_RUN, async (span) => {
// No backoff is set for readonly transactions (i.e. attempts == 1)
if (!this._writeBatch) {
return this.runTransactionOnce(updateFunction);
}
let lastError = undefined;
for (let attempt = 0; attempt < this._maxAttempts; ++attempt) {
span.setAttributes({
[trace_util_1.ATTRIBUTE_KEY_TRANSACTION_TYPE]: this._writeBatch
? 'READ_WRITE'
: 'READ_ONLY',
[trace_util_1.ATTRIBUTE_KEY_ATTEMPTS_ALLOWED]: this._maxAttempts,
[trace_util_1.ATTRIBUTE_KEY_ATTEMPTS_REMAINING]: this._maxAttempts - attempt - 1,
});
try {
if (lastError) {
(0, logger_1.logger)('Firestore.runTransaction', this._requestTag, 'Retrying transaction after error:', lastError);
span.addEvent('Initiate transaction retry');
}
this._writeBatch._reset();
await maybeBackoff(this._backoff, lastError);
return await this.runTransactionOnce(updateFunction);
}
catch (err) {
lastError = err;
if (!isRetryableTransactionError(err)) {
break;
}
}
}
(0, logger_1.logger)('Firestore.runTransaction', this._requestTag, 'Transaction not eligible for retry, returning error: %s', lastError);
return Promise.reject(lastError);
});
}
/**
* Make single attempt to execute `updateFunction()` and commit the
* transaction. Will rollback upon error.
*
* @private
* @internal
* @param updateFunction The user function to execute within the transaction
* context.
*/
async runTransactionOnce(updateFunction) {
try {
const promise = updateFunction(this);
if (!(promise instanceof Promise)) {
throw new Error('You must return a Promise in your transaction()-callback.');
}
const result = await promise;
if (this._writeBatch) {
await this.commit();
}
return result;
}
catch (err) {
(0, logger_1.logger)('Firestore.runTransaction', this._requestTag, 'Rolling back transaction after callback error:', err);
await this.rollback();
return Promise.reject(err);
}
}
/**
* Given a function that performs a read operation, ensures that the first one
* is provided with new transaction options and all subsequent ones are queued
* upon the resulting transaction ID.
*/
withLazyStartedTransaction(param, resultFn) {
if (this._transactionIdPromise) {
// Simply queue this subsequent read operation after the first read
// operation has resolved and we don't expect a transaction ID in the
// response because we are not starting a new transaction
return this._transactionIdPromise
.then(opts => resultFn.call(this, param, opts))
.then(r => r.result);
}
else {
if (this._readOnlyReadTime) {
// We do not start a transaction for read-only transactions
// do not set _prevTransactionId
return resultFn
.call(this, param, this._readOnlyReadTime)
.then(r => r.result);
}
else {
// This is the first read of the transaction so we create the appropriate
// options for lazily starting the transaction inside this first read op
const opts = {};
if (this._writeBatch) {
opts.readWrite = this._prevTransactionId
? { retryTransaction: this._prevTransactionId }
: {};
}
else {
opts.readOnly = {};
}
const resultPromise = resultFn.call(this, param, opts);
// Ensure the _transactionIdPromise is set synchronously so that
// subsequent operations will not race to start another transaction
this._transactionIdPromise = resultPromise.then(r => {
if (!r.transaction) {
// Illegal state
// The read operation was provided with new transaction options but did not return a transaction ID
// Rejecting here will cause all queued reads to reject
throw new Error('Transaction ID was missing from server response');
}
return r.transaction;
});
return resultPromise.then(r => r.result);
}
}
}
async getSingleFn(document, opts) {
const documentReader = new document_reader_1.DocumentReader(this._firestore, [document], undefined, opts);
const { transaction, result: [result], } = await documentReader._get(this._requestTag);
return { transaction, result };
}
async getBatchFn({ documents, fieldMask, }, opts) {
return this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_TRANSACTION_GET_DOCUMENTS, async () => {
const documentReader = new document_reader_1.DocumentReader(this._firestore, documents, fieldMask, opts);
return documentReader._get(this._requestTag);
});
}
async getQueryFn(query, opts) {
return query._get(opts);
}
}
exports.Transaction = Transaction;
/**
* Parses the arguments for the `getAll()` call supported by both the Firestore
* and Transaction class.
*
* @private
* @internal
* @param documentRefsOrReadOptions An array of document references followed by
* an optional ReadOptions object.
*/
function parseGetAllArguments(documentRefsOrReadOptions) {
let documents;
let readOptions = undefined;
if (Array.isArray(documentRefsOrReadOptions[0])) {
throw new Error('getAll() no longer accepts an array as its first argument. ' +
'Please unpack your array and call getAll() with individual arguments.');
}
if (documentRefsOrReadOptions.length > 0 &&
(0, util_1.isPlainObject)(documentRefsOrReadOptions[documentRefsOrReadOptions.length - 1])) {
readOptions = documentRefsOrReadOptions.pop();
documents = documentRefsOrReadOptions;
}
else {
documents = documentRefsOrReadOptions;
}
for (let i = 0; i < documents.length; ++i) {
(0, helpers_1.validateDocumentReference)(i, documents[i]);
}
validateReadOptions('options', readOptions, { optional: true });
const fieldMask = readOptions && readOptions.fieldMask
? readOptions.fieldMask.map(fieldPath => path_1.FieldPath.fromArgument(fieldPath))
: undefined;
return { fieldMask, documents };
}
/**
* Validates the use of 'options' as ReadOptions and enforces that 'fieldMask'
* is an array of strings or field paths.
*
* @private
* @internal
* @param arg The argument name or argument index (for varargs methods).
* @param value The input to validate.
* @param options Options that specify whether the ReadOptions can be omitted.
*/
function validateReadOptions(arg, value, options) {
if (!(0, validate_1.validateOptional)(value, options)) {
if (!(0, util_1.isObject)(value)) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'read option')} Input is not an object.'`);
}
const options = value;
if (options.fieldMask !== undefined) {
if (!Array.isArray(options.fieldMask)) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'read option')} "fieldMask" is not an array.`);
}
for (let i = 0; i < options.fieldMask.length; ++i) {
try {
(0, path_1.validateFieldPath)(i, options.fieldMask[i]);
}
catch (err) {
throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'read option')} "fieldMask" is not valid: ${err.message}`);
}
}
}
}
}
function isRetryableTransactionError(error) {
if (error.code !== undefined) {
// This list is based on https://github.com/firebase/firebase-js-sdk/blob/master/packages/firestore/src/core/transaction_runner.ts#L112
switch (error.code) {
case 10 /* StatusCode.ABORTED */:
case 1 /* StatusCode.CANCELLED */:
case 2 /* StatusCode.UNKNOWN */:
case 4 /* StatusCode.DEADLINE_EXCEEDED */:
case 13 /* StatusCode.INTERNAL */:
case 14 /* StatusCode.UNAVAILABLE */:
case 16 /* StatusCode.UNAUTHENTICATED */:
case 8 /* StatusCode.RESOURCE_EXHAUSTED */:
return true;
case 3 /* StatusCode.INVALID_ARGUMENT */:
// The Firestore backend uses "INVALID_ARGUMENT" for transactions
// IDs that have expired. While INVALID_ARGUMENT is generally not
// retryable, we retry this specific case.
return !!error.message.match(/transaction has expired/);
default:
return false;
}
}
return false;
}
/**
* Delays further operations based on the provided error.
*
* @private
* @internal
* @return A Promise that resolves after the delay expired.
*/
async function maybeBackoff(backoff, error) {
if ((error === null || error === void 0 ? void 0 : error.code) === 8 /* StatusCode.RESOURCE_EXHAUSTED */) {
backoff.resetToMax();
}
await backoff.backoffAndWait();
}
//# sourceMappingURL=transaction.js.map

View File

@@ -0,0 +1,89 @@
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
import { FirestoreDataConverter, DocumentData } from '@google-cloud/firestore';
import { CallOptions } from 'google-gax';
import { Duplex } from 'stream';
import { google } from '../protos/firestore_v1_proto_api';
import { FieldPath } from './path';
import api = google.firestore.v1;
/**
* A map in the format of the Proto API
*/
export interface ApiMapValue {
[k: string]: google.firestore.v1.IValue;
}
/**
* The subset of methods we use from FirestoreClient.
*
* We don't depend on the actual Gapic client to avoid loading the GAX stack at
* module initialization time.
*/
export interface GapicClient {
getProjectId(): Promise<string>;
beginTransaction(request: api.IBeginTransactionRequest, options?: CallOptions): Promise<[api.IBeginTransactionResponse, unknown, unknown]>;
commit(request: api.ICommitRequest, options?: CallOptions): Promise<[api.ICommitResponse, unknown, unknown]>;
batchWrite(request: api.IBatchWriteRequest, options?: CallOptions): Promise<[api.IBatchWriteResponse, unknown, unknown]>;
rollback(request: api.IRollbackRequest, options?: CallOptions): Promise<[google.protobuf.IEmpty, unknown, unknown]>;
batchGetDocuments(request?: api.IBatchGetDocumentsRequest, options?: CallOptions): Duplex;
runQuery(request?: api.IRunQueryRequest, options?: CallOptions): Duplex;
runAggregationQuery(request?: api.IRunAggregationQueryRequest, options?: CallOptions): Duplex;
listDocuments(request: api.IListDocumentsRequest, options?: CallOptions): Promise<[api.IDocument[], unknown, unknown]>;
listCollectionIds(request: api.IListCollectionIdsRequest, options?: CallOptions): Promise<[string[], unknown, unknown]>;
listen(options?: CallOptions): Duplex;
partitionQueryStream(request?: api.IPartitionQueryRequest, options?: CallOptions): Duplex;
close(): Promise<void>;
}
/** Request/response methods used in the Firestore SDK. */
export type FirestoreUnaryMethod = 'listDocuments' | 'listCollectionIds' | 'rollback' | 'beginTransaction' | 'commit' | 'batchWrite';
/** Streaming methods used in the Firestore SDK. */
export type FirestoreStreamingMethod = 'listen' | 'partitionQueryStream' | 'runQuery' | 'runAggregationQuery' | 'batchGetDocuments';
/** Type signature for the unary methods in the GAPIC layer. */
export type UnaryMethod<Req, Resp> = (request: Req, callOptions: CallOptions) => Promise<[Resp, unknown, unknown]>;
export type RBTree = any;
/**
* A default converter to use when none is provided.
* @private
* @internal
*/
export declare function defaultConverter<AppModelType, DbModelType extends DocumentData>(): FirestoreDataConverter<AppModelType, DbModelType>;
/**
* Update data that has been resolved to a mapping of FieldPaths to values.
*/
export type UpdateMap = Map<FieldPath, unknown>;
/**
* Internal user data validation options.
* @private
* @internal
*/
export interface ValidationOptions {
/** At what level field deletes are supported. */
allowDeletes: 'none' | 'root' | 'all';
/** Whether server transforms are supported. */
allowTransforms: boolean;
/**
* Whether undefined values are allowed. Undefined values cannot appear at
* the root.
*/
allowUndefined: boolean;
}
/**
* A Firestore Proto value in ProtoJs format.
* @private
* @internal
*/
export interface ProtobufJsValue extends api.IValue {
valueType?: string;
}

View File

@@ -0,0 +1,44 @@
"use strict";
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultConverter = defaultConverter;
/**
* A default converter to use when none is provided.
*
* By declaring the converter as a variable instead of creating the object
* inside defaultConverter(), object equality when comparing default converters
* is preserved.
* @private
* @internal
*/
const defaultConverterObj = {
toFirestore(modelObject) {
return modelObject;
},
fromFirestore(snapshot) {
return snapshot.data();
},
};
/**
* A default converter to use when none is provided.
* @private
* @internal
*/
function defaultConverter() {
return defaultConverterObj;
}
//# sourceMappingURL=types.js.map

View File

@@ -0,0 +1,178 @@
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
import { DocumentData } from '@google-cloud/firestore';
import type { GoogleError } from 'google-gax';
import type { BackoffSettings } from 'google-gax/build/src/gax';
import Dict = NodeJS.Dict;
/**
* A Promise implementation that supports deferred resolution.
* @private
* @internal
*/
export declare class Deferred<R> {
promise: Promise<R>;
resolve: (value: R | Promise<R>) => void;
reject: (reason: Error) => void;
constructor();
}
/**
* Generate a unique client-side identifier.
*
* Used for the creation of new documents.
*
* @private
* @internal
* @returns {string} A unique 20-character wide identifier.
*/
export declare function autoId(): string;
/**
* Generate a short and semi-random client-side identifier.
*
* Used for the creation of request tags.
*
* @private
* @internal
* @returns {string} A random 5-character wide identifier.
*/
export declare function requestTag(): string;
/**
* Determines whether `value` is a JavaScript object.
*
* @private
* @internal
*/
export declare function isObject(value: unknown): value is {
[k: string]: unknown;
};
/**
* Verifies that 'obj' is a plain JavaScript object that can be encoded as a
* 'Map' in Firestore.
*
* @private
* @internal
* @param input The argument to verify.
* @returns 'true' if the input can be a treated as a plain object.
*/
export declare function isPlainObject(input: unknown): input is DocumentData;
/**
* Returns whether `value` has no custom properties.
*
* @private
* @internal
*/
export declare function isEmpty(value: {}): boolean;
/**
* Determines whether `value` is a JavaScript function.
*
* @private
* @internal
*/
export declare function isFunction(value: unknown): boolean;
/**
* Determines whether the provided error is considered permanent for the given
* RPC.
*
* @private
* @internal
*/
export declare function isPermanentRpcError(err: GoogleError, methodName: string): boolean;
/**
* Returns the list of retryable error codes specified in the service
* configuration.
* @private
* @internal
*/
export declare function getRetryCodes(methodName: string): number[];
/**
* Gets the total timeout in milliseconds from the retry settings in
* the service config for the given RPC. If the total timeout is not
* set, then `0` is returned.
*
* @private
* @internal
*/
export declare function getTotalTimeout(methodName: string): number;
/**
* Returns the backoff setting from the service configuration.
* @private
* @internal
*/
export declare function getRetryParams(methodName: string): BackoffSettings;
/**
* Returns a promise with a void return type. The returned promise swallows all
* errors and never throws.
*
* This is primarily used to wait for a promise to complete when the result of
* the promise will be discarded.
*
* @private
* @internal
*/
export declare function silencePromise(promise: Promise<unknown>): Promise<void>;
/**
* Wraps the provided error in a new error that includes the provided stack.
*
* Used to preserve stack traces across async calls.
* @private
* @internal
*/
export declare function wrapError(err: Error, stack: string): Error;
/**
* Parses the value of the environment variable FIRESTORE_PREFER_REST, and
* returns a value indicating if the environment variable enables or disables
* preferRest.
*
* This function will warn to the console if the environment variable is set
* to an unsupported value.
*
* @return `true` if the environment variable enables `preferRest`,
* `false` if the environment variable disables `preferRest`, or `undefined`
* if the environment variable is not set or is set to an unsupported value.
*
* @internal
* @private
*/
export declare function tryGetPreferRestEnvironmentVariable(): boolean | undefined;
/**
* Returns an array of values that are calculated by performing the given `fn`
* on all keys in the given `obj` dictionary.
*
* @private
* @internal
*/
export declare function mapToArray<V, R>(obj: Dict<V>, fn: (element: V, key: string, obj: Dict<V>) => R): R[];
/**
* Verifies equality for an array of objects using the `isEqual` interface.
*
* @private
* @internal
* @param left Array of objects supporting `isEqual`.
* @param right Array of objects supporting `isEqual`.
* @return True if arrays are equal.
*/
export declare function isArrayEqual<T extends {
isEqual: (t: T) => boolean;
}>(left: T[], right: T[]): boolean;
/**
* Verifies equality for an array of primitives.
*
* @private
* @internal
* @param left Array of primitives.
* @param right Array of primitives.
* @return True if arrays are equal.
*/
export declare function isPrimitiveArrayEqual<T extends number | string>(left: T[], right: T[]): boolean;

View File

@@ -0,0 +1,305 @@
"use strict";
/*!
* Copyright 2018 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Deferred = void 0;
exports.autoId = autoId;
exports.requestTag = requestTag;
exports.isObject = isObject;
exports.isPlainObject = isPlainObject;
exports.isEmpty = isEmpty;
exports.isFunction = isFunction;
exports.isPermanentRpcError = isPermanentRpcError;
exports.getRetryCodes = getRetryCodes;
exports.getTotalTimeout = getTotalTimeout;
exports.getRetryParams = getRetryParams;
exports.silencePromise = silencePromise;
exports.wrapError = wrapError;
exports.tryGetPreferRestEnvironmentVariable = tryGetPreferRestEnvironmentVariable;
exports.mapToArray = mapToArray;
exports.isArrayEqual = isArrayEqual;
exports.isPrimitiveArrayEqual = isPrimitiveArrayEqual;
const crypto_1 = require("crypto");
const gapicConfig = require("./v1/firestore_client_config.json");
/**
* A Promise implementation that supports deferred resolution.
* @private
* @internal
*/
class Deferred {
constructor() {
this.resolve = () => { };
this.reject = () => { };
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
exports.Deferred = Deferred;
/**
* Generate a unique client-side identifier.
*
* Used for the creation of new documents.
*
* @private
* @internal
* @returns {string} A unique 20-character wide identifier.
*/
function autoId() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let autoId = '';
while (autoId.length < 20) {
const bytes = (0, crypto_1.randomBytes)(40);
bytes.forEach(b => {
// Length of `chars` is 62. We only take bytes between 0 and 62*4-1
// (both inclusive). The value is then evenly mapped to indices of `char`
// via a modulo operation.
const maxValue = 62 * 4 - 1;
if (autoId.length < 20 && b <= maxValue) {
autoId += chars.charAt(b % 62);
}
});
}
return autoId;
}
/**
* Generate a short and semi-random client-side identifier.
*
* Used for the creation of request tags.
*
* @private
* @internal
* @returns {string} A random 5-character wide identifier.
*/
function requestTag() {
return autoId().substr(0, 5);
}
/**
* Determines whether `value` is a JavaScript object.
*
* @private
* @internal
*/
function isObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}
/**
* Verifies that 'obj' is a plain JavaScript object that can be encoded as a
* 'Map' in Firestore.
*
* @private
* @internal
* @param input The argument to verify.
* @returns 'true' if the input can be a treated as a plain object.
*/
function isPlainObject(input) {
return (isObject(input) &&
(Object.getPrototypeOf(input) === Object.prototype ||
Object.getPrototypeOf(input) === null ||
input.constructor.name === 'Object'));
}
/**
* Returns whether `value` has no custom properties.
*
* @private
* @internal
*/
function isEmpty(value) {
return Object.keys(value).length === 0;
}
/**
* Determines whether `value` is a JavaScript function.
*
* @private
* @internal
*/
function isFunction(value) {
return typeof value === 'function';
}
/**
* Determines whether the provided error is considered permanent for the given
* RPC.
*
* @private
* @internal
*/
function isPermanentRpcError(err, methodName) {
if (err.code !== undefined) {
const retryCodes = getRetryCodes(methodName);
return retryCodes.indexOf(err.code) === -1;
}
else {
return false;
}
}
let serviceConfig;
/**
* Lazy-loads the service config when first accessed.
* @private
* @internal
**/
function getServiceConfig(methodName) {
if (!serviceConfig) {
serviceConfig = require('google-gax/build/src/fallback').constructSettings('google.firestore.v1.Firestore', gapicConfig, {}, require('google-gax/build/src/status').Status);
}
return serviceConfig[methodName];
}
/**
* Returns the list of retryable error codes specified in the service
* configuration.
* @private
* @internal
*/
function getRetryCodes(methodName) {
var _a, _b, _c;
return (_c = (_b = (_a = getServiceConfig(methodName)) === null || _a === void 0 ? void 0 : _a.retry) === null || _b === void 0 ? void 0 : _b.retryCodes) !== null && _c !== void 0 ? _c : [];
}
/**
* Gets the total timeout in milliseconds from the retry settings in
* the service config for the given RPC. If the total timeout is not
* set, then `0` is returned.
*
* @private
* @internal
*/
function getTotalTimeout(methodName) {
var _a, _b, _c, _d;
return ((_d = (_c = (_b = (_a = getServiceConfig(methodName)) === null || _a === void 0 ? void 0 : _a.retry) === null || _b === void 0 ? void 0 : _b.backoffSettings) === null || _c === void 0 ? void 0 : _c.totalTimeoutMillis) !== null && _d !== void 0 ? _d : 0);
}
/**
* Returns the backoff setting from the service configuration.
* @private
* @internal
*/
function getRetryParams(methodName) {
var _a, _b, _c;
return ((_c = (_b = (_a = getServiceConfig(methodName)) === null || _a === void 0 ? void 0 : _a.retry) === null || _b === void 0 ? void 0 : _b.backoffSettings) !== null && _c !== void 0 ? _c : require('google-gax/build/src/fallback').createDefaultBackoffSettings());
}
/**
* Returns a promise with a void return type. The returned promise swallows all
* errors and never throws.
*
* This is primarily used to wait for a promise to complete when the result of
* the promise will be discarded.
*
* @private
* @internal
*/
function silencePromise(promise) {
return promise.then(() => { }, () => { });
}
/**
* Wraps the provided error in a new error that includes the provided stack.
*
* Used to preserve stack traces across async calls.
* @private
* @internal
*/
function wrapError(err, stack) {
err.stack += '\nCaused by: ' + stack;
return err;
}
/**
* Parses the value of the environment variable FIRESTORE_PREFER_REST, and
* returns a value indicating if the environment variable enables or disables
* preferRest.
*
* This function will warn to the console if the environment variable is set
* to an unsupported value.
*
* @return `true` if the environment variable enables `preferRest`,
* `false` if the environment variable disables `preferRest`, or `undefined`
* if the environment variable is not set or is set to an unsupported value.
*
* @internal
* @private
*/
function tryGetPreferRestEnvironmentVariable() {
var _a;
const rawValue = (_a = process.env.FIRESTORE_PREFER_REST) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase();
if (rawValue === undefined) {
return undefined;
}
else if (rawValue === '1' || rawValue === 'true') {
return true;
}
else if (rawValue === '0' || rawValue === 'false') {
return false;
}
else {
// eslint-disable-next-line no-console
console.warn(`An unsupported value was specified for the environment variable FIRESTORE_PREFER_REST. Value ${rawValue} is unsupported.`);
return undefined;
}
}
/**
* Returns an array of values that are calculated by performing the given `fn`
* on all keys in the given `obj` dictionary.
*
* @private
* @internal
*/
function mapToArray(obj, fn) {
const result = [];
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
result.push(fn(obj[key], key, obj));
}
}
return result;
}
/**
* Verifies equality for an array of objects using the `isEqual` interface.
*
* @private
* @internal
* @param left Array of objects supporting `isEqual`.
* @param right Array of objects supporting `isEqual`.
* @return True if arrays are equal.
*/
function isArrayEqual(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;
}
/**
* Verifies equality for an array of primitives.
*
* @private
* @internal
* @param left Array of primitives.
* @param right Array of primitives.
* @return True if arrays are equal.
*/
function isPrimitiveArrayEqual(left, right) {
if (left.length !== right.length) {
return false;
}
for (let i = 0; i < left.length; ++i) {
if (left[i] !== right[i]) {
return false;
}
}
return true;
}
//# sourceMappingURL=util.js.map

Some files were not shown because too many files have changed in this diff Show More