diff --git a/.changeset/big-pipeline-release.md b/.changeset/big-pipeline-release.md new file mode 100644 index 0000000000..f65efc162e --- /dev/null +++ b/.changeset/big-pipeline-release.md @@ -0,0 +1,5 @@ +--- +'@firebase/firestore': minor +--- + +Release Firestore Pipelines for Enterprise edition in public preview. diff --git a/common/api-review/firestore-lite-pipelines.api.md b/common/api-review/firestore-lite-pipelines.api.md new file mode 100644 index 0000000000..b19de978f9 --- /dev/null +++ b/common/api-review/firestore-lite-pipelines.api.md @@ -0,0 +1,1316 @@ +## API Report File for "@firebase/firestore-lite-pipelines" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app'; + +// @beta +export function abs(expr: Expression): FunctionExpression; + +// @beta +export function abs(fieldName: string): FunctionExpression; + +// @beta +export function add(first: Expression, second: Expression | unknown): FunctionExpression; + +// @beta +export function add(fieldName: string, second: Expression | unknown): FunctionExpression; + +// @beta +export type AddFieldsStageOptions = StageOptions & { + fields: Selectable[]; +}; + +// @beta +export class AggregateFunction { + constructor(name: string, params: Expression[]); + as(name: string): AliasedAggregate; + // (undocumented) + exprType: ExpressionType; + } + +// @beta +export type AggregateStageOptions = StageOptions & { + accumulators: AliasedAggregate[]; + groups?: Array; +}; + +// @beta +export class AliasedAggregate { + constructor(aggregate: AggregateFunction, alias: string, _methodName: string | undefined); + // (undocumented) + readonly aggregate: AggregateFunction; + // (undocumented) + readonly alias: string; +} + +// @beta (undocumented) +export class AliasedExpression implements Selectable { + constructor(expr: Expression, alias: string, _methodName: string | undefined); + // (undocumented) + readonly alias: string; + // (undocumented) + readonly expr: Expression; + // (undocumented) + exprType: ExpressionType; + // (undocumented) + selectable: true; +} + +// @beta +export function and(first: BooleanExpression, second: BooleanExpression, ...more: BooleanExpression[]): BooleanExpression; + +// @beta +export function array(elements: unknown[]): FunctionExpression; + +// @beta +export function arrayConcat(firstArray: Expression, secondArray: Expression | unknown[], ...otherArrays: Array): FunctionExpression; + +// @beta +export function arrayConcat(firstArrayField: string, secondArray: Expression | unknown[], ...otherArrays: Array): FunctionExpression; + +// @beta +export function arrayContains(array: Expression, element: Expression): BooleanExpression; + +// @beta +export function arrayContains(array: Expression, element: unknown): BooleanExpression; + +// @beta +export function arrayContains(fieldName: string, element: Expression): BooleanExpression; + +// @beta +export function arrayContains(fieldName: string, element: unknown): BooleanExpression; + +// @beta +export function arrayContainsAll(array: Expression, values: Array): BooleanExpression; + +// @beta +export function arrayContainsAll(fieldName: string, values: Array): BooleanExpression; + +// @beta +export function arrayContainsAll(array: Expression, arrayExpression: Expression): BooleanExpression; + +// @beta +export function arrayContainsAll(fieldName: string, arrayExpression: Expression): BooleanExpression; + +// @beta +export function arrayContainsAny(array: Expression, values: Array): BooleanExpression; + +// @beta +export function arrayContainsAny(fieldName: string, values: Array): BooleanExpression; + +// @beta +export function arrayContainsAny(array: Expression, values: Expression): BooleanExpression; + +// @beta +export function arrayContainsAny(fieldName: string, values: Expression): BooleanExpression; + +// @beta +export function arrayGet(arrayField: string, offset: number): FunctionExpression; + +// @beta +export function arrayGet(arrayField: string, offsetExpr: Expression): FunctionExpression; + +// @beta +export function arrayGet(arrayExpression: Expression, offset: number): FunctionExpression; + +// @beta +export function arrayGet(arrayExpression: Expression, offsetExpr: Expression): FunctionExpression; + +// @beta +export function arrayLength(fieldName: string): FunctionExpression; + +// @beta +export function arrayLength(array: Expression): FunctionExpression; + +// @beta +export function arraySum(fieldName: string): FunctionExpression; + +// @beta +export function arraySum(expression: Expression): FunctionExpression; + +// @beta +export function ascending(expr: Expression): Ordering; + +// @beta +export function ascending(fieldName: string): Ordering; + +// @beta +export function average(expression: Expression): AggregateFunction; + +// @beta +export function average(fieldName: string): AggregateFunction; + +// @beta +export abstract class BooleanExpression extends Expression { + conditional(thenExpr: Expression, elseExpr: Expression): FunctionExpression; + countIf(): AggregateFunction; + ifError(catchValue: BooleanExpression): BooleanExpression; + ifError(catchValue: boolean): BooleanExpression; + ifError(catchValue: Expression): FunctionExpression; + ifError(catchValue: unknown): FunctionExpression; + not(): BooleanExpression; +} + +// @beta +export function byteLength(expr: Expression): FunctionExpression; + +// @beta +export function byteLength(fieldName: string): FunctionExpression; + +// @beta +export function ceil(fieldName: string): FunctionExpression; + +// @beta +export function ceil(expression: Expression): FunctionExpression; + +// @beta +export function charLength(fieldName: string): FunctionExpression; + +// @beta +export function charLength(stringExpression: Expression): FunctionExpression; + +// @beta +export type CollectionGroupStageOptions = StageOptions & { + collectionId: string; + forceIndex?: string; +}; + +// @beta +export function collectionId(fieldName: string): FunctionExpression; + +// @beta +export function collectionId(expression: Expression): FunctionExpression; + +// @beta +export type CollectionStageOptions = StageOptions & { + collection: string | Query; + forceIndex?: string; +}; + +// @beta +export function concat(first: Expression, second: Expression | unknown, ...others: Array): FunctionExpression; + +// @beta +export function concat(fieldName: string, second: Expression | unknown, ...others: Array): FunctionExpression; + +// @beta +export function conditional(condition: BooleanExpression, thenExpr: Expression, elseExpr: Expression): FunctionExpression; + +// @beta +export function constant(value: number): Expression; + +// @beta +export function constant(value: string): Expression; + +// @beta +export function constant(value: boolean): BooleanExpression; + +// @beta +export function constant(value: null): Expression; + +// Warning: (ae-forgotten-export) The symbol "GeoPoint" needs to be exported by the entry point pipelines.d.ts +// +// @beta +export function constant(value: GeoPoint): Expression; + +// Warning: (ae-forgotten-export) The symbol "Timestamp" needs to be exported by the entry point pipelines.d.ts +// +// @beta +export function constant(value: Timestamp): Expression; + +// @beta +export function constant(value: Date): Expression; + +// Warning: (ae-forgotten-export) The symbol "Bytes" needs to be exported by the entry point pipelines.d.ts +// +// @beta +export function constant(value: Bytes): Expression; + +// Warning: (ae-forgotten-export) The symbol "DocumentReference" needs to be exported by the entry point pipelines.d.ts +// +// @beta +export function constant(value: DocumentReference): Expression; + +// Warning: (ae-forgotten-export) The symbol "VectorValue" needs to be exported by the entry point pipelines.d.ts +// +// @beta +export function constant(value: VectorValue): Expression; + +// @beta +export function cosineDistance(fieldName: string, vector: number[] | VectorValue): FunctionExpression; + +// @beta +export function cosineDistance(fieldName: string, vectorExpression: Expression): FunctionExpression; + +// @beta +export function cosineDistance(vectorExpression: Expression, vector: number[] | VectorValue): FunctionExpression; + +// @beta +export function cosineDistance(vectorExpression: Expression, otherVectorExpression: Expression): FunctionExpression; + +// @beta +export function count(expression: Expression): AggregateFunction; + +// @beta +export function count(fieldName: string): AggregateFunction; + +// @beta +export function countAll(): AggregateFunction; + +// @beta +export function countDistinct(expr: Expression | string): AggregateFunction; + +// @beta +export function countIf(booleanExpr: BooleanExpression): AggregateFunction; + +// @beta +export function currentTimestamp(): FunctionExpression; + +// @beta +export type DatabaseStageOptions = StageOptions & {}; + +// @beta +export function descending(expr: Expression): Ordering; + +// @beta +export function descending(fieldName: string): Ordering; + +// @beta +export type DistinctStageOptions = StageOptions & { + groups: Array; +}; + +// @beta +export function divide(left: Expression, right: Expression): FunctionExpression; + +// @beta +export function divide(expression: Expression, value: unknown): FunctionExpression; + +// @beta +export function divide(fieldName: string, expressions: Expression): FunctionExpression; + +// @beta +export function divide(fieldName: string, value: unknown): FunctionExpression; + +// @beta +export function documentId(documentPath: string | DocumentReference): FunctionExpression; + +// @beta +export function documentId(documentPathExpr: Expression): FunctionExpression; + +// @beta +export type DocumentsStageOptions = StageOptions & { + docs: Array; +}; + +// @beta +export function dotProduct(fieldName: string, vector: number[] | VectorValue): FunctionExpression; + +// @beta +export function dotProduct(fieldName: string, vectorExpression: Expression): FunctionExpression; + +// @beta +export function dotProduct(vectorExpression: Expression, vector: number[] | VectorValue): FunctionExpression; + +// @beta +export function dotProduct(vectorExpression: Expression, otherVectorExpression: Expression): FunctionExpression; + +// @beta +export function endsWith(fieldName: string, suffix: string): BooleanExpression; + +// @beta +export function endsWith(fieldName: string, suffix: Expression): BooleanExpression; + +// @beta +export function endsWith(stringExpression: Expression, suffix: string): BooleanExpression; + +// @beta +export function endsWith(stringExpression: Expression, suffix: Expression): BooleanExpression; + +// @beta +export function equal(left: Expression, right: Expression): BooleanExpression; + +// @beta +export function equal(expression: Expression, value: unknown): BooleanExpression; + +// @beta +export function equal(fieldName: string, expression: Expression): BooleanExpression; + +// @beta +export function equal(fieldName: string, value: unknown): BooleanExpression; + +// @beta +export function equalAny(expression: Expression, values: Array): BooleanExpression; + +// @beta +export function equalAny(expression: Expression, arrayExpression: Expression): BooleanExpression; + +// @beta +export function equalAny(fieldName: string, values: Array): BooleanExpression; + +// @beta +export function equalAny(fieldName: string, arrayExpression: Expression): BooleanExpression; + +// @beta +export function euclideanDistance(fieldName: string, vector: number[] | VectorValue): FunctionExpression; + +// @beta +export function euclideanDistance(fieldName: string, vectorExpression: Expression): FunctionExpression; + +// @beta +export function euclideanDistance(vectorExpression: Expression, vector: number[] | VectorValue): FunctionExpression; + +// @beta +export function euclideanDistance(vectorExpression: Expression, otherVectorExpression: Expression): FunctionExpression; + +// @beta +export function execute(pipeline: Pipeline): Promise; + +// @beta +export function exists(value: Expression): BooleanExpression; + +// @beta +export function exists(fieldName: string): BooleanExpression; + +// @beta +export function exp(expression: Expression): FunctionExpression; + +// @beta +export function exp(fieldName: string): FunctionExpression; + +// @beta +export abstract class Expression { + abs(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + add(second: Expression | unknown): FunctionExpression; + /* Excluded from this release type: _readUserData */ + arrayConcat(secondArray: Expression | unknown[], ...otherArrays: Array): FunctionExpression; + /* Excluded from this release type: _readUserData */ + arrayContains(expression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + arrayContains(value: unknown): BooleanExpression; + /* Excluded from this release type: _readUserData */ + arrayContainsAll(values: Array): BooleanExpression; + /* Excluded from this release type: _readUserData */ + arrayContainsAll(arrayExpression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + arrayContainsAny(values: Array): BooleanExpression; + /* Excluded from this release type: _readUserData */ + arrayContainsAny(arrayExpression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + arrayGet(offset: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + arrayGet(offsetExpr: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + arrayLength(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + arrayReverse(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + arraySum(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + as(name: string): AliasedExpression; + /* Excluded from this release type: _readUserData */ + asBoolean(): BooleanExpression; + /* Excluded from this release type: _readUserData */ + ascending(): Ordering; + /* Excluded from this release type: _readUserData */ + average(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + byteLength(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + ceil(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + charLength(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + collectionId(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + concat(second: Expression | unknown, ...others: Array): FunctionExpression; + /* Excluded from this release type: _readUserData */ + cosineDistance(vectorExpression: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + cosineDistance(vector: VectorValue | number[]): FunctionExpression; + /* Excluded from this release type: _readUserData */ + count(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + countDistinct(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + descending(): Ordering; + /* Excluded from this release type: _readUserData */ + divide(divisor: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + divide(divisor: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + documentId(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + dotProduct(vectorExpression: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + dotProduct(vector: VectorValue | number[]): FunctionExpression; + /* Excluded from this release type: _readUserData */ + endsWith(suffix: string): BooleanExpression; + /* Excluded from this release type: _readUserData */ + endsWith(suffix: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + equal(expression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + equal(value: unknown): BooleanExpression; + /* Excluded from this release type: _readUserData */ + equalAny(values: Array): BooleanExpression; + /* Excluded from this release type: _readUserData */ + equalAny(arrayExpression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + euclideanDistance(vectorExpression: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + euclideanDistance(vector: VectorValue | number[]): FunctionExpression; + /* Excluded from this release type: _readUserData */ + exists(): BooleanExpression; + /* Excluded from this release type: _readUserData */ + exp(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + // (undocumented) + abstract readonly expressionType: ExpressionType; + /* Excluded from this release type: _readUserData */ + floor(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + greaterThan(expression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + greaterThan(value: unknown): BooleanExpression; + /* Excluded from this release type: _readUserData */ + greaterThanOrEqual(expression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + greaterThanOrEqual(value: unknown): BooleanExpression; + /* Excluded from this release type: _readUserData */ + ifAbsent(elseValue: unknown): Expression; + /* Excluded from this release type: _readUserData */ + ifAbsent(elseExpression: unknown): Expression; + /* Excluded from this release type: _readUserData */ + ifError(catchExpr: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + ifError(catchValue: unknown): FunctionExpression; + /* Excluded from this release type: _readUserData */ + isAbsent(): BooleanExpression; + /* Excluded from this release type: _readUserData */ + isError(): BooleanExpression; + /* Excluded from this release type: _readUserData */ + join(delimiterExpression: Expression): Expression; + /* Excluded from this release type: _readUserData */ + join(delimiter: string): Expression; + /* Excluded from this release type: _readUserData */ + length(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + lessThan(experession: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + lessThan(value: unknown): BooleanExpression; + /* Excluded from this release type: _readUserData */ + lessThanOrEqual(expression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + lessThanOrEqual(value: unknown): BooleanExpression; + /* Excluded from this release type: _readUserData */ + like(pattern: string): BooleanExpression; + /* Excluded from this release type: _readUserData */ + like(pattern: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + ln(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + log10(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + logicalMaximum(second: Expression | unknown, ...others: Array): FunctionExpression; + /* Excluded from this release type: _readUserData */ + logicalMinimum(second: Expression | unknown, ...others: Array): FunctionExpression; + /* Excluded from this release type: _readUserData */ + mapGet(subfield: string): FunctionExpression; + /* Excluded from this release type: _readUserData */ + mapMerge(secondMap: Record | Expression, ...otherMaps: Array | Expression>): FunctionExpression; + /* Excluded from this release type: _readUserData */ + mapRemove(key: string): FunctionExpression; + /* Excluded from this release type: _readUserData */ + mapRemove(keyExpr: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + maximum(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + minimum(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + mod(expression: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + mod(value: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + multiply(second: Expression | number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + notEqual(expression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + notEqual(value: unknown): BooleanExpression; + /* Excluded from this release type: _readUserData */ + notEqualAny(values: Array): BooleanExpression; + /* Excluded from this release type: _readUserData */ + notEqualAny(arrayExpression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + pow(exponent: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + pow(exponent: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + regexContains(pattern: string): BooleanExpression; + /* Excluded from this release type: _readUserData */ + regexContains(pattern: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + regexMatch(pattern: string): BooleanExpression; + /* Excluded from this release type: _readUserData */ + regexMatch(pattern: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + reverse(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + round(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + round(decimalPlaces: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + round(decimalPlaces: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + split(delimiter: string): FunctionExpression; + /* Excluded from this release type: _readUserData */ + split(delimiter: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + sqrt(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + startsWith(prefix: string): BooleanExpression; + /* Excluded from this release type: _readUserData */ + startsWith(prefix: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + stringConcat(secondString: Expression | string, ...otherStrings: Array): FunctionExpression; + /* Excluded from this release type: _readUserData */ + stringContains(substring: string): BooleanExpression; + /* Excluded from this release type: _readUserData */ + stringContains(expr: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + stringReverse(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + substring(position: number, length?: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + substring(position: Expression, length?: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + subtract(subtrahend: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + subtract(subtrahend: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + sum(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + timestampAdd(unit: Expression, amount: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampSubtract(unit: Expression, amount: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampSubtract(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampToUnixMicros(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampToUnixMillis(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampToUnixSeconds(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampTruncate(granularity: TimeGranularity, timezone?: string | Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampTruncate(granularity: Expression, timezone?: string | Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + toLower(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + toUpper(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + trim(valueToTrim?: string | Expression | Bytes): FunctionExpression; + /* Excluded from this release type: _readUserData */ + type(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + unixMicrosToTimestamp(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + unixMillisToTimestamp(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + unixSecondsToTimestamp(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + vectorLength(): FunctionExpression; +} + +// @beta +export type ExpressionType = 'Field' | 'Constant' | 'Function' | 'AggregateFunction' | 'ListOfExpressions' | 'AliasedExpression'; + +// @beta +export class Field extends Expression implements Selectable { + // (undocumented) + get alias(): string; + // (undocumented) + get expr(): Expression; + // (undocumented) + readonly expressionType: ExpressionType; + // (undocumented) + get fieldName(): string; + // (undocumented) + selectable: true; +} + +// @beta +export function field(name: string): Field; + +// Warning: (ae-forgotten-export) The symbol "FieldPath" needs to be exported by the entry point pipelines.d.ts +// +// @beta +export function field(path: FieldPath): Field; + +// @beta +export type FindNearestStageOptions = StageOptions & { + field: Field | string; + vectorValue: VectorValue | number[]; + distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; + limit?: number; + distanceField?: string; +}; + +// @beta +export function floor(expr: Expression): FunctionExpression; + +// @beta +export function floor(fieldName: string): FunctionExpression; + +// @beta +export class FunctionExpression extends Expression { + constructor(name: string, params: Expression[]); + constructor(name: string, params: Expression[], _methodName: string | undefined); + // (undocumented) + readonly expressionType: ExpressionType; + } + +// @beta +export function greaterThan(left: Expression, right: Expression): BooleanExpression; + +// @beta +export function greaterThan(expression: Expression, value: unknown): BooleanExpression; + +// @beta +export function greaterThan(fieldName: string, expression: Expression): BooleanExpression; + +// @beta +export function greaterThan(fieldName: string, value: unknown): BooleanExpression; + +// @beta +export function greaterThanOrEqual(left: Expression, right: Expression): BooleanExpression; + +// @beta +export function greaterThanOrEqual(expression: Expression, value: unknown): BooleanExpression; + +// @beta +export function greaterThanOrEqual(fieldName: string, value: Expression): BooleanExpression; + +// @beta +export function greaterThanOrEqual(fieldName: string, value: unknown): BooleanExpression; + +// @beta +export function ifAbsent(ifExpr: Expression, elseExpr: Expression): Expression; + +// @beta +export function ifAbsent(ifExpr: Expression, elseValue: unknown): Expression; + +// @beta +export function ifAbsent(ifFieldName: string, elseExpr: Expression): Expression; + +// @beta +export function ifAbsent(ifFieldName: string | Expression, elseValue: Expression | unknown): Expression; + +// @beta +export function ifError(tryExpr: BooleanExpression, catchExpr: BooleanExpression): BooleanExpression; + +// @beta +export function ifError(tryExpr: Expression, catchExpr: Expression): FunctionExpression; + +// @beta +export function ifError(tryExpr: Expression, catchValue: unknown): FunctionExpression; + +// @beta +export function isAbsent(value: Expression): BooleanExpression; + +// @beta +export function isAbsent(field: string): BooleanExpression; + +// @beta +export function isError(value: Expression): BooleanExpression; + +// @beta +export function join(arrayFieldName: string, delimiter: string): Expression; + +// @beta +export function join(arrayExpression: Expression, delimiterExpression: Expression): Expression; + +// @beta +export function join(arrayExpression: Expression, delimiter: string): Expression; + +// @beta +export function join(arrayFieldName: string, delimiterExpression: Expression): Expression; + +// @beta +function length_2(fieldName: string): FunctionExpression; + +// @beta +function length_2(expression: Expression): FunctionExpression; + +export { length_2 as length } + +// @beta +export function lessThan(left: Expression, right: Expression): BooleanExpression; + +// @beta +export function lessThan(expression: Expression, value: unknown): BooleanExpression; + +// @beta +export function lessThan(fieldName: string, expression: Expression): BooleanExpression; + +// @beta +export function lessThan(fieldName: string, value: unknown): BooleanExpression; + +// @beta +export function lessThanOrEqual(left: Expression, right: Expression): BooleanExpression; + +// @beta +export function lessThanOrEqual(expression: Expression, value: unknown): BooleanExpression; + +// @beta +export function lessThanOrEqual(fieldName: string, expression: Expression): BooleanExpression; + +// @beta +export function lessThanOrEqual(fieldName: string, value: unknown): BooleanExpression; + +// @beta +export function like(fieldName: string, pattern: string): BooleanExpression; + +// @beta +export function like(fieldName: string, pattern: Expression): BooleanExpression; + +// @beta +export function like(stringExpression: Expression, pattern: string): BooleanExpression; + +// @beta +export function like(stringExpression: Expression, pattern: Expression): BooleanExpression; + +// @beta +export type LimitStageOptions = StageOptions & { + limit: number; +}; + +// @beta +export function ln(fieldName: string): FunctionExpression; + +// @beta +export function ln(expression: Expression): FunctionExpression; + +// @beta +export function log(expression: Expression, base: number): FunctionExpression; + +// @beta +export function log(expression: Expression, base: Expression): FunctionExpression; + +// @beta +export function log(fieldName: string, base: number): FunctionExpression; + +// @beta +export function log(fieldName: string, base: Expression): FunctionExpression; + +// @beta +export function log10(fieldName: string): FunctionExpression; + +// @beta +export function log10(expression: Expression): FunctionExpression; + +// @beta +export function logicalMaximum(first: Expression, second: Expression | unknown, ...others: Array): FunctionExpression; + +// @beta +export function logicalMaximum(fieldName: string, second: Expression | unknown, ...others: Array): FunctionExpression; + +// @beta +export function logicalMinimum(first: Expression, second: Expression | unknown, ...others: Array): FunctionExpression; + +// @beta +export function logicalMinimum(fieldName: string, second: Expression | unknown, ...others: Array): FunctionExpression; + +// @beta +export function map(elements: Record): FunctionExpression; + +// @beta +export function mapGet(fieldName: string, subField: string): FunctionExpression; + +// @beta +export function mapGet(mapExpression: Expression, subField: string): FunctionExpression; + +// @beta +export function mapMerge(mapField: string, secondMap: Record | Expression, ...otherMaps: Array | Expression>): FunctionExpression; + +// @beta +export function mapMerge(firstMap: Record | Expression, secondMap: Record | Expression, ...otherMaps: Array | Expression>): FunctionExpression; + +// @beta +export function mapRemove(mapField: string, key: string): FunctionExpression; + +// @beta +export function mapRemove(mapExpr: Expression, key: string): FunctionExpression; + +// @beta +export function mapRemove(mapField: string, keyExpr: Expression): FunctionExpression; + +// @beta +export function mapRemove(mapExpr: Expression, keyExpr: Expression): FunctionExpression; + +// @beta +export function maximum(expression: Expression): AggregateFunction; + +// @beta +export function maximum(fieldName: string): AggregateFunction; + +// @beta +export function minimum(expression: Expression): AggregateFunction; + +// @beta +export function minimum(fieldName: string): AggregateFunction; + +// @beta +export function mod(left: Expression, right: Expression): FunctionExpression; + +// @beta +export function mod(expression: Expression, value: unknown): FunctionExpression; + +// @beta +export function mod(fieldName: string, expression: Expression): FunctionExpression; + +// @beta +export function mod(fieldName: string, value: unknown): FunctionExpression; + +// @beta +export function multiply(first: Expression, second: Expression | unknown): FunctionExpression; + +// @beta +export function multiply(fieldName: string, second: Expression | unknown): FunctionExpression; + +// @beta +export function not(booleanExpr: BooleanExpression): BooleanExpression; + +// @beta +export function notEqual(left: Expression, right: Expression): BooleanExpression; + +// @beta +export function notEqual(expression: Expression, value: unknown): BooleanExpression; + +// @beta +export function notEqual(fieldName: string, expression: Expression): BooleanExpression; + +// @beta +export function notEqual(fieldName: string, value: unknown): BooleanExpression; + +// @beta +export function notEqualAny(element: Expression, values: Array): BooleanExpression; + +// @beta +export function notEqualAny(fieldName: string, values: Array): BooleanExpression; + +// @beta +export function notEqualAny(element: Expression, arrayExpression: Expression): BooleanExpression; + +// @beta +export function notEqualAny(fieldName: string, arrayExpression: Expression): BooleanExpression; + +// @beta +export type OffsetStageOptions = StageOptions & { + offset: number; +}; + +// @beta +export type OneOf = { + [K in keyof T]: Pick & { + [P in Exclude]?: undefined; + }; +}[keyof T]; + +// @beta +export function or(first: BooleanExpression, second: BooleanExpression, ...more: BooleanExpression[]): BooleanExpression; + +// @beta +export class Ordering { + constructor(expr: Expression, direction: 'ascending' | 'descending', _methodName: string | undefined); + // (undocumented) + readonly direction: 'ascending' | 'descending'; + // (undocumented) + readonly expr: Expression; +} + +// @beta +export class Pipeline { + /* Excluded from this release type: _db */ + /* Excluded from this release type: userDataReader */ + /* Excluded from this release type: _userDataWriter */ + /* Excluded from this release type: stages */ + /* Excluded from this release type: __constructor */ + addFields(field: Selectable, ...additionalFields: Selectable[]): Pipeline; + addFields(options: AddFieldsStageOptions): Pipeline; + aggregate(accumulator: AliasedAggregate, ...additionalAccumulators: AliasedAggregate[]): Pipeline; + aggregate(options: AggregateStageOptions): Pipeline; + distinct(group: string | Selectable, ...additionalGroups: Array): Pipeline; + distinct(options: DistinctStageOptions): Pipeline; + findNearest(options: FindNearestStageOptions): Pipeline; + limit(limit: number): Pipeline; + limit(options: LimitStageOptions): Pipeline; + offset(offset: number): Pipeline; + offset(options: OffsetStageOptions): Pipeline; + rawStage(name: string, params: unknown[], options?: { + [key: string]: Expression | unknown; + }): Pipeline; + removeFields(fieldValue: Field | string, ...additionalFields: Array): Pipeline; + removeFields(options: RemoveFieldsStageOptions): Pipeline; + replaceWith(fieldName: string): Pipeline; + replaceWith(expr: Expression): Pipeline; + replaceWith(options: ReplaceWithStageOptions): Pipeline; + sample(documents: number): Pipeline; + sample(options: SampleStageOptions): Pipeline; + select(selection: Selectable | string, ...additionalSelections: Array): Pipeline; + select(options: SelectStageOptions): Pipeline; + sort(ordering: Ordering, ...additionalOrderings: Ordering[]): Pipeline; + sort(options: SortStageOptions): Pipeline; + union(other: Pipeline): Pipeline; + union(options: UnionStageOptions): Pipeline; + unnest(selectable: Selectable, indexField?: string): Pipeline; + unnest(options: UnnestStageOptions): Pipeline; + where(condition: BooleanExpression): Pipeline; + where(options: WhereStageOptions): Pipeline; +} + +// Warning: (ae-forgotten-export) The symbol "DocumentData" needs to be exported by the entry point pipelines.d.ts +// +// @beta +export class PipelineResult { + /* Excluded from this release type: _ref */ + /* Excluded from this release type: _fields */ + /* Excluded from this release type: __constructor */ + get createTime(): Timestamp | undefined; + data(): AppModelType; + get(fieldPath: string | FieldPath | Field): any; + get id(): string | undefined; + get ref(): DocumentReference | undefined; + get updateTime(): Timestamp | undefined; +} + +// @beta +export class PipelineSnapshot { + constructor(pipeline: Pipeline, results: PipelineResult[], executionTime?: Timestamp); + get executionTime(): Timestamp; + get results(): PipelineResult[]; +} + +// @beta +export class PipelineSource { + collection(collection: string | Query): PipelineType; + collection(options: CollectionStageOptions): PipelineType; + collectionGroup(collectionId: string): PipelineType; + collectionGroup(options: CollectionGroupStageOptions): PipelineType; + createFrom(query: Query): Pipeline; + database(): PipelineType; + database(options: DatabaseStageOptions): PipelineType; + documents(docs: Array): PipelineType; + documents(options: DocumentsStageOptions): PipelineType; + } + +// @beta +export function pow(base: Expression, exponent: Expression): FunctionExpression; + +// @beta +export function pow(base: Expression, exponent: number): FunctionExpression; + +// @beta +export function pow(base: string, exponent: Expression): FunctionExpression; + +// @beta +export function pow(base: string, exponent: number): FunctionExpression; + +// @beta +export function regexContains(fieldName: string, pattern: string): BooleanExpression; + +// @beta +export function regexContains(fieldName: string, pattern: Expression): BooleanExpression; + +// @beta +export function regexContains(stringExpression: Expression, pattern: string): BooleanExpression; + +// @beta +export function regexContains(stringExpression: Expression, pattern: Expression): BooleanExpression; + +// @beta +export function regexMatch(fieldName: string, pattern: string): BooleanExpression; + +// @beta +export function regexMatch(fieldName: string, pattern: Expression): BooleanExpression; + +// @beta +export function regexMatch(stringExpression: Expression, pattern: string): BooleanExpression; + +// @beta +export function regexMatch(stringExpression: Expression, pattern: Expression): BooleanExpression; + +// @beta +export type RemoveFieldsStageOptions = StageOptions & { + fields: Array; +}; + +// @beta +export type ReplaceWithStageOptions = StageOptions & { + map: Expression | string; +}; + +// @beta +export function reverse(stringExpression: Expression): FunctionExpression; + +// @beta +export function reverse(field: string): FunctionExpression; + +// @beta +export function round(fieldName: string): FunctionExpression; + +// @beta +export function round(expression: Expression): FunctionExpression; + +// @beta +export function round(fieldName: string, decimalPlaces: number | Expression): FunctionExpression; + +// @beta +export function round(expression: Expression, decimalPlaces: number | Expression): FunctionExpression; + +// @beta +export type SampleStageOptions = StageOptions & OneOf<{ + percentage: number; + documents: number; +}>; + +// @beta +export interface Selectable { + // (undocumented) + selectable: true; +} + +// @beta +export type SelectStageOptions = StageOptions & { + selections: Array; +}; + +// @beta +export type SortStageOptions = StageOptions & { + orderings: Ordering[]; +}; + +// @beta +export function split(fieldName: string, delimiter: string): FunctionExpression; + +// @beta +export function split(fieldName: string, delimiter: Expression): FunctionExpression; + +// @beta +export function split(expression: Expression, delimiter: string): FunctionExpression; + +// @beta +export function split(expression: Expression, delimiter: Expression): FunctionExpression; + +// @beta +export function sqrt(expression: Expression): FunctionExpression; + +// @beta +export function sqrt(fieldName: string): FunctionExpression; + +// @beta +export interface StageOptions { + rawOptions?: { + [name: string]: unknown; + }; +} + +// @beta +export function startsWith(fieldName: string, prefix: string): BooleanExpression; + +// @beta +export function startsWith(fieldName: string, prefix: Expression): BooleanExpression; + +// @beta +export function startsWith(stringExpression: Expression, prefix: string): BooleanExpression; + +// @beta +export function startsWith(stringExpression: Expression, prefix: Expression): BooleanExpression; + +// @beta +export function stringConcat(fieldName: string, secondString: Expression | string, ...otherStrings: Array): FunctionExpression; + +// @beta +export function stringConcat(firstString: Expression, secondString: Expression | string, ...otherStrings: Array): FunctionExpression; + +// @beta +export function stringContains(fieldName: string, substring: string): BooleanExpression; + +// @beta +export function stringContains(fieldName: string, substring: Expression): BooleanExpression; + +// @beta +export function stringContains(stringExpression: Expression, substring: string): BooleanExpression; + +// @beta +export function stringContains(stringExpression: Expression, substring: Expression): BooleanExpression; + +// @beta +export function stringReverse(stringExpression: Expression): FunctionExpression; + +// @beta +export function stringReverse(field: string): FunctionExpression; + +// @beta +export function substring(field: string, position: number, length?: number): FunctionExpression; + +// @beta +export function substring(input: Expression, position: number, length?: number): FunctionExpression; + +// @beta +export function substring(field: string, position: Expression, length?: Expression): FunctionExpression; + +// @beta +export function substring(input: Expression, position: Expression, length?: Expression): FunctionExpression; + +// @beta +export function subtract(left: Expression, right: Expression): FunctionExpression; + +// @beta +export function subtract(expression: Expression, value: unknown): FunctionExpression; + +// @beta +export function subtract(fieldName: string, expression: Expression): FunctionExpression; + +// @beta +export function subtract(fieldName: string, value: unknown): FunctionExpression; + +// @beta +export function sum(expression: Expression): AggregateFunction; + +// @beta +export function sum(fieldName: string): AggregateFunction; + +// @beta +export type TimeGranularity = 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'week(monday)' | 'week(tuesday)' | 'week(wednesday)' | 'week(thursday)' | 'week(friday)' | 'week(saturday)' | 'week(sunday)' | 'isoWeek' | 'month' | 'quarter' | 'year' | 'isoYear'; + +// @beta +export function timestampAdd(timestamp: Expression, unit: Expression, amount: Expression): FunctionExpression; + +// @beta +export function timestampAdd(timestamp: Expression, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + +// @beta +export function timestampAdd(fieldName: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + +// @beta +export function timestampSubtract(timestamp: Expression, unit: Expression, amount: Expression): FunctionExpression; + +// @beta +export function timestampSubtract(timestamp: Expression, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + +// @beta +export function timestampSubtract(fieldName: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + +// @beta +export function timestampToUnixMicros(expr: Expression): FunctionExpression; + +// @beta +export function timestampToUnixMicros(fieldName: string): FunctionExpression; + +// @beta +export function timestampToUnixMillis(expr: Expression): FunctionExpression; + +// @beta +export function timestampToUnixMillis(fieldName: string): FunctionExpression; + +// @beta +export function timestampToUnixSeconds(expr: Expression): FunctionExpression; + +// @beta +export function timestampToUnixSeconds(fieldName: string): FunctionExpression; + +// @beta +export function timestampTruncate(fieldName: string, granularity: TimeGranularity, timezone?: string | Expression): FunctionExpression; + +// @beta +export function timestampTruncate(fieldName: string, granularity: Expression, timezone?: string | Expression): FunctionExpression; + +// @beta +export function timestampTruncate(timestampExpression: Expression, granularity: TimeGranularity, timezone?: string | Expression): FunctionExpression; + +// @beta +export function timestampTruncate(timestampExpression: Expression, granularity: Expression, timezone?: string | Expression): FunctionExpression; + +// @beta +export function toLower(fieldName: string): FunctionExpression; + +// @beta +export function toLower(stringExpression: Expression): FunctionExpression; + +// @beta +export function toUpper(fieldName: string): FunctionExpression; + +// @beta +export function toUpper(stringExpression: Expression): FunctionExpression; + +// @beta +export function trim(fieldName: string, valueToTrim?: string | Expression): FunctionExpression; + +// @beta +export function trim(stringExpression: Expression, valueToTrim?: string | Expression): FunctionExpression; + +// @beta +export function type(fieldName: string): FunctionExpression; + +// @beta +export function type(expression: Expression): FunctionExpression; + +// @beta +export type UnionStageOptions = StageOptions & { + other: Pipeline; +}; + +// @beta +export function unixMicrosToTimestamp(expr: Expression): FunctionExpression; + +// @beta +export function unixMicrosToTimestamp(fieldName: string): FunctionExpression; + +// @beta +export function unixMillisToTimestamp(expr: Expression): FunctionExpression; + +// @beta +export function unixMillisToTimestamp(fieldName: string): FunctionExpression; + +// @beta +export function unixSecondsToTimestamp(expr: Expression): FunctionExpression; + +// @beta +export function unixSecondsToTimestamp(fieldName: string): FunctionExpression; + +// @beta +export type UnnestStageOptions = StageOptions & { + selectable: Selectable; + indexField?: string; +}; + +// @beta +export function vectorLength(vectorExpression: Expression): FunctionExpression; + +// @beta +export function vectorLength(fieldName: string): FunctionExpression; + +// @beta +export type WhereStageOptions = StageOptions & { + condition: BooleanExpression; +}; + +// @beta +export function xor(first: BooleanExpression, second: BooleanExpression, ...additionalConditions: BooleanExpression[]): BooleanExpression; + + +// Warnings were encountered during analysis: +// +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:830:5 - (ae-forgotten-export) The symbol "Query" needs to be exported by the entry point pipelines.d.ts + +// (No @packageDocumentation comment for this package) + +``` diff --git a/common/api-review/firestore-pipelines.api.md b/common/api-review/firestore-pipelines.api.md new file mode 100644 index 0000000000..2d701d9058 --- /dev/null +++ b/common/api-review/firestore-pipelines.api.md @@ -0,0 +1,1353 @@ +## API Report File for "@firebase/firestore-pipelines" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app'; + +// @beta +export function abs(expr: Expression): FunctionExpression; + +// @beta +export function abs(fieldName: string): FunctionExpression; + +// @beta +export function add(first: Expression, second: Expression | unknown): FunctionExpression; + +// @beta +export function add(fieldName: string, second: Expression | unknown): FunctionExpression; + +// @beta +export type AddFieldsStageOptions = StageOptions & { + fields: Selectable[]; +}; + +// @beta +export class AggregateFunction { + constructor(name: string, params: Expression[]); + as(name: string): AliasedAggregate; + // (undocumented) + exprType: ExpressionType; + } + +// @beta +export type AggregateStageOptions = StageOptions & { + accumulators: AliasedAggregate[]; + groups?: Array; +}; + +// @beta +export class AliasedAggregate { + constructor(aggregate: AggregateFunction, alias: string, _methodName: string | undefined); + // (undocumented) + readonly aggregate: AggregateFunction; + // (undocumented) + readonly alias: string; +} + +// @beta (undocumented) +export class AliasedExpression implements Selectable { + constructor(expr: Expression, alias: string, _methodName: string | undefined); + // (undocumented) + readonly alias: string; + // (undocumented) + readonly expr: Expression; + // (undocumented) + exprType: ExpressionType; + // (undocumented) + selectable: true; +} + +// @beta +export function and(first: BooleanExpression, second: BooleanExpression, ...more: BooleanExpression[]): BooleanExpression; + +// @beta +export function array(elements: unknown[]): FunctionExpression; + +// @beta +export function arrayConcat(firstArray: Expression, secondArray: Expression | unknown[], ...otherArrays: Array): FunctionExpression; + +// @beta +export function arrayConcat(firstArrayField: string, secondArray: Expression | unknown[], ...otherArrays: Array): FunctionExpression; + +// @beta +export function arrayContains(array: Expression, element: Expression): BooleanExpression; + +// @beta +export function arrayContains(array: Expression, element: unknown): BooleanExpression; + +// @beta +export function arrayContains(fieldName: string, element: Expression): BooleanExpression; + +// @beta +export function arrayContains(fieldName: string, element: unknown): BooleanExpression; + +// @beta +export function arrayContainsAll(array: Expression, values: Array): BooleanExpression; + +// @beta +export function arrayContainsAll(fieldName: string, values: Array): BooleanExpression; + +// @beta +export function arrayContainsAll(array: Expression, arrayExpression: Expression): BooleanExpression; + +// @beta +export function arrayContainsAll(fieldName: string, arrayExpression: Expression): BooleanExpression; + +// @beta +export function arrayContainsAny(array: Expression, values: Array): BooleanExpression; + +// @beta +export function arrayContainsAny(fieldName: string, values: Array): BooleanExpression; + +// @beta +export function arrayContainsAny(array: Expression, values: Expression): BooleanExpression; + +// @beta +export function arrayContainsAny(fieldName: string, values: Expression): BooleanExpression; + +// @beta +export function arrayGet(arrayField: string, offset: number): FunctionExpression; + +// @beta +export function arrayGet(arrayField: string, offsetExpr: Expression): FunctionExpression; + +// @beta +export function arrayGet(arrayExpression: Expression, offset: number): FunctionExpression; + +// @beta +export function arrayGet(arrayExpression: Expression, offsetExpr: Expression): FunctionExpression; + +// @beta +export function arrayLength(fieldName: string): FunctionExpression; + +// @beta +export function arrayLength(array: Expression): FunctionExpression; + +// @beta +export function arraySum(fieldName: string): FunctionExpression; + +// @beta +export function arraySum(expression: Expression): FunctionExpression; + +// @beta +export function ascending(expr: Expression): Ordering; + +// @beta +export function ascending(fieldName: string): Ordering; + +// @beta +export function average(expression: Expression): AggregateFunction; + +// @beta +export function average(fieldName: string): AggregateFunction; + +// @beta +export abstract class BooleanExpression extends Expression { + conditional(thenExpr: Expression, elseExpr: Expression): FunctionExpression; + countIf(): AggregateFunction; + ifError(catchValue: BooleanExpression): BooleanExpression; + ifError(catchValue: boolean): BooleanExpression; + ifError(catchValue: Expression): FunctionExpression; + ifError(catchValue: unknown): FunctionExpression; + not(): BooleanExpression; +} + +// @beta +export function byteLength(expr: Expression): FunctionExpression; + +// @beta +export function byteLength(fieldName: string): FunctionExpression; + +// @beta +export function ceil(fieldName: string): FunctionExpression; + +// @beta +export function ceil(expression: Expression): FunctionExpression; + +// @beta +export function charLength(fieldName: string): FunctionExpression; + +// @beta +export function charLength(stringExpression: Expression): FunctionExpression; + +// @beta +export type CollectionGroupStageOptions = StageOptions & { + collectionId: string; + forceIndex?: string; +}; + +// @beta +export function collectionId(fieldName: string): FunctionExpression; + +// @beta +export function collectionId(expression: Expression): FunctionExpression; + +// @beta +export type CollectionStageOptions = StageOptions & { + collection: string | Query; + forceIndex?: string; +}; + +// @beta +export function concat(first: Expression, second: Expression | unknown, ...others: Array): FunctionExpression; + +// @beta +export function concat(fieldName: string, second: Expression | unknown, ...others: Array): FunctionExpression; + +// @beta +export function conditional(condition: BooleanExpression, thenExpr: Expression, elseExpr: Expression): FunctionExpression; + +// @beta +export function constant(value: number): Expression; + +// @beta +export function constant(value: string): Expression; + +// @beta +export function constant(value: boolean): BooleanExpression; + +// @beta +export function constant(value: null): Expression; + +// Warning: (ae-forgotten-export) The symbol "GeoPoint" needs to be exported by the entry point pipelines.d.ts +// +// @beta +export function constant(value: GeoPoint): Expression; + +// Warning: (ae-forgotten-export) The symbol "Timestamp" needs to be exported by the entry point pipelines.d.ts +// +// @beta +export function constant(value: Timestamp): Expression; + +// @beta +export function constant(value: Date): Expression; + +// Warning: (ae-forgotten-export) The symbol "Bytes" needs to be exported by the entry point pipelines.d.ts +// +// @beta +export function constant(value: Bytes): Expression; + +// Warning: (ae-forgotten-export) The symbol "DocumentReference" needs to be exported by the entry point pipelines.d.ts +// +// @beta +export function constant(value: DocumentReference): Expression; + +// Warning: (ae-forgotten-export) The symbol "VectorValue" needs to be exported by the entry point pipelines.d.ts +// +// @beta +export function constant(value: VectorValue): Expression; + +// @beta +export function cosineDistance(fieldName: string, vector: number[] | VectorValue): FunctionExpression; + +// @beta +export function cosineDistance(fieldName: string, vectorExpression: Expression): FunctionExpression; + +// @beta +export function cosineDistance(vectorExpression: Expression, vector: number[] | VectorValue): FunctionExpression; + +// @beta +export function cosineDistance(vectorExpression: Expression, otherVectorExpression: Expression): FunctionExpression; + +// @beta +export function count(expression: Expression): AggregateFunction; + +// @beta +export function count(fieldName: string): AggregateFunction; + +// @beta +export function countAll(): AggregateFunction; + +// @beta +export function countDistinct(expr: Expression | string): AggregateFunction; + +// @beta +export function countIf(booleanExpr: BooleanExpression): AggregateFunction; + +// @beta +export function currentTimestamp(): FunctionExpression; + +// @beta +export type DatabaseStageOptions = StageOptions & {}; + +// @beta +export function descending(expr: Expression): Ordering; + +// @beta +export function descending(fieldName: string): Ordering; + +// @beta +export type DistinctStageOptions = StageOptions & { + groups: Array; +}; + +// @beta +export function divide(left: Expression, right: Expression): FunctionExpression; + +// @beta +export function divide(expression: Expression, value: unknown): FunctionExpression; + +// @beta +export function divide(fieldName: string, expressions: Expression): FunctionExpression; + +// @beta +export function divide(fieldName: string, value: unknown): FunctionExpression; + +// @beta +export function documentId(documentPath: string | DocumentReference): FunctionExpression; + +// @beta +export function documentId(documentPathExpr: Expression): FunctionExpression; + +// @beta +export type DocumentsStageOptions = StageOptions & { + docs: Array; +}; + +// @beta +export function dotProduct(fieldName: string, vector: number[] | VectorValue): FunctionExpression; + +// @beta +export function dotProduct(fieldName: string, vectorExpression: Expression): FunctionExpression; + +// @beta +export function dotProduct(vectorExpression: Expression, vector: number[] | VectorValue): FunctionExpression; + +// @beta +export function dotProduct(vectorExpression: Expression, otherVectorExpression: Expression): FunctionExpression; + +// @beta +export function endsWith(fieldName: string, suffix: string): BooleanExpression; + +// @beta +export function endsWith(fieldName: string, suffix: Expression): BooleanExpression; + +// @beta +export function endsWith(stringExpression: Expression, suffix: string): BooleanExpression; + +// @beta +export function endsWith(stringExpression: Expression, suffix: Expression): BooleanExpression; + +// @beta +export function equal(left: Expression, right: Expression): BooleanExpression; + +// @beta +export function equal(expression: Expression, value: unknown): BooleanExpression; + +// @beta +export function equal(fieldName: string, expression: Expression): BooleanExpression; + +// @beta +export function equal(fieldName: string, value: unknown): BooleanExpression; + +// @beta +export function equalAny(expression: Expression, values: Array): BooleanExpression; + +// @beta +export function equalAny(expression: Expression, arrayExpression: Expression): BooleanExpression; + +// @beta +export function equalAny(fieldName: string, values: Array): BooleanExpression; + +// @beta +export function equalAny(fieldName: string, arrayExpression: Expression): BooleanExpression; + +// @beta +export function euclideanDistance(fieldName: string, vector: number[] | VectorValue): FunctionExpression; + +// @beta +export function euclideanDistance(fieldName: string, vectorExpression: Expression): FunctionExpression; + +// @beta +export function euclideanDistance(vectorExpression: Expression, vector: number[] | VectorValue): FunctionExpression; + +// @beta +export function euclideanDistance(vectorExpression: Expression, otherVectorExpression: Expression): FunctionExpression; + +// @beta +export function execute(pipeline: Pipeline): Promise; + +// @beta +export function execute(options: PipelineExecuteOptions): Promise; + +// @beta +export function exists(value: Expression): BooleanExpression; + +// @beta +export function exists(fieldName: string): BooleanExpression; + +// @beta +export function exp(expression: Expression): FunctionExpression; + +// @beta +export function exp(fieldName: string): FunctionExpression; + +// @beta +export abstract class Expression { + abs(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + add(second: Expression | unknown): FunctionExpression; + /* Excluded from this release type: _readUserData */ + arrayConcat(secondArray: Expression | unknown[], ...otherArrays: Array): FunctionExpression; + /* Excluded from this release type: _readUserData */ + arrayContains(expression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + arrayContains(value: unknown): BooleanExpression; + /* Excluded from this release type: _readUserData */ + arrayContainsAll(values: Array): BooleanExpression; + /* Excluded from this release type: _readUserData */ + arrayContainsAll(arrayExpression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + arrayContainsAny(values: Array): BooleanExpression; + /* Excluded from this release type: _readUserData */ + arrayContainsAny(arrayExpression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + arrayGet(offset: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + arrayGet(offsetExpr: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + arrayLength(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + arrayReverse(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + arraySum(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + as(name: string): AliasedExpression; + /* Excluded from this release type: _readUserData */ + asBoolean(): BooleanExpression; + /* Excluded from this release type: _readUserData */ + ascending(): Ordering; + /* Excluded from this release type: _readUserData */ + average(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + byteLength(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + ceil(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + charLength(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + collectionId(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + concat(second: Expression | unknown, ...others: Array): FunctionExpression; + /* Excluded from this release type: _readUserData */ + cosineDistance(vectorExpression: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + cosineDistance(vector: VectorValue | number[]): FunctionExpression; + /* Excluded from this release type: _readUserData */ + count(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + countDistinct(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + descending(): Ordering; + /* Excluded from this release type: _readUserData */ + divide(divisor: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + divide(divisor: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + documentId(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + dotProduct(vectorExpression: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + dotProduct(vector: VectorValue | number[]): FunctionExpression; + /* Excluded from this release type: _readUserData */ + endsWith(suffix: string): BooleanExpression; + /* Excluded from this release type: _readUserData */ + endsWith(suffix: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + equal(expression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + equal(value: unknown): BooleanExpression; + /* Excluded from this release type: _readUserData */ + equalAny(values: Array): BooleanExpression; + /* Excluded from this release type: _readUserData */ + equalAny(arrayExpression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + euclideanDistance(vectorExpression: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + euclideanDistance(vector: VectorValue | number[]): FunctionExpression; + /* Excluded from this release type: _readUserData */ + exists(): BooleanExpression; + /* Excluded from this release type: _readUserData */ + exp(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + // (undocumented) + abstract readonly expressionType: ExpressionType; + /* Excluded from this release type: _readUserData */ + floor(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + greaterThan(expression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + greaterThan(value: unknown): BooleanExpression; + /* Excluded from this release type: _readUserData */ + greaterThanOrEqual(expression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + greaterThanOrEqual(value: unknown): BooleanExpression; + /* Excluded from this release type: _readUserData */ + ifAbsent(elseValue: unknown): Expression; + /* Excluded from this release type: _readUserData */ + ifAbsent(elseExpression: unknown): Expression; + /* Excluded from this release type: _readUserData */ + ifError(catchExpr: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + ifError(catchValue: unknown): FunctionExpression; + /* Excluded from this release type: _readUserData */ + isAbsent(): BooleanExpression; + /* Excluded from this release type: _readUserData */ + isError(): BooleanExpression; + /* Excluded from this release type: _readUserData */ + join(delimiterExpression: Expression): Expression; + /* Excluded from this release type: _readUserData */ + join(delimiter: string): Expression; + /* Excluded from this release type: _readUserData */ + length(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + lessThan(experession: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + lessThan(value: unknown): BooleanExpression; + /* Excluded from this release type: _readUserData */ + lessThanOrEqual(expression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + lessThanOrEqual(value: unknown): BooleanExpression; + /* Excluded from this release type: _readUserData */ + like(pattern: string): BooleanExpression; + /* Excluded from this release type: _readUserData */ + like(pattern: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + ln(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + log10(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + logicalMaximum(second: Expression | unknown, ...others: Array): FunctionExpression; + /* Excluded from this release type: _readUserData */ + logicalMinimum(second: Expression | unknown, ...others: Array): FunctionExpression; + /* Excluded from this release type: _readUserData */ + mapGet(subfield: string): FunctionExpression; + /* Excluded from this release type: _readUserData */ + mapMerge(secondMap: Record | Expression, ...otherMaps: Array | Expression>): FunctionExpression; + /* Excluded from this release type: _readUserData */ + mapRemove(key: string): FunctionExpression; + /* Excluded from this release type: _readUserData */ + mapRemove(keyExpr: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + maximum(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + minimum(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + mod(expression: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + mod(value: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + multiply(second: Expression | number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + notEqual(expression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + notEqual(value: unknown): BooleanExpression; + /* Excluded from this release type: _readUserData */ + notEqualAny(values: Array): BooleanExpression; + /* Excluded from this release type: _readUserData */ + notEqualAny(arrayExpression: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + pow(exponent: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + pow(exponent: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + regexContains(pattern: string): BooleanExpression; + /* Excluded from this release type: _readUserData */ + regexContains(pattern: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + regexMatch(pattern: string): BooleanExpression; + /* Excluded from this release type: _readUserData */ + regexMatch(pattern: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + reverse(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + round(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + round(decimalPlaces: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + round(decimalPlaces: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + split(delimiter: string): FunctionExpression; + /* Excluded from this release type: _readUserData */ + split(delimiter: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + sqrt(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + startsWith(prefix: string): BooleanExpression; + /* Excluded from this release type: _readUserData */ + startsWith(prefix: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + stringConcat(secondString: Expression | string, ...otherStrings: Array): FunctionExpression; + /* Excluded from this release type: _readUserData */ + stringContains(substring: string): BooleanExpression; + /* Excluded from this release type: _readUserData */ + stringContains(expr: Expression): BooleanExpression; + /* Excluded from this release type: _readUserData */ + stringReverse(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + substring(position: number, length?: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + substring(position: Expression, length?: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + subtract(subtrahend: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + subtract(subtrahend: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + sum(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + timestampAdd(unit: Expression, amount: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampSubtract(unit: Expression, amount: Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampSubtract(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampToUnixMicros(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampToUnixMillis(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampToUnixSeconds(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampTruncate(granularity: TimeGranularity, timezone?: string | Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + timestampTruncate(granularity: Expression, timezone?: string | Expression): FunctionExpression; + /* Excluded from this release type: _readUserData */ + toLower(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + toUpper(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + trim(valueToTrim?: string | Expression | Bytes): FunctionExpression; + /* Excluded from this release type: _readUserData */ + type(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + unixMicrosToTimestamp(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + unixMillisToTimestamp(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + unixSecondsToTimestamp(): FunctionExpression; + /* Excluded from this release type: _readUserData */ + vectorLength(): FunctionExpression; +} + +// @beta +export type ExpressionType = 'Field' | 'Constant' | 'Function' | 'AggregateFunction' | 'ListOfExpressions' | 'AliasedExpression'; + +// @beta +export class Field extends Expression implements Selectable { + // (undocumented) + get alias(): string; + // (undocumented) + get expr(): Expression; + // (undocumented) + readonly expressionType: ExpressionType; + // (undocumented) + get fieldName(): string; + // (undocumented) + selectable: true; +} + +// @beta +export function field(name: string): Field; + +// Warning: (ae-forgotten-export) The symbol "FieldPath" needs to be exported by the entry point pipelines.d.ts +// +// @beta +export function field(path: FieldPath): Field; + +// @beta +export type FindNearestStageOptions = StageOptions & { + field: Field | string; + vectorValue: VectorValue | number[]; + distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; + limit?: number; + distanceField?: string; +}; + +// @beta +export function floor(expr: Expression): FunctionExpression; + +// @beta +export function floor(fieldName: string): FunctionExpression; + +// @beta +export class FunctionExpression extends Expression { + constructor(name: string, params: Expression[]); + constructor(name: string, params: Expression[], _methodName: string | undefined); + // (undocumented) + readonly expressionType: ExpressionType; + } + +// @beta +export function greaterThan(left: Expression, right: Expression): BooleanExpression; + +// @beta +export function greaterThan(expression: Expression, value: unknown): BooleanExpression; + +// @beta +export function greaterThan(fieldName: string, expression: Expression): BooleanExpression; + +// @beta +export function greaterThan(fieldName: string, value: unknown): BooleanExpression; + +// @beta +export function greaterThanOrEqual(left: Expression, right: Expression): BooleanExpression; + +// @beta +export function greaterThanOrEqual(expression: Expression, value: unknown): BooleanExpression; + +// @beta +export function greaterThanOrEqual(fieldName: string, value: Expression): BooleanExpression; + +// @beta +export function greaterThanOrEqual(fieldName: string, value: unknown): BooleanExpression; + +// @beta +export function ifAbsent(ifExpr: Expression, elseExpr: Expression): Expression; + +// @beta +export function ifAbsent(ifExpr: Expression, elseValue: unknown): Expression; + +// @beta +export function ifAbsent(ifFieldName: string, elseExpr: Expression): Expression; + +// @beta +export function ifAbsent(ifFieldName: string | Expression, elseValue: Expression | unknown): Expression; + +// @beta +export function ifError(tryExpr: BooleanExpression, catchExpr: BooleanExpression): BooleanExpression; + +// @beta +export function ifError(tryExpr: Expression, catchExpr: Expression): FunctionExpression; + +// @beta +export function ifError(tryExpr: Expression, catchValue: unknown): FunctionExpression; + +// @beta +export function isAbsent(value: Expression): BooleanExpression; + +// @beta +export function isAbsent(field: string): BooleanExpression; + +// @beta +export function isError(value: Expression): BooleanExpression; + +// @beta +export function join(arrayFieldName: string, delimiter: string): Expression; + +// @beta +export function join(arrayExpression: Expression, delimiterExpression: Expression): Expression; + +// @beta +export function join(arrayExpression: Expression, delimiter: string): Expression; + +// @beta +export function join(arrayFieldName: string, delimiterExpression: Expression): Expression; + +// @beta +function length_2(fieldName: string): FunctionExpression; + +// @beta +function length_2(expression: Expression): FunctionExpression; + +export { length_2 as length } + +// @beta +export function lessThan(left: Expression, right: Expression): BooleanExpression; + +// @beta +export function lessThan(expression: Expression, value: unknown): BooleanExpression; + +// @beta +export function lessThan(fieldName: string, expression: Expression): BooleanExpression; + +// @beta +export function lessThan(fieldName: string, value: unknown): BooleanExpression; + +// @beta +export function lessThanOrEqual(left: Expression, right: Expression): BooleanExpression; + +// @beta +export function lessThanOrEqual(expression: Expression, value: unknown): BooleanExpression; + +// @beta +export function lessThanOrEqual(fieldName: string, expression: Expression): BooleanExpression; + +// @beta +export function lessThanOrEqual(fieldName: string, value: unknown): BooleanExpression; + +// @beta +export function like(fieldName: string, pattern: string): BooleanExpression; + +// @beta +export function like(fieldName: string, pattern: Expression): BooleanExpression; + +// @beta +export function like(stringExpression: Expression, pattern: string): BooleanExpression; + +// @beta +export function like(stringExpression: Expression, pattern: Expression): BooleanExpression; + +// @beta +export type LimitStageOptions = StageOptions & { + limit: number; +}; + +// @beta +export function ln(fieldName: string): FunctionExpression; + +// @beta +export function ln(expression: Expression): FunctionExpression; + +// @beta +export function log(expression: Expression, base: number): FunctionExpression; + +// @beta +export function log(expression: Expression, base: Expression): FunctionExpression; + +// @beta +export function log(fieldName: string, base: number): FunctionExpression; + +// @beta +export function log(fieldName: string, base: Expression): FunctionExpression; + +// @beta +export function log10(fieldName: string): FunctionExpression; + +// @beta +export function log10(expression: Expression): FunctionExpression; + +// @beta +export function logicalMaximum(first: Expression, second: Expression | unknown, ...others: Array): FunctionExpression; + +// @beta +export function logicalMaximum(fieldName: string, second: Expression | unknown, ...others: Array): FunctionExpression; + +// @beta +export function logicalMinimum(first: Expression, second: Expression | unknown, ...others: Array): FunctionExpression; + +// @beta +export function logicalMinimum(fieldName: string, second: Expression | unknown, ...others: Array): FunctionExpression; + +// @beta +export function map(elements: Record): FunctionExpression; + +// @beta +export function mapGet(fieldName: string, subField: string): FunctionExpression; + +// @beta +export function mapGet(mapExpression: Expression, subField: string): FunctionExpression; + +// @beta +export function mapMerge(mapField: string, secondMap: Record | Expression, ...otherMaps: Array | Expression>): FunctionExpression; + +// @beta +export function mapMerge(firstMap: Record | Expression, secondMap: Record | Expression, ...otherMaps: Array | Expression>): FunctionExpression; + +// @beta +export function mapRemove(mapField: string, key: string): FunctionExpression; + +// @beta +export function mapRemove(mapExpr: Expression, key: string): FunctionExpression; + +// @beta +export function mapRemove(mapField: string, keyExpr: Expression): FunctionExpression; + +// @beta +export function mapRemove(mapExpr: Expression, keyExpr: Expression): FunctionExpression; + +// @beta +export function maximum(expression: Expression): AggregateFunction; + +// @beta +export function maximum(fieldName: string): AggregateFunction; + +// @beta +export function minimum(expression: Expression): AggregateFunction; + +// @beta +export function minimum(fieldName: string): AggregateFunction; + +// @beta +export function mod(left: Expression, right: Expression): FunctionExpression; + +// @beta +export function mod(expression: Expression, value: unknown): FunctionExpression; + +// @beta +export function mod(fieldName: string, expression: Expression): FunctionExpression; + +// @beta +export function mod(fieldName: string, value: unknown): FunctionExpression; + +// @beta +export function multiply(first: Expression, second: Expression | unknown): FunctionExpression; + +// @beta +export function multiply(fieldName: string, second: Expression | unknown): FunctionExpression; + +// @beta +export function not(booleanExpr: BooleanExpression): BooleanExpression; + +// @beta +export function notEqual(left: Expression, right: Expression): BooleanExpression; + +// @beta +export function notEqual(expression: Expression, value: unknown): BooleanExpression; + +// @beta +export function notEqual(fieldName: string, expression: Expression): BooleanExpression; + +// @beta +export function notEqual(fieldName: string, value: unknown): BooleanExpression; + +// @beta +export function notEqualAny(element: Expression, values: Array): BooleanExpression; + +// @beta +export function notEqualAny(fieldName: string, values: Array): BooleanExpression; + +// @beta +export function notEqualAny(element: Expression, arrayExpression: Expression): BooleanExpression; + +// @beta +export function notEqualAny(fieldName: string, arrayExpression: Expression): BooleanExpression; + +// @beta +export type OffsetStageOptions = StageOptions & { + offset: number; +}; + +// @beta +export type OneOf = { + [K in keyof T]: Pick & { + [P in Exclude]?: undefined; + }; +}[keyof T]; + +// @beta +export function or(first: BooleanExpression, second: BooleanExpression, ...more: BooleanExpression[]): BooleanExpression; + +// @beta +export class Ordering { + constructor(expr: Expression, direction: 'ascending' | 'descending', _methodName: string | undefined); + // (undocumented) + readonly direction: 'ascending' | 'descending'; + // (undocumented) + readonly expr: Expression; +} + +// @beta (undocumented) +export class Pipeline { + // (undocumented) + addFields(field: Selectable, ...additionalFields: Selectable[]): Pipeline; + // (undocumented) + addFields(options: AddFieldsStageOptions): Pipeline; + // (undocumented) + aggregate(accumulator: AliasedAggregate, ...additionalAccumulators: AliasedAggregate[]): Pipeline; + // (undocumented) + aggregate(options: AggregateStageOptions): Pipeline; + // (undocumented) + distinct(group: string | Selectable, ...additionalGroups: Array): Pipeline; + // (undocumented) + distinct(options: DistinctStageOptions): Pipeline; + // (undocumented) + findNearest(options: FindNearestStageOptions): Pipeline; + // (undocumented) + limit(limit: number): Pipeline; + // (undocumented) + limit(options: LimitStageOptions): Pipeline; + // (undocumented) + offset(offset: number): Pipeline; + // (undocumented) + offset(options: OffsetStageOptions): Pipeline; + // (undocumented) + rawStage(name: string, params: unknown[], options?: { [key: string]: Expression | unknown; }): Pipeline; + // (undocumented) + removeFields(fieldValue: Field | string, ...additionalFields: Array): Pipeline; + // (undocumented) + removeFields(options: RemoveFieldsStageOptions): Pipeline; + // (undocumented) + replaceWith(fieldName: string): Pipeline; + // (undocumented) + replaceWith(expr: Expression): Pipeline; + // (undocumented) + replaceWith(options: ReplaceWithStageOptions): Pipeline; + // (undocumented) + sample(documents: number): Pipeline; + // (undocumented) + sample(options: SampleStageOptions): Pipeline; + // (undocumented) + select(selection: Selectable | string, ...additionalSelections: Array): Pipeline; + // (undocumented) + select(options: SelectStageOptions): Pipeline; + // (undocumented) + sort(ordering: Ordering, ...additionalOrderings: Ordering[]): Pipeline; + // (undocumented) + sort(options: SortStageOptions): Pipeline; + // (undocumented) + union(other: Pipeline): Pipeline; + // (undocumented) + union(options: UnionStageOptions): Pipeline; + // (undocumented) + unnest(selectable: Selectable, indexField?: string): Pipeline; + // (undocumented) + unnest(options: UnnestStageOptions): Pipeline; + // (undocumented) + where(condition: BooleanExpression): Pipeline; + // (undocumented) + where(options: WhereStageOptions): Pipeline; +} + +// @beta +export interface PipelineExecuteOptions { + indexMode?: 'recommended'; + pipeline: Pipeline; + rawOptions?: { + [name: string]: unknown; + }; +} + +// Warning: (ae-forgotten-export) The symbol "DocumentData" needs to be exported by the entry point pipelines.d.ts +// +// @beta +export class PipelineResult { + /* Excluded from this release type: _ref */ + /* Excluded from this release type: _fields */ + /* Excluded from this release type: __constructor */ + get createTime(): Timestamp | undefined; + data(): AppModelType; + get(fieldPath: string | FieldPath | Field): any; + get id(): string | undefined; + get ref(): DocumentReference | undefined; + get updateTime(): Timestamp | undefined; +} + +// @beta +export function pipelineResultEqual(left: PipelineResult, right: PipelineResult): boolean; + +// @beta +export class PipelineSnapshot { + constructor(pipeline: Pipeline, results: PipelineResult[], executionTime?: Timestamp); + get executionTime(): Timestamp; + get results(): PipelineResult[]; +} + +// @beta +export class PipelineSource { + collection(collection: string | Query): PipelineType; + collection(options: CollectionStageOptions): PipelineType; + collectionGroup(collectionId: string): PipelineType; + collectionGroup(options: CollectionGroupStageOptions): PipelineType; + createFrom(query: Query): Pipeline; + database(): PipelineType; + database(options: DatabaseStageOptions): PipelineType; + documents(docs: Array): PipelineType; + documents(options: DocumentsStageOptions): PipelineType; + } + +// @beta +export function pow(base: Expression, exponent: Expression): FunctionExpression; + +// @beta +export function pow(base: Expression, exponent: number): FunctionExpression; + +// @beta +export function pow(base: string, exponent: Expression): FunctionExpression; + +// @beta +export function pow(base: string, exponent: number): FunctionExpression; + +// @beta +export function regexContains(fieldName: string, pattern: string): BooleanExpression; + +// @beta +export function regexContains(fieldName: string, pattern: Expression): BooleanExpression; + +// @beta +export function regexContains(stringExpression: Expression, pattern: string): BooleanExpression; + +// @beta +export function regexContains(stringExpression: Expression, pattern: Expression): BooleanExpression; + +// @beta +export function regexMatch(fieldName: string, pattern: string): BooleanExpression; + +// @beta +export function regexMatch(fieldName: string, pattern: Expression): BooleanExpression; + +// @beta +export function regexMatch(stringExpression: Expression, pattern: string): BooleanExpression; + +// @beta +export function regexMatch(stringExpression: Expression, pattern: Expression): BooleanExpression; + +// @beta +export type RemoveFieldsStageOptions = StageOptions & { + fields: Array; +}; + +// @beta +export type ReplaceWithStageOptions = StageOptions & { + map: Expression | string; +}; + +// @beta +export function reverse(stringExpression: Expression): FunctionExpression; + +// @beta +export function reverse(field: string): FunctionExpression; + +// @beta +export function round(fieldName: string): FunctionExpression; + +// @beta +export function round(expression: Expression): FunctionExpression; + +// @beta +export function round(fieldName: string, decimalPlaces: number | Expression): FunctionExpression; + +// @beta +export function round(expression: Expression, decimalPlaces: number | Expression): FunctionExpression; + +// @beta +export type SampleStageOptions = StageOptions & OneOf<{ + percentage: number; + documents: number; +}>; + +// @beta +export interface Selectable { + // (undocumented) + selectable: true; +} + +// @beta +export type SelectStageOptions = StageOptions & { + selections: Array; +}; + +// @beta +export type SortStageOptions = StageOptions & { + orderings: Ordering[]; +}; + +// @beta +export function split(fieldName: string, delimiter: string): FunctionExpression; + +// @beta +export function split(fieldName: string, delimiter: Expression): FunctionExpression; + +// @beta +export function split(expression: Expression, delimiter: string): FunctionExpression; + +// @beta +export function split(expression: Expression, delimiter: Expression): FunctionExpression; + +// @beta +export function sqrt(expression: Expression): FunctionExpression; + +// @beta +export function sqrt(fieldName: string): FunctionExpression; + +// @beta +export interface StageOptions { + rawOptions?: { + [name: string]: unknown; + }; +} + +// @beta +export function startsWith(fieldName: string, prefix: string): BooleanExpression; + +// @beta +export function startsWith(fieldName: string, prefix: Expression): BooleanExpression; + +// @beta +export function startsWith(stringExpression: Expression, prefix: string): BooleanExpression; + +// @beta +export function startsWith(stringExpression: Expression, prefix: Expression): BooleanExpression; + +// @beta +export function stringConcat(fieldName: string, secondString: Expression | string, ...otherStrings: Array): FunctionExpression; + +// @beta +export function stringConcat(firstString: Expression, secondString: Expression | string, ...otherStrings: Array): FunctionExpression; + +// @beta +export function stringContains(fieldName: string, substring: string): BooleanExpression; + +// @beta +export function stringContains(fieldName: string, substring: Expression): BooleanExpression; + +// @beta +export function stringContains(stringExpression: Expression, substring: string): BooleanExpression; + +// @beta +export function stringContains(stringExpression: Expression, substring: Expression): BooleanExpression; + +// @beta +export function stringReverse(stringExpression: Expression): FunctionExpression; + +// @beta +export function stringReverse(field: string): FunctionExpression; + +// @beta +export function substring(field: string, position: number, length?: number): FunctionExpression; + +// @beta +export function substring(input: Expression, position: number, length?: number): FunctionExpression; + +// @beta +export function substring(field: string, position: Expression, length?: Expression): FunctionExpression; + +// @beta +export function substring(input: Expression, position: Expression, length?: Expression): FunctionExpression; + +// @beta +export function subtract(left: Expression, right: Expression): FunctionExpression; + +// @beta +export function subtract(expression: Expression, value: unknown): FunctionExpression; + +// @beta +export function subtract(fieldName: string, expression: Expression): FunctionExpression; + +// @beta +export function subtract(fieldName: string, value: unknown): FunctionExpression; + +// @beta +export function sum(expression: Expression): AggregateFunction; + +// @beta +export function sum(fieldName: string): AggregateFunction; + +// @beta +export type TimeGranularity = 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'week(monday)' | 'week(tuesday)' | 'week(wednesday)' | 'week(thursday)' | 'week(friday)' | 'week(saturday)' | 'week(sunday)' | 'isoWeek' | 'month' | 'quarter' | 'year' | 'isoYear'; + +// @beta +export function timestampAdd(timestamp: Expression, unit: Expression, amount: Expression): FunctionExpression; + +// @beta +export function timestampAdd(timestamp: Expression, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + +// @beta +export function timestampAdd(fieldName: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + +// @beta +export function timestampSubtract(timestamp: Expression, unit: Expression, amount: Expression): FunctionExpression; + +// @beta +export function timestampSubtract(timestamp: Expression, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + +// @beta +export function timestampSubtract(fieldName: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + +// @beta +export function timestampToUnixMicros(expr: Expression): FunctionExpression; + +// @beta +export function timestampToUnixMicros(fieldName: string): FunctionExpression; + +// @beta +export function timestampToUnixMillis(expr: Expression): FunctionExpression; + +// @beta +export function timestampToUnixMillis(fieldName: string): FunctionExpression; + +// @beta +export function timestampToUnixSeconds(expr: Expression): FunctionExpression; + +// @beta +export function timestampToUnixSeconds(fieldName: string): FunctionExpression; + +// @beta +export function timestampTruncate(fieldName: string, granularity: TimeGranularity, timezone?: string | Expression): FunctionExpression; + +// @beta +export function timestampTruncate(fieldName: string, granularity: Expression, timezone?: string | Expression): FunctionExpression; + +// @beta +export function timestampTruncate(timestampExpression: Expression, granularity: TimeGranularity, timezone?: string | Expression): FunctionExpression; + +// @beta +export function timestampTruncate(timestampExpression: Expression, granularity: Expression, timezone?: string | Expression): FunctionExpression; + +// @beta +export function toLower(fieldName: string): FunctionExpression; + +// @beta +export function toLower(stringExpression: Expression): FunctionExpression; + +// @beta +export function toUpper(fieldName: string): FunctionExpression; + +// @beta +export function toUpper(stringExpression: Expression): FunctionExpression; + +// @beta +export function trim(fieldName: string, valueToTrim?: string | Expression): FunctionExpression; + +// @beta +export function trim(stringExpression: Expression, valueToTrim?: string | Expression): FunctionExpression; + +// @beta +export function type(fieldName: string): FunctionExpression; + +// @beta +export function type(expression: Expression): FunctionExpression; + +// @beta +export type UnionStageOptions = StageOptions & { + other: Pipeline; +}; + +// @beta +export function unixMicrosToTimestamp(expr: Expression): FunctionExpression; + +// @beta +export function unixMicrosToTimestamp(fieldName: string): FunctionExpression; + +// @beta +export function unixMillisToTimestamp(expr: Expression): FunctionExpression; + +// @beta +export function unixMillisToTimestamp(fieldName: string): FunctionExpression; + +// @beta +export function unixSecondsToTimestamp(expr: Expression): FunctionExpression; + +// @beta +export function unixSecondsToTimestamp(fieldName: string): FunctionExpression; + +// @beta +export type UnnestStageOptions = StageOptions & { + selectable: Selectable; + indexField?: string; +}; + +// @beta +export function vectorLength(vectorExpression: Expression): FunctionExpression; + +// @beta +export function vectorLength(fieldName: string): FunctionExpression; + +// @beta +export type WhereStageOptions = StageOptions & { + condition: BooleanExpression; +}; + +// @beta +export function xor(first: BooleanExpression, second: BooleanExpression, ...additionalConditions: BooleanExpression[]): BooleanExpression; + + +// Warnings were encountered during analysis: +// +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:827:5 - (ae-forgotten-export) The symbol "Query" needs to be exported by the entry point pipelines.d.ts + +// (No @packageDocumentation comment for this package) + +``` diff --git a/docs-devsite/_toc.yaml b/docs-devsite/_toc.yaml index 92633c553a..56a8f88dec 100644 --- a/docs-devsite/_toc.yaml +++ b/docs-devsite/_toc.yaml @@ -597,6 +597,71 @@ toc: path: /docs/reference/js/firestore_lite.vectorvalue.md - title: WriteBatch path: /docs/reference/js/firestore_lite.writebatch.md +- title: firestore/pipelines + path: /docs/reference/js/firestore_pipelines.md + section: + - title: AggregateFunction + path: /docs/reference/js/firestore_pipelines.aggregatefunction.md + - title: AliasedAggregate + path: /docs/reference/js/firestore_pipelines.aliasedaggregate.md + - title: AliasedExpression + path: /docs/reference/js/firestore_pipelines.aliasedexpression.md + - title: BooleanExpression + path: /docs/reference/js/firestore_pipelines.booleanexpression.md + - title: Bytes + path: /docs/reference/js/firestore_pipelines.bytes.md + - title: CollectionReference + path: /docs/reference/js/firestore_pipelines.collectionreference.md + - title: DocumentData + path: /docs/reference/js/firestore_pipelines.documentdata.md + - title: DocumentReference + path: /docs/reference/js/firestore_pipelines.documentreference.md + - title: DocumentSnapshot + path: /docs/reference/js/firestore_pipelines.documentsnapshot.md + - title: Expression + path: /docs/reference/js/firestore_pipelines.expression.md + - title: Field + path: /docs/reference/js/firestore_pipelines.field.md + - title: FieldPath + path: /docs/reference/js/firestore_pipelines.fieldpath.md + - title: FieldValue + path: /docs/reference/js/firestore_pipelines.fieldvalue.md + - title: Firestore + path: /docs/reference/js/firestore_pipelines.firestore.md + - title: FirestoreDataConverter + path: /docs/reference/js/firestore_pipelines.firestoredataconverter.md + - title: FunctionExpression + path: /docs/reference/js/firestore_pipelines.functionexpression.md + - title: GeoPoint + path: /docs/reference/js/firestore_pipelines.geopoint.md + - title: Ordering + path: /docs/reference/js/firestore_pipelines.ordering.md + - title: Pipeline + path: /docs/reference/js/firestore_pipelines.pipeline.md + - title: PipelineExecuteOptions + path: /docs/reference/js/firestore_pipelines.pipelineexecuteoptions.md + - title: PipelineResult + path: /docs/reference/js/firestore_pipelines.pipelineresult.md + - title: PipelineSnapshot + path: /docs/reference/js/firestore_pipelines.pipelinesnapshot.md + - title: PipelineSource + path: /docs/reference/js/firestore_pipelines.pipelinesource.md + - title: Query + path: /docs/reference/js/firestore_pipelines.query.md + - title: QueryDocumentSnapshot + path: /docs/reference/js/firestore_pipelines.querydocumentsnapshot.md + - title: Selectable + path: /docs/reference/js/firestore_pipelines.selectable.md + - title: SnapshotMetadata + path: /docs/reference/js/firestore_pipelines.snapshotmetadata.md + - title: SnapshotOptions + path: /docs/reference/js/firestore_pipelines.snapshotoptions.md + - title: StageOptions + path: /docs/reference/js/firestore_pipelines.stageoptions.md + - title: Timestamp + path: /docs/reference/js/firestore_pipelines.timestamp.md + - title: VectorValue + path: /docs/reference/js/firestore_pipelines.vectorvalue.md - title: functions path: /docs/reference/js/functions.md section: diff --git a/docs-devsite/firestore.md b/docs-devsite/firestore.md index c8c647cf61..596c7d66da 100644 --- a/docs-devsite/firestore.md +++ b/docs-devsite/firestore.md @@ -16,4 +16,5 @@ Cloud Firestore | --- | --- | | [/](./firestore_.md#@firebase/firestore) | | | [/lite](./firestore_lite.md#@firebase/firestore/lite) | | +| [/pipelines](./firestore_pipelines.md#@firebase/firestore/pipelines) | | diff --git a/docs-devsite/firestore_pipelines.aggregatefunction.md b/docs-devsite/firestore_pipelines.aggregatefunction.md new file mode 100644 index 0000000000..5373c05e5a --- /dev/null +++ b/docs-devsite/firestore_pipelines.aggregatefunction.md @@ -0,0 +1,109 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# AggregateFunction class +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +A class that represents an aggregate function. + +Signature: + +```typescript +export declare class AggregateFunction +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(name, params)](./firestore_pipelines.aggregatefunction.md#aggregatefunctionconstructor) | | (Public Preview) Constructs a new instance of the AggregateFunction class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [exprType](./firestore_pipelines.aggregatefunction.md#aggregatefunctionexprtype) | | [ExpressionType](./firestore_pipelines.md#expressiontype) | (Public Preview) | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [as(name)](./firestore_pipelines.aggregatefunction.md#aggregatefunctionas) | | (Public Preview) Assigns an alias to this AggregateFunction. The alias specifies the name that the aggregated value will have in the output document. +```typescript +// Calculate the average price of all items and assign it the alias "averagePrice". +firestore.pipeline().collection("items") + .aggregate(field("price").average().as("averagePrice")); + +``` + | + +## AggregateFunction.(constructor) + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Constructs a new instance of the `AggregateFunction` class + +Signature: + +```typescript +constructor(name: string, params: Expression[]); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | +| params | [Expression](./firestore_pipelines.expression.md#expression_class)\[\] | | + +## AggregateFunction.exprType + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +exprType: ExpressionType; +``` + +## AggregateFunction.as() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Assigns an alias to this AggregateFunction. The alias specifies the name that the aggregated value will have in the output document. + +```typescript +// Calculate the average price of all items and assign it the alias "averagePrice". +firestore.pipeline().collection("items") + .aggregate(field("price").average().as("averagePrice")); + +``` + +Signature: + +```typescript +as(name: string): AliasedAggregate; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | The alias to assign to this AggregateFunction. A new that wraps this AggregateFunction and associates it with the provided alias. | + +Returns: + +[AliasedAggregate](./firestore_pipelines.aliasedaggregate.md#aliasedaggregate_class) + diff --git a/docs-devsite/firestore_pipelines.aliasedaggregate.md b/docs-devsite/firestore_pipelines.aliasedaggregate.md new file mode 100644 index 0000000000..39826b83b6 --- /dev/null +++ b/docs-devsite/firestore_pipelines.aliasedaggregate.md @@ -0,0 +1,78 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# AliasedAggregate class +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +An AggregateFunction with alias. + +Signature: + +```typescript +export declare class AliasedAggregate +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(aggregate, alias, \_methodName)](./firestore_pipelines.aliasedaggregate.md#aliasedaggregateconstructor) | | (Public Preview) Constructs a new instance of the AliasedAggregate class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [aggregate](./firestore_pipelines.aliasedaggregate.md#aliasedaggregateaggregate) | | [AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) | (Public Preview) | +| [alias](./firestore_pipelines.aliasedaggregate.md#aliasedaggregatealias) | | string | (Public Preview) | + +## AliasedAggregate.(constructor) + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Constructs a new instance of the `AliasedAggregate` class + +Signature: + +```typescript +constructor(aggregate: AggregateFunction, alias: string, _methodName: string | undefined); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| aggregate | [AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) | | +| alias | string | | +| \_methodName | string \| undefined | | + +## AliasedAggregate.aggregate + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +readonly aggregate: AggregateFunction; +``` + +## AliasedAggregate.alias + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +readonly alias: string; +``` diff --git a/docs-devsite/firestore_pipelines.aliasedexpression.md b/docs-devsite/firestore_pipelines.aliasedexpression.md new file mode 100644 index 0000000000..6a602a9e44 --- /dev/null +++ b/docs-devsite/firestore_pipelines.aliasedexpression.md @@ -0,0 +1,102 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# AliasedExpression class +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + + +Signature: + +```typescript +export declare class AliasedExpression implements Selectable +``` +Implements: [Selectable](./firestore_pipelines.selectable.md#selectable_interface) + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(expr, alias, \_methodName)](./firestore_pipelines.aliasedexpression.md#aliasedexpressionconstructor) | | (Public Preview) Constructs a new instance of the AliasedExpression class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [alias](./firestore_pipelines.aliasedexpression.md#aliasedexpressionalias) | | string | (Public Preview) | +| [expr](./firestore_pipelines.aliasedexpression.md#aliasedexpressionexpr) | | [Expression](./firestore_pipelines.expression.md#expression_class) | (Public Preview) | +| [exprType](./firestore_pipelines.aliasedexpression.md#aliasedexpressionexprtype) | | [ExpressionType](./firestore_pipelines.md#expressiontype) | (Public Preview) | +| [selectable](./firestore_pipelines.aliasedexpression.md#aliasedexpressionselectable) | | true | (Public Preview) | + +## AliasedExpression.(constructor) + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Constructs a new instance of the `AliasedExpression` class + +Signature: + +```typescript +constructor(expr: Expression, alias: string, _methodName: string | undefined); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) | | +| alias | string | | +| \_methodName | string \| undefined | | + +## AliasedExpression.alias + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +readonly alias: string; +``` + +## AliasedExpression.expr + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +readonly expr: Expression; +``` + +## AliasedExpression.exprType + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +exprType: ExpressionType; +``` + +## AliasedExpression.selectable + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +selectable: true; +``` diff --git a/docs-devsite/firestore_pipelines.booleanexpression.md b/docs-devsite/firestore_pipelines.booleanexpression.md new file mode 100644 index 0000000000..b4855ea111 --- /dev/null +++ b/docs-devsite/firestore_pipelines.booleanexpression.md @@ -0,0 +1,274 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# BooleanExpression class +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +An interface that represents a filter condition. + +Signature: + +```typescript +export declare abstract class BooleanExpression extends Expression +``` +Extends: [Expression](./firestore_pipelines.expression.md#expression_class) + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [conditional(thenExpr, elseExpr)](./firestore_pipelines.booleanexpression.md#booleanexpressionconditional) | | (Public Preview) Creates a conditional expression that evaluates to the 'then' expression if this expression evaluates to true, or evaluates to the 'else' expression if this expressions evaluates false. +```typescript +// If 'age' is greater than 18, return "Adult"; otherwise, return "Minor". +field("age").greaterThanOrEqual(18).conditional(constant("Adult"), constant("Minor")); + +``` + | +| [countIf()](./firestore_pipelines.booleanexpression.md#booleanexpressioncountif) | | (Public Preview) Creates an aggregation that finds the count of input documents satisfying this boolean expression. +```typescript +// Find the count of documents with a score greater than 90 +field("score").greaterThan(90).countIf().as("highestScore"); + +``` + A new AggregateFunction representing the 'countIf' aggregation. | +| [ifError(catchValue)](./firestore_pipelines.booleanexpression.md#booleanexpressioniferror) | | (Public Preview) Creates an expression that returns the catch argument if there is an error, else return the result of this expression. +```typescript +// Create an expression that protects against a divide by zero error +// but always returns a boolean expression. +constant(50).divide('length').gt(1).ifError(constant(false)); + +``` + | +| [ifError(catchValue)](./firestore_pipelines.booleanexpression.md#booleanexpressioniferror) | | (Public Preview) Creates an expression that returns the catch argument if there is an error, else return the result of this expression. +```typescript +// Create an expression that protects against a divide by zero error +// but always returns a boolean expression. +constant(50).divide('length').gt(1).ifError(false); + +``` + | +| [ifError(catchValue)](./firestore_pipelines.booleanexpression.md#booleanexpressioniferror) | | (Public Preview) Creates an expression that returns the catch argument if there is an error, else return the result of this expression. +```typescript +// Create an expression that protects against a divide by zero error. +constant(50).divide('length').gt(1).ifError(constant(0)); + +``` + | +| [ifError(catchValue)](./firestore_pipelines.booleanexpression.md#booleanexpressioniferror) | | (Public Preview) Creates an expression that returns the catch argument if there is an error, else return the result of this expression. +```typescript +// Create an expression that protects against a divide by zero error. +constant(50).divide('length').gt(1).ifError(0); + +``` + | +| [not()](./firestore_pipelines.booleanexpression.md#booleanexpressionnot) | | (Public Preview) Creates an expression that negates this boolean expression. +```typescript +// Find documents where the 'tags' field does not contain 'completed' +field("tags").arrayContains("completed").not(); + +``` + A new representing the negated filter condition. | + +## BooleanExpression.conditional() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates a conditional expression that evaluates to the 'then' expression if `this` expression evaluates to `true`, or evaluates to the 'else' expression if `this` expressions evaluates `false`. + +```typescript +// If 'age' is greater than 18, return "Adult"; otherwise, return "Minor". +field("age").greaterThanOrEqual(18).conditional(constant("Adult"), constant("Minor")); + +``` + +Signature: + +```typescript +conditional(thenExpr: Expression, elseExpr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| thenExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to evaluate if the condition is true. | +| elseExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to evaluate if the condition is false. A new representing the conditional expression. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## BooleanExpression.countIf() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that finds the count of input documents satisfying this boolean expression. + +```typescript +// Find the count of documents with a score greater than 90 +field("score").greaterThan(90).countIf().as("highestScore"); + +``` + A new `AggregateFunction` representing the 'countIf' aggregation. + +Signature: + +```typescript +countIf(): AggregateFunction; +``` +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +## BooleanExpression.ifError() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the `catch` argument if there is an error, else return the result of this expression. + +```typescript +// Create an expression that protects against a divide by zero error +// but always returns a boolean expression. +constant(50).divide('length').gt(1).ifError(constant(false)); + +``` + +Signature: + +```typescript +ifError(catchValue: BooleanExpression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| catchValue | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) | The value that will be returned if this expression produces an error. A new representing the 'ifError' operation. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## BooleanExpression.ifError() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the `catch` argument if there is an error, else return the result of this expression. + +```typescript +// Create an expression that protects against a divide by zero error +// but always returns a boolean expression. +constant(50).divide('length').gt(1).ifError(false); + +``` + +Signature: + +```typescript +ifError(catchValue: boolean): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| catchValue | boolean | The value that will be returned if this expression produces an error. A new representing the 'ifError' operation. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## BooleanExpression.ifError() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the `catch` argument if there is an error, else return the result of this expression. + +```typescript +// Create an expression that protects against a divide by zero error. +constant(50).divide('length').gt(1).ifError(constant(0)); + +``` + +Signature: + +```typescript +ifError(catchValue: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| catchValue | [Expression](./firestore_pipelines.expression.md#expression_class) | The value that will be returned if this expression produces an error. A new representing the 'ifError' operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## BooleanExpression.ifError() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the `catch` argument if there is an error, else return the result of this expression. + +```typescript +// Create an expression that protects against a divide by zero error. +constant(50).divide('length').gt(1).ifError(0); + +``` + +Signature: + +```typescript +ifError(catchValue: unknown): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| catchValue | unknown | The value that will be returned if this expression produces an error. A new representing the 'ifError' operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## BooleanExpression.not() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that negates this boolean expression. + +```typescript +// Find documents where the 'tags' field does not contain 'completed' +field("tags").arrayContains("completed").not(); + +``` + A new representing the negated filter condition. + +Signature: + +```typescript +not(): BooleanExpression; +``` +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + diff --git a/docs-devsite/firestore_pipelines.bytes.md b/docs-devsite/firestore_pipelines.bytes.md new file mode 100644 index 0000000000..ca7674d511 --- /dev/null +++ b/docs-devsite/firestore_pipelines.bytes.md @@ -0,0 +1,177 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Bytes class +An immutable object representing an array of bytes. + +Signature: + +```typescript +export declare class Bytes +``` + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [fromBase64String(base64)](./firestore_pipelines.bytes.md#bytesfrombase64string) | static | Creates a new Bytes object from the given Base64 string, converting it to bytes. | +| [fromJSON(json)](./firestore_pipelines.bytes.md#bytesfromjson) | static | Builds a Bytes instance from a JSON object created by [Bytes.toJSON()](./firestore_.bytes.md#bytestojson). | +| [fromUint8Array(array)](./firestore_pipelines.bytes.md#bytesfromuint8array) | static | Creates a new Bytes object from the given Uint8Array. | +| [isEqual(other)](./firestore_pipelines.bytes.md#bytesisequal) | | Returns true if this Bytes object is equal to the provided one. | +| [toBase64()](./firestore_pipelines.bytes.md#bytestobase64) | | Returns the underlying bytes as a Base64-encoded string. | +| [toJSON()](./firestore_pipelines.bytes.md#bytestojson) | | Returns a JSON-serializable representation of this Bytes instance. | +| [toString()](./firestore_pipelines.bytes.md#bytestostring) | | Returns a string representation of the Bytes object. | +| [toUint8Array()](./firestore_pipelines.bytes.md#bytestouint8array) | | Returns the underlying bytes in a new Uint8Array. | + +## Bytes.fromBase64String() + +Creates a new `Bytes` object from the given Base64 string, converting it to bytes. + +Signature: + +```typescript +static fromBase64String(base64: string): Bytes; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| base64 | string | The Base64 string used to create the Bytes object. | + +Returns: + +[Bytes](./firestore_.bytes.md#bytes_class) + +## Bytes.fromJSON() + +Builds a `Bytes` instance from a JSON object created by [Bytes.toJSON()](./firestore_.bytes.md#bytestojson). + +Signature: + +```typescript +static fromJSON(json: object): Bytes; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| json | object | a JSON object represention of a Bytes instance | + +Returns: + +[Bytes](./firestore_.bytes.md#bytes_class) + +an instance of [Bytes](./firestore_.bytes.md#bytes_class) if the JSON object could be parsed. Throws a [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class) if an error occurs. + +## Bytes.fromUint8Array() + +Creates a new `Bytes` object from the given Uint8Array. + +Signature: + +```typescript +static fromUint8Array(array: Uint8Array): Bytes; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| array | Uint8Array | The Uint8Array used to create the Bytes object. | + +Returns: + +[Bytes](./firestore_.bytes.md#bytes_class) + +## Bytes.isEqual() + +Returns true if this `Bytes` object is equal to the provided one. + +Signature: + +```typescript +isEqual(other: Bytes): boolean; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| other | [Bytes](./firestore_.bytes.md#bytes_class) | The Bytes object to compare against. | + +Returns: + +boolean + +true if this `Bytes` object is equal to the provided one. + +## Bytes.toBase64() + +Returns the underlying bytes as a Base64-encoded string. + +Signature: + +```typescript +toBase64(): string; +``` +Returns: + +string + +The Base64-encoded string created from the `Bytes` object. + +## Bytes.toJSON() + +Returns a JSON-serializable representation of this `Bytes` instance. + +Signature: + +```typescript +toJSON(): object; +``` +Returns: + +object + +a JSON representation of this object. + +## Bytes.toString() + +Returns a string representation of the `Bytes` object. + +Signature: + +```typescript +toString(): string; +``` +Returns: + +string + +A string representation of the `Bytes` object. + +## Bytes.toUint8Array() + +Returns the underlying bytes in a new `Uint8Array`. + +Signature: + +```typescript +toUint8Array(): Uint8Array; +``` +Returns: + +Uint8Array + +The Uint8Array created from the `Bytes` object. + diff --git a/docs-devsite/firestore_pipelines.collectionreference.md b/docs-devsite/firestore_pipelines.collectionreference.md new file mode 100644 index 0000000000..cc7ea2d553 --- /dev/null +++ b/docs-devsite/firestore_pipelines.collectionreference.md @@ -0,0 +1,121 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# CollectionReference class +A `CollectionReference` object can be used for adding documents, getting document references, and querying for documents (using [query()](./firestore_.md#query_9f7b0f4)). + +Signature: + +```typescript +export declare class CollectionReference extends Query +``` +Extends: [Query](./firestore_.query.md#query_class)<AppModelType, DbModelType> + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [id](./firestore_pipelines.collectionreference.md#collectionreferenceid) | | string | The collection's identifier. | +| [parent](./firestore_pipelines.collectionreference.md#collectionreferenceparent) | | [DocumentReference](./firestore_.documentreference.md#documentreference_class)<[DocumentData](./firestore_.documentdata.md#documentdata_interface), [DocumentData](./firestore_.documentdata.md#documentdata_interface)> \| null | A reference to the containing DocumentReference if this is a subcollection. If this isn't a subcollection, the reference is null. | +| [path](./firestore_pipelines.collectionreference.md#collectionreferencepath) | | string | A string representing the path of the referenced collection (relative to the root of the database). | +| [type](./firestore_pipelines.collectionreference.md#collectionreferencetype) | | (not declared) | The type of this Firestore reference. | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [withConverter(converter)](./firestore_pipelines.collectionreference.md#collectionreferencewithconverter) | | Applies a custom data converter to this CollectionReference, allowing you to use your own custom model objects with Firestore. When you call [addDoc()](./firestore_.md#adddoc_6e783ff) with the returned CollectionReference instance, the provided converter will convert between Firestore data of type NewDbModelType and your custom type NewAppModelType. | +| [withConverter(converter)](./firestore_pipelines.collectionreference.md#collectionreferencewithconverter) | | Removes the current converter. | + +## CollectionReference.id + +The collection's identifier. + +Signature: + +```typescript +get id(): string; +``` + +## CollectionReference.parent + +A reference to the containing `DocumentReference` if this is a subcollection. If this isn't a subcollection, the reference is null. + +Signature: + +```typescript +get parent(): DocumentReference | null; +``` + +## CollectionReference.path + +A string representing the path of the referenced collection (relative to the root of the database). + +Signature: + +```typescript +get path(): string; +``` + +## CollectionReference.type + +The type of this Firestore reference. + +Signature: + +```typescript +readonly type = "collection"; +``` + +## CollectionReference.withConverter() + +Applies a custom data converter to this `CollectionReference`, allowing you to use your own custom model objects with Firestore. When you call [addDoc()](./firestore_.md#adddoc_6e783ff) with the returned `CollectionReference` instance, the provided converter will convert between Firestore data of type `NewDbModelType` and your custom type `NewAppModelType`. + +Signature: + +```typescript +withConverter(converter: FirestoreDataConverter): CollectionReference; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<NewAppModelType, NewDbModelType> | Converts objects to and from Firestore. | + +Returns: + +[CollectionReference](./firestore_.collectionreference.md#collectionreference_class)<NewAppModelType, NewDbModelType> + +A `CollectionReference` that uses the provided converter. + +## CollectionReference.withConverter() + +Removes the current converter. + +Signature: + +```typescript +withConverter(converter: null): CollectionReference; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| converter | null | null removes the current converter. | + +Returns: + +[CollectionReference](./firestore_.collectionreference.md#collectionreference_class)<[DocumentData](./firestore_.documentdata.md#documentdata_interface), [DocumentData](./firestore_.documentdata.md#documentdata_interface)> + +A `CollectionReference` that does not use a converter. + diff --git a/docs-devsite/firestore_pipelines.documentdata.md b/docs-devsite/firestore_pipelines.documentdata.md new file mode 100644 index 0000000000..c93565207a --- /dev/null +++ b/docs-devsite/firestore_pipelines.documentdata.md @@ -0,0 +1,19 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# DocumentData interface +Document data (for use with [setDoc()](./firestore_lite.md#setdoc_ee215ad)) consists of fields mapped to values. + +Signature: + +```typescript +export declare interface DocumentData +``` diff --git a/docs-devsite/firestore_pipelines.documentreference.md b/docs-devsite/firestore_pipelines.documentreference.md new file mode 100644 index 0000000000..246dc92150 --- /dev/null +++ b/docs-devsite/firestore_pipelines.documentreference.md @@ -0,0 +1,207 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# DocumentReference class +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. + +Signature: + +```typescript +export declare class DocumentReference +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [converter](./firestore_pipelines.documentreference.md#documentreferenceconverter) | | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<AppModelType, DbModelType> \| null | If provided, the FirestoreDataConverter associated with this instance. | +| [firestore](./firestore_pipelines.documentreference.md#documentreferencefirestore) | | [Firestore](./firestore_.firestore.md#firestore_class) | The [Firestore](./firestore_.firestore.md#firestore_class) instance the document is in. This is useful for performing transactions, for example. | +| [id](./firestore_pipelines.documentreference.md#documentreferenceid) | | string | The document's identifier within its collection. | +| [parent](./firestore_pipelines.documentreference.md#documentreferenceparent) | | [CollectionReference](./firestore_.collectionreference.md#collectionreference_class)<AppModelType, DbModelType> | The collection this DocumentReference belongs to. | +| [path](./firestore_pipelines.documentreference.md#documentreferencepath) | | string | A string representing the path of the referenced document (relative to the root of the database). | +| [type](./firestore_pipelines.documentreference.md#documentreferencetype) | | (not declared) | The type of this Firestore reference. | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [fromJSON(firestore, json)](./firestore_pipelines.documentreference.md#documentreferencefromjson) | static | Builds a DocumentReference instance from a JSON object created by [DocumentReference.toJSON()](./firestore_.documentreference.md#documentreferencetojson). | +| [fromJSON(firestore, json, converter)](./firestore_pipelines.documentreference.md#documentreferencefromjson) | static | Builds a DocumentReference instance from a JSON object created by [DocumentReference.toJSON()](./firestore_.documentreference.md#documentreferencetojson). | +| [toJSON()](./firestore_pipelines.documentreference.md#documentreferencetojson) | | Returns a JSON-serializable representation of this DocumentReference instance. | +| [withConverter(converter)](./firestore_pipelines.documentreference.md#documentreferencewithconverter) | | Applies a custom data converter to this DocumentReference, allowing you to use your own custom model objects with Firestore. When you call [setDoc()](./firestore_lite.md#setdoc_ee215ad), [getDoc()](./firestore_lite.md#getdoc_4569087), etc. with the returned DocumentReference instance, the provided converter will convert between Firestore data of type NewDbModelType and your custom type NewAppModelType. | +| [withConverter(converter)](./firestore_pipelines.documentreference.md#documentreferencewithconverter) | | Removes the current converter. | + +## DocumentReference.converter + +If provided, the `FirestoreDataConverter` associated with this instance. + +Signature: + +```typescript +readonly converter: FirestoreDataConverter | null; +``` + +## DocumentReference.firestore + +The [Firestore](./firestore_.firestore.md#firestore_class) instance the document is in. This is useful for performing transactions, for example. + +Signature: + +```typescript +readonly firestore: Firestore; +``` + +## DocumentReference.id + +The document's identifier within its collection. + +Signature: + +```typescript +get id(): string; +``` + +## DocumentReference.parent + +The collection this `DocumentReference` belongs to. + +Signature: + +```typescript +get parent(): CollectionReference; +``` + +## DocumentReference.path + +A string representing the path of the referenced document (relative to the root of the database). + +Signature: + +```typescript +get path(): string; +``` + +## DocumentReference.type + +The type of this Firestore reference. + +Signature: + +```typescript +readonly type = "document"; +``` + +## DocumentReference.fromJSON() + +Builds a `DocumentReference` instance from a JSON object created by [DocumentReference.toJSON()](./firestore_.documentreference.md#documentreferencetojson). + +Signature: + +```typescript +static fromJSON(firestore: Firestore, json: object): DocumentReference; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firestore | [Firestore](./firestore_.firestore.md#firestore_class) | The [Firestore](./firestore_.firestore.md#firestore_class) instance the snapshot should be loaded for. | +| json | object | a JSON object represention of a DocumentReference instance | + +Returns: + +[DocumentReference](./firestore_.documentreference.md#documentreference_class) + +an instance of [DocumentReference](./firestore_.documentreference.md#documentreference_class) if the JSON object could be parsed. Throws a [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class) if an error occurs. + +## DocumentReference.fromJSON() + +Builds a `DocumentReference` instance from a JSON object created by [DocumentReference.toJSON()](./firestore_.documentreference.md#documentreferencetojson). + +Signature: + +```typescript +static fromJSON(firestore: Firestore, json: object, converter: FirestoreDataConverter): DocumentReference; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firestore | [Firestore](./firestore_.firestore.md#firestore_class) | The [Firestore](./firestore_.firestore.md#firestore_class) instance the snapshot should be loaded for. | +| json | object | a JSON object represention of a DocumentReference instance | +| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<NewAppModelType, NewDbModelType> | Converts objects to and from Firestore. | + +Returns: + +[DocumentReference](./firestore_.documentreference.md#documentreference_class)<NewAppModelType, NewDbModelType> + +an instance of [DocumentReference](./firestore_.documentreference.md#documentreference_class) if the JSON object could be parsed. Throws a [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class) if an error occurs. + +## DocumentReference.toJSON() + +Returns a JSON-serializable representation of this `DocumentReference` instance. + +Signature: + +```typescript +toJSON(): object; +``` +Returns: + +object + +a JSON representation of this object. + +## DocumentReference.withConverter() + +Applies a custom data converter to this `DocumentReference`, allowing you to use your own custom model objects with Firestore. When you call [setDoc()](./firestore_lite.md#setdoc_ee215ad), [getDoc()](./firestore_lite.md#getdoc_4569087), etc. with the returned `DocumentReference` instance, the provided converter will convert between Firestore data of type `NewDbModelType` and your custom type `NewAppModelType`. + +Signature: + +```typescript +withConverter(converter: FirestoreDataConverter): DocumentReference; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<NewAppModelType, NewDbModelType> | Converts objects to and from Firestore. | + +Returns: + +[DocumentReference](./firestore_.documentreference.md#documentreference_class)<NewAppModelType, NewDbModelType> + +A `DocumentReference` that uses the provided converter. + +## DocumentReference.withConverter() + +Removes the current converter. + +Signature: + +```typescript +withConverter(converter: null): DocumentReference; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| converter | null | null removes the current converter. | + +Returns: + +[DocumentReference](./firestore_.documentreference.md#documentreference_class)<[DocumentData](./firestore_.documentdata.md#documentdata_interface), [DocumentData](./firestore_.documentdata.md#documentdata_interface)> + +A `DocumentReference` that does not use a converter. + diff --git a/docs-devsite/firestore_pipelines.documentsnapshot.md b/docs-devsite/firestore_pipelines.documentsnapshot.md new file mode 100644 index 0000000000..59752e41fc --- /dev/null +++ b/docs-devsite/firestore_pipelines.documentsnapshot.md @@ -0,0 +1,162 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# DocumentSnapshot class +A `DocumentSnapshot` contains data read from a document in your Firestore database. The data can be extracted with `.data()` or `.get()` to get a specific field. + +For a `DocumentSnapshot` that points to a non-existing document, any data access will return 'undefined'. You can use the `exists()` method to explicitly verify a document's existence. + +Signature: + +```typescript +export declare class DocumentSnapshot +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)()](./firestore_pipelines.documentsnapshot.md#documentsnapshotconstructor) | | Constructs a new instance of the DocumentSnapshot class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [id](./firestore_pipelines.documentsnapshot.md#documentsnapshotid) | | string | Property of the DocumentSnapshot that provides the document's ID. | +| [metadata](./firestore_pipelines.documentsnapshot.md#documentsnapshotmetadata) | | [SnapshotMetadata](./firestore_.snapshotmetadata.md#snapshotmetadata_class) | Metadata about the DocumentSnapshot, including information about its source and local modifications. | +| [ref](./firestore_pipelines.documentsnapshot.md#documentsnapshotref) | | [DocumentReference](./firestore_.documentreference.md#documentreference_class)<AppModelType, DbModelType> | The DocumentReference for the document included in the DocumentSnapshot. | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [data(options)](./firestore_pipelines.documentsnapshot.md#documentsnapshotdata) | | Retrieves all fields in the document as an Object. Returns undefined if the document doesn't exist.By default, serverTimestamp() values that have not yet been set to their final value will be returned as null. You can override this by passing an options object. | +| [exists()](./firestore_pipelines.documentsnapshot.md#documentsnapshotexists) | | Returns whether or not the data exists. True if the document exists. | +| [get(fieldPath, options)](./firestore_pipelines.documentsnapshot.md#documentsnapshotget) | | Retrieves the field specified by fieldPath. Returns undefined if the document or field doesn't exist.By default, a serverTimestamp() that has not yet been set to its final value will be returned as null. You can override this by passing an options object. | +| [toJSON()](./firestore_pipelines.documentsnapshot.md#documentsnapshottojson) | | Returns a JSON-serializable representation of this DocumentSnapshot instance. | + +## DocumentSnapshot.(constructor) + +Constructs a new instance of the `DocumentSnapshot` class + +Signature: + +```typescript +protected constructor(); +``` + +## DocumentSnapshot.id + +Property of the `DocumentSnapshot` that provides the document's ID. + +Signature: + +```typescript +get id(): string; +``` + +## DocumentSnapshot.metadata + +Metadata about the `DocumentSnapshot`, including information about its source and local modifications. + +Signature: + +```typescript +readonly metadata: SnapshotMetadata; +``` + +## DocumentSnapshot.ref + +The `DocumentReference` for the document included in the `DocumentSnapshot`. + +Signature: + +```typescript +get ref(): DocumentReference; +``` + +## DocumentSnapshot.data() + +Retrieves all fields in the document as an `Object`. Returns `undefined` if the document doesn't exist. + +By default, `serverTimestamp()` values that have not yet been set to their final value will be returned as `null`. You can override this by passing an options object. + +Signature: + +```typescript +data(options?: SnapshotOptions): AppModelType | undefined; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [SnapshotOptions](./firestore_.snapshotoptions.md#snapshotoptions_interface) | An options object to configure how data is retrieved from the snapshot (for example the desired behavior for server timestamps that have not yet been set to their final value). | + +Returns: + +AppModelType \| undefined + +An `Object` containing all fields in the document or `undefined` if the document doesn't exist. + +## DocumentSnapshot.exists() + +Returns whether or not the data exists. True if the document exists. + +Signature: + +```typescript +exists(): this is QueryDocumentSnapshot; +``` +Returns: + +this is [QueryDocumentSnapshot](./firestore_.querydocumentsnapshot.md#querydocumentsnapshot_class)<AppModelType, DbModelType> + +## DocumentSnapshot.get() + +Retrieves the field specified by `fieldPath`. Returns `undefined` if the document or field doesn't exist. + +By default, a `serverTimestamp()` that has not yet been set to its final value will be returned as `null`. You can override this by passing an options object. + +Signature: + +```typescript +get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldPath | string \| [FieldPath](./firestore_.fieldpath.md#fieldpath_class) | The path (for example 'foo' or 'foo.bar') to a specific field. | +| options | [SnapshotOptions](./firestore_.snapshotoptions.md#snapshotoptions_interface) | An options object to configure how the field is retrieved from the snapshot (for example the desired behavior for server timestamps that have not yet been set to their final value). | + +Returns: + +any + +The data at the specified field location or undefined if no such field exists in the document. + +## DocumentSnapshot.toJSON() + +Returns a JSON-serializable representation of this `DocumentSnapshot` instance. + +Signature: + +```typescript +toJSON(): object; +``` +Returns: + +object + +a JSON representation of this object. Throws a [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class) if this `DocumentSnapshot` has pending writes. + diff --git a/docs-devsite/firestore_pipelines.expression.md b/docs-devsite/firestore_pipelines.expression.md new file mode 100644 index 0000000000..c082bbfd5f --- /dev/null +++ b/docs-devsite/firestore_pipelines.expression.md @@ -0,0 +1,4173 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Expression class +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Represents an expression that can be evaluated to a value within the execution of a . + +Expressions are the building blocks for creating complex queries and transformations in Firestore pipelines. They can represent: + +- \*\*Field references:\*\* Access values from document fields. - \*\*Literals:\*\* Represent constant values (strings, numbers, booleans). - \*\*Function calls:\*\* Apply functions to one or more expressions. + +The `Expr` class provides a fluent API for building expressions. You can chain together method calls to create complex expressions. + +Signature: + +```typescript +export declare abstract class Expression +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [expressionType](./firestore_pipelines.expression.md#expressionexpressiontype) | | [ExpressionType](./firestore_pipelines.md#expressiontype) | (Public Preview) | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [abs()](./firestore_pipelines.expression.md#expressionabs) | | (Public Preview) Creates an expression that computes the absolute value of a numeric value. +```typescript +// Compute the absolute value of the 'price' field. +field("price").abs(); + +``` + A new representing the absolute value of the numeric value. | +| [add(second)](./firestore_pipelines.expression.md#expressionadd) | | (Public Preview) Creates an expression that adds this expression to another expression. +```typescript +// Add the value of the 'quantity' field and the 'reserve' field. +field("quantity").add(field("reserve")); + +``` + | +| [arrayConcat(secondArray, otherArrays)](./firestore_pipelines.expression.md#expressionarrayconcat) | | (Public Preview) Creates an expression that concatenates an array expression with one or more other arrays. +```typescript +// Combine the 'items' array with another array field. +field("items").arrayConcat(field("otherItems")); + +``` + | +| [arrayContains(expression)](./firestore_pipelines.expression.md#expressionarraycontains) | | (Public Preview) Creates an expression that checks if an array contains a specific element. +```typescript +// Check if the 'sizes' array contains the value from the 'selectedSize' field +field("sizes").arrayContains(field("selectedSize")); + +``` + | +| [arrayContains(value)](./firestore_pipelines.expression.md#expressionarraycontains) | | (Public Preview) Creates an expression that checks if an array contains a specific value. +```typescript +// Check if the 'colors' array contains "red" +field("colors").arrayContains("red"); + +``` + | +| [arrayContainsAll(values)](./firestore_pipelines.expression.md#expressionarraycontainsall) | | (Public Preview) Creates an expression that checks if an array contains all the specified elements. +```typescript +// Check if the 'tags' array contains both the value in field "tag1" and the literal value "tag2" +field("tags").arrayContainsAll([field("tag1"), "tag2"]); + +``` + | +| [arrayContainsAll(arrayExpression)](./firestore_pipelines.expression.md#expressionarraycontainsall) | | (Public Preview) Creates an expression that checks if an array contains all the specified elements. +```typescript +// Check if the 'tags' array contains both of the values from field "tag1" and the literal value "tag2" +field("tags").arrayContainsAll(array([field("tag1"), "tag2"])); + +``` + | +| [arrayContainsAny(values)](./firestore_pipelines.expression.md#expressionarraycontainsany) | | (Public Preview) Creates an expression that checks if an array contains any of the specified elements. +```typescript +// Check if the 'categories' array contains either values from field "cate1" or "cate2" +field("categories").arrayContainsAny([field("cate1"), field("cate2")]); + +``` + | +| [arrayContainsAny(arrayExpression)](./firestore_pipelines.expression.md#expressionarraycontainsany) | | (Public Preview) Creates an expression that checks if an array contains any of the specified elements. +```typescript +// Check if the 'groups' array contains either the value from the 'userGroup' field +// or the value "guest" +field("groups").arrayContainsAny(array([field("userGroup"), "guest"])); + +``` + | +| [arrayGet(offset)](./firestore_pipelines.expression.md#expressionarrayget) | | (Public Preview) Creates an expression that indexes into an array from the beginning or end and returns the element. If the offset exceeds the array length, an error is returned. A negative offset, starts from the end. +```typescript +// Return the value in the 'tags' field array at index `1`. +field('tags').arrayGet(1); + +``` + | +| [arrayGet(offsetExpr)](./firestore_pipelines.expression.md#expressionarrayget) | | (Public Preview) Creates an expression that indexes into an array from the beginning or end and returns the element. If the offset exceeds the array length, an error is returned. A negative offset, starts from the end. +```typescript +// Return the value in the tags field array at index specified by field +// 'favoriteTag'. +field('tags').arrayGet(field('favoriteTag')); + +``` + | +| [arrayLength()](./firestore_pipelines.expression.md#expressionarraylength) | | (Public Preview) Creates an expression that calculates the length of an array. +```typescript +// Get the number of items in the 'cart' array +field("cart").arrayLength(); + +``` + A new Expr representing the length of the array. | +| [arrayReverse()](./firestore_pipelines.expression.md#expressionarrayreverse) | | (Public Preview) Creates an expression that reverses an array. +```typescript +// Reverse the value of the 'myArray' field. +field("myArray").arrayReverse(); + +``` + A new representing the reversed array. | +| [arraySum()](./firestore_pipelines.expression.md#expressionarraysum) | | (Public Preview) Creates an expression that computes the sum of the elements in an array. +```typescript +// Compute the sum of the elements in the 'scores' field. +field("scores").arraySum(); + +``` + A new representing the sum of the elements in the array. | +| [as(name)](./firestore_pipelines.expression.md#expressionas) | | (Public Preview) Assigns an alias to this expression.Aliases are useful for renaming fields in the output of a stage or for giving meaningful names to calculated values. +```typescript +// Calculate the total price and assign it the alias "totalPrice" and add it to the output. +firestore.pipeline().collection("items") + .addFields(field("price").multiply(field("quantity")).as("totalPrice")); + +``` + | +| [asBoolean()](./firestore_pipelines.expression.md#expressionasboolean) | | (Public Preview) Wraps the expression in a \[BooleanExpression\]. A \[BooleanExpression\] representing the same expression. | +| [ascending()](./firestore_pipelines.expression.md#expressionascending) | | (Public Preview) Creates an that sorts documents in ascending order based on this expression. +```typescript +// Sort documents by the 'name' field in ascending order +pipeline().collection("users") + .sort(field("name").ascending()); + +``` + A new Ordering for ascending sorting. | +| [average()](./firestore_pipelines.expression.md#expressionaverage) | | (Public Preview) Creates an aggregation that calculates the average (mean) of a numeric field across multiple stage inputs. +```typescript +// Calculate the average age of users +field("age").average().as("averageAge"); + +``` + A new AggregateFunction representing the 'average' aggregation. | +| [byteLength()](./firestore_pipelines.expression.md#expressionbytelength) | | (Public Preview) Creates an expression that calculates the length of this string expression in bytes. +```typescript +// Calculate the length of the 'myString' field in bytes. +field("myString").byteLength(); + +``` + A new representing the length of the string in bytes. | +| [ceil()](./firestore_pipelines.expression.md#expressionceil) | | (Public Preview) Creates an expression that computes the ceiling of a numeric value. +```typescript +// Compute the ceiling of the 'price' field. +field("price").ceil(); + +``` + A new representing the ceiling of the numeric value. | +| [charLength()](./firestore_pipelines.expression.md#expressioncharlength) | | (Public Preview) Creates an expression that calculates the character length of a string in UTF-8. +```typescript +// Get the character length of the 'name' field in its UTF-8 form. +field("name").charLength(); + +``` + A new Expr representing the length of the string. | +| [collectionId()](./firestore_pipelines.expression.md#expressioncollectionid) | | (Public Preview) Creates an expression that returns the collection ID from a path. +```typescript +// Get the collection ID from a path. +field("__path__").collectionId(); + +``` + A new representing the collectionId operation. | +| [concat(second, others)](./firestore_pipelines.expression.md#expressionconcat) | | (Public Preview) Creates an expression that concatenates expression results together. +```typescript +// Combine the 'firstName', ' ', and 'lastName' fields into a single value. +field("firstName").concat(constant(" "), field("lastName")); + +``` + | +| [cosineDistance(vectorExpression)](./firestore_pipelines.expression.md#expressioncosinedistance) | | (Public Preview) Calculates the cosine distance between two vectors. +```typescript +// Calculate the cosine distance between the 'userVector' field and the 'itemVector' field +field("userVector").cosineDistance(field("itemVector")); + +``` + | +| [cosineDistance(vector)](./firestore_pipelines.expression.md#expressioncosinedistance) | | (Public Preview) Calculates the Cosine distance between two vectors. +```typescript +// Calculate the Cosine distance between the 'location' field and a target location +field("location").cosineDistance(new VectorValue([37.7749, -122.4194])); + +``` + | +| [count()](./firestore_pipelines.expression.md#expressioncount) | | (Public Preview) Creates an aggregation that counts the number of stage inputs with valid evaluations of the expression or field. +```typescript +// Count the total number of products +field("productId").count().as("totalProducts"); + +``` + A new AggregateFunction representing the 'count' aggregation. | +| [countDistinct()](./firestore_pipelines.expression.md#expressioncountdistinct) | | (Public Preview) Creates an aggregation that counts the number of distinct values of the expression or field. +```typescript +// Count the distinct number of products +field("productId").countDistinct().as("distinctProducts"); + +``` + A new AggregateFunction representing the 'count\_distinct' aggregation. | +| [descending()](./firestore_pipelines.expression.md#expressiondescending) | | (Public Preview) Creates an that sorts documents in descending order based on this expression. +```typescript +// Sort documents by the 'createdAt' field in descending order +firestore.pipeline().collection("users") + .sort(field("createdAt").descending()); + +``` + A new Ordering for descending sorting. | +| [divide(divisor)](./firestore_pipelines.expression.md#expressiondivide) | | (Public Preview) Creates an expression that divides this expression by another expression. +```typescript +// Divide the 'total' field by the 'count' field +field("total").divide(field("count")); + +``` + | +| [divide(divisor)](./firestore_pipelines.expression.md#expressiondivide) | | (Public Preview) Creates an expression that divides this expression by a constant value. +```typescript +// Divide the 'value' field by 10 +field("value").divide(10); + +``` + | +| [documentId()](./firestore_pipelines.expression.md#expressiondocumentid) | | (Public Preview) Creates an expression that returns the document ID from a path. +```typescript +// Get the document ID from a path. +field("__path__").documentId(); + +``` + A new representing the documentId operation. | +| [dotProduct(vectorExpression)](./firestore_pipelines.expression.md#expressiondotproduct) | | (Public Preview) Calculates the dot product between two vectors. +```typescript +// Calculate the dot product between a feature vector and a target vector +field("features").dotProduct([0.5, 0.8, 0.2]); + +``` + | +| [dotProduct(vector)](./firestore_pipelines.expression.md#expressiondotproduct) | | (Public Preview) Calculates the dot product between two vectors. +```typescript +// Calculate the dot product between a feature vector and a target vector +field("features").dotProduct(new VectorValue([0.5, 0.8, 0.2])); + +``` + | +| [endsWith(suffix)](./firestore_pipelines.expression.md#expressionendswith) | | (Public Preview) Creates an expression that checks if a string ends with a given postfix. +```typescript +// Check if the 'filename' field ends with ".txt" +field("filename").endsWith(".txt"); + +``` + | +| [endsWith(suffix)](./firestore_pipelines.expression.md#expressionendswith) | | (Public Preview) Creates an expression that checks if a string ends with a given postfix (represented as an expression). +```typescript +// Check if the 'url' field ends with the value of the 'extension' field +field("url").endsWith(field("extension")); + +``` + | +| [equal(expression)](./firestore_pipelines.expression.md#expressionequal) | | (Public Preview) Creates an expression that checks if this expression is equal to another expression. +```typescript +// Check if the 'age' field is equal to 21 +field("age").equal(21); + +``` + | +| [equal(value)](./firestore_pipelines.expression.md#expressionequal) | | (Public Preview) Creates an expression that checks if this expression is equal to a constant value. +```typescript +// Check if the 'city' field is equal to "London" +field("city").equal("London"); + +``` + | +| [equalAny(values)](./firestore_pipelines.expression.md#expressionequalany) | | (Public Preview) Creates an expression that checks if this expression is equal to any of the provided values or expressions. +```typescript +// Check if the 'category' field is either "Electronics" or value of field 'primaryType' +field("category").equalAny("Electronics", field("primaryType")); + +``` + | +| [equalAny(arrayExpression)](./firestore_pipelines.expression.md#expressionequalany) | | (Public Preview) Creates an expression that checks if this expression is equal to any of the provided values or expressions. +```typescript +// Check if the 'category' field is either "Electronics" or value of field 'primaryType' +field("category").equalAny(array(["Electronics", field("primaryType")])); + +``` + | +| [euclideanDistance(vectorExpression)](./firestore_pipelines.expression.md#expressioneuclideandistance) | | (Public Preview) Calculates the Euclidean distance between two vectors. +```typescript +// Calculate the Euclidean distance between the 'location' field and a target location +field("location").euclideanDistance([37.7749, -122.4194]); + +``` + | +| [euclideanDistance(vector)](./firestore_pipelines.expression.md#expressioneuclideandistance) | | (Public Preview) Calculates the Euclidean distance between two vectors. +```typescript +// Calculate the Euclidean distance between the 'location' field and a target location +field("location").euclideanDistance(new VectorValue([37.7749, -122.4194])); + +``` + | +| [exists()](./firestore_pipelines.expression.md#expressionexists) | | (Public Preview) Creates an expression that checks if a field exists in the document. +```typescript +// Check if the document has a field named "phoneNumber" +field("phoneNumber").exists(); + +``` + A new Expr representing the 'exists' check. | +| [exp()](./firestore_pipelines.expression.md#expressionexp) | | (Public Preview) Creates an expression that computes e to the power of this expression. +```typescript +// Compute e to the power of the 'value' field. +field("value").exp(); + +``` + A new representing the exp of the numeric value. | +| [floor()](./firestore_pipelines.expression.md#expressionfloor) | | (Public Preview) Creates an expression that computes the floor of a numeric value. +```typescript +// Compute the floor of the 'price' field. +field("price").floor(); + +``` + A new representing the floor of the numeric value. | +| [greaterThan(expression)](./firestore_pipelines.expression.md#expressiongreaterthan) | | (Public Preview) Creates an expression that checks if this expression is greater than another expression. +```typescript +// Check if the 'age' field is greater than the 'limit' field +field("age").greaterThan(field("limit")); + +``` + | +| [greaterThan(value)](./firestore_pipelines.expression.md#expressiongreaterthan) | | (Public Preview) Creates an expression that checks if this expression is greater than a constant value. +```typescript +// Check if the 'price' field is greater than 100 +field("price").greaterThan(100); + +``` + | +| [greaterThanOrEqual(expression)](./firestore_pipelines.expression.md#expressiongreaterthanorequal) | | (Public Preview) Creates an expression that checks if this expression is greater than or equal to another expression. +```typescript +// Check if the 'quantity' field is greater than or equal to field 'requirement' plus 1 +field("quantity").greaterThanOrEqual(field('requirement').add(1)); + +``` + | +| [greaterThanOrEqual(value)](./firestore_pipelines.expression.md#expressiongreaterthanorequal) | | (Public Preview) Creates an expression that checks if this expression is greater than or equal to a constant value. +```typescript +// Check if the 'score' field is greater than or equal to 80 +field("score").greaterThanOrEqual(80); + +``` + | +| [ifAbsent(elseValue)](./firestore_pipelines.expression.md#expressionifabsent) | | (Public Preview) Creates an expression that returns the elseValue argument if this expression results in an absent value, else return the result of the this expression evaluation. +```typescript +// Returns the value of the optional field 'optional_field', or returns 'default_value' +// if the field is absent. +field("optional_field").ifAbsent("default_value") + +``` + | +| [ifAbsent(elseExpression)](./firestore_pipelines.expression.md#expressionifabsent) | | (Public Preview) Creates an expression that returns the elseValue argument if this expression results in an absent value, else return the result of this expression evaluation. +```typescript +// Returns the value of the optional field 'optional_field', or if that is +// absent, then returns the value of the field ` +field("optional_field").ifAbsent(field('default_field')) + +``` + | +| [ifError(catchExpr)](./firestore_pipelines.expression.md#expressioniferror) | | (Public Preview) Creates an expression that returns the result of the catchExpr argument if there is an error, else return the result of this expression. +```typescript +// Returns the first item in the title field arrays, or returns +// the entire title field if the array is empty or the field is another type. +field("title").arrayGet(0).ifError(field("title")); + +``` + | +| [ifError(catchValue)](./firestore_pipelines.expression.md#expressioniferror) | | (Public Preview) Creates an expression that returns the catch argument if there is an error, else return the result of this expression. +```typescript +// Returns the first item in the title field arrays, or returns +// "Default Title" +field("title").arrayGet(0).ifError("Default Title"); + +``` + | +| [isAbsent()](./firestore_pipelines.expression.md#expressionisabsent) | | (Public Preview) Creates an expression that returns true if the result of this expression is absent. Otherwise, returns false even if the value is null. +```typescript +// Check if the field `value` is absent. +field("value").isAbsent(); + +``` + A new representing the 'isAbsent' check. | +| [isError()](./firestore_pipelines.expression.md#expressioniserror) | | (Public Preview) Creates an expression that checks if a given expression produces an error. +```typescript +// Check if the result of a calculation is an error +field("title").arrayContains(1).isError(); + +``` + A new representing the 'isError' check. | +| [join(delimiterExpression)](./firestore_pipelines.expression.md#expressionjoin) | | (Public Preview) Creates an expression that joins the elements of an array into a string. +```typescript +// Join the elements of the 'tags' field with the delimiter from the 'separator' field. +field("tags").join(field("separator")) + +``` + | +| [join(delimiter)](./firestore_pipelines.expression.md#expressionjoin) | | (Public Preview) Creates an expression that joins the elements of an array field into a string. +```typescript +// Join the elements of the 'tags' field with a comma and space. +field("tags").join(", ") + +``` + | +| [length()](./firestore_pipelines.expression.md#expressionlength) | | (Public Preview) Creates an expression that calculates the length of a string, array, map, vector, or bytes. +```typescript +// Get the length of the 'name' field. +field("name").length(); + +// Get the number of items in the 'cart' array. +field("cart").length(); + +``` + A new Expr representing the length of the string, array, map, vector, or bytes. | +| [lessThan(experession)](./firestore_pipelines.expression.md#expressionlessthan) | | (Public Preview) Creates an expression that checks if this expression is less than another expression. +```typescript +// Check if the 'age' field is less than 'limit' +field("age").lessThan(field('limit')); + +``` + | +| [lessThan(value)](./firestore_pipelines.expression.md#expressionlessthan) | | (Public Preview) Creates an expression that checks if this expression is less than a constant value. +```typescript +// Check if the 'price' field is less than 50 +field("price").lessThan(50); + +``` + | +| [lessThanOrEqual(expression)](./firestore_pipelines.expression.md#expressionlessthanorequal) | | (Public Preview) Creates an expression that checks if this expression is less than or equal to another expression. +```typescript +// Check if the 'quantity' field is less than or equal to 20 +field("quantity").lessThan(constant(20)); + +``` + | +| [lessThanOrEqual(value)](./firestore_pipelines.expression.md#expressionlessthanorequal) | | (Public Preview) Creates an expression that checks if this expression is less than or equal to a constant value. +```typescript +// Check if the 'score' field is less than or equal to 70 +field("score").lessThan(70); + +``` + | +| [like(pattern)](./firestore_pipelines.expression.md#expressionlike) | | (Public Preview) Creates an expression that performs a case-sensitive string comparison. +```typescript +// Check if the 'title' field contains the word "guide" (case-sensitive) +field("title").like("%guide%"); + +``` + | +| [like(pattern)](./firestore_pipelines.expression.md#expressionlike) | | (Public Preview) Creates an expression that performs a case-sensitive string comparison. +```typescript +// Check if the 'title' field contains the word "guide" (case-sensitive) +field("title").like("%guide%"); + +``` + | +| [ln()](./firestore_pipelines.expression.md#expressionln) | | (Public Preview) Creates an expression that computes the natural logarithm of a numeric value. +```typescript +// Compute the natural logarithm of the 'value' field. +field("value").ln(); + +``` + A new representing the natural logarithm of the numeric value. | +| [log10()](./firestore_pipelines.expression.md#expressionlog10) | | (Public Preview) Creates an expression that computes the base-10 logarithm of a numeric value. +```typescript +// Compute the base-10 logarithm of the 'value' field. +field("value").log10(); + +``` + A new representing the base-10 logarithm of the numeric value. | +| [logicalMaximum(second, others)](./firestore_pipelines.expression.md#expressionlogicalmaximum) | | (Public Preview) Creates an expression that returns the larger value between this expression and another expression, based on Firestore's value type ordering. +```typescript +// Returns the larger value between the 'timestamp' field and the current timestamp. +field("timestamp").logicalMaximum(Function.currentTimestamp()); + +``` + | +| [logicalMinimum(second, others)](./firestore_pipelines.expression.md#expressionlogicalminimum) | | (Public Preview) Creates an expression that returns the smaller value between this expression and another expression, based on Firestore's value type ordering. +```typescript +// Returns the smaller value between the 'timestamp' field and the current timestamp. +field("timestamp").logicalMinimum(Function.currentTimestamp()); + +``` + | +| [mapGet(subfield)](./firestore_pipelines.expression.md#expressionmapget) | | (Public Preview) Accesses a value from a map (object) field using the provided key. +```typescript +// Get the 'city' value from the 'address' map field +field("address").mapGet("city"); + +``` + | +| [mapMerge(secondMap, otherMaps)](./firestore_pipelines.expression.md#expressionmapmerge) | | (Public Preview) Creates an expression that merges multiple map values. +``` +// Merges the map in the settings field with, a map literal, and a map in +// that is conditionally returned by another expression +field('settings').mapMerge({ enabled: true }, conditional(field('isAdmin'), { admin: true}, {}) + +``` + | +| [mapRemove(key)](./firestore_pipelines.expression.md#expressionmapremove) | | (Public Preview) Creates an expression that removes a key from the map produced by evaluating this expression. +``` +// Removes the key 'baz' from the input map. +map({foo: 'bar', baz: true}).mapRemove('baz'); + +``` + | +| [mapRemove(keyExpr)](./firestore_pipelines.expression.md#expressionmapremove) | | (Public Preview) Creates an expression that removes a key from the map produced by evaluating this expression. +``` +// Removes the key 'baz' from the input map. +map({foo: 'bar', baz: true}).mapRemove(constant('baz')); + +``` + | +| [maximum()](./firestore_pipelines.expression.md#expressionmaximum) | | (Public Preview) Creates an aggregation that finds the maximum value of a field across multiple stage inputs. +```typescript +// Find the highest score in a leaderboard +field("score").maximum().as("highestScore"); + +``` + A new AggregateFunction representing the 'maximum' aggregation. | +| [minimum()](./firestore_pipelines.expression.md#expressionminimum) | | (Public Preview) Creates an aggregation that finds the minimum value of a field across multiple stage inputs. +```typescript +// Find the lowest price of all products +field("price").minimum().as("lowestPrice"); + +``` + A new AggregateFunction representing the 'minimum' aggregation. | +| [mod(expression)](./firestore_pipelines.expression.md#expressionmod) | | (Public Preview) Creates an expression that calculates the modulo (remainder) of dividing this expression by another expression. +```typescript +// Calculate the remainder of dividing the 'value' field by the 'divisor' field +field("value").mod(field("divisor")); + +``` + | +| [mod(value)](./firestore_pipelines.expression.md#expressionmod) | | (Public Preview) Creates an expression that calculates the modulo (remainder) of dividing this expression by a constant value. +```typescript +// Calculate the remainder of dividing the 'value' field by 10 +field("value").mod(10); + +``` + | +| [multiply(second)](./firestore_pipelines.expression.md#expressionmultiply) | | (Public Preview) Creates an expression that multiplies this expression by another expression. +```typescript +// Multiply the 'quantity' field by the 'price' field +field("quantity").multiply(field("price")); + +``` + | +| [notEqual(expression)](./firestore_pipelines.expression.md#expressionnotequal) | | (Public Preview) Creates an expression that checks if this expression is not equal to another expression. +```typescript +// Check if the 'status' field is not equal to "completed" +field("status").notEqual("completed"); + +``` + | +| [notEqual(value)](./firestore_pipelines.expression.md#expressionnotequal) | | (Public Preview) Creates an expression that checks if this expression is not equal to a constant value. +```typescript +// Check if the 'country' field is not equal to "USA" +field("country").notEqual("USA"); + +``` + | +| [notEqualAny(values)](./firestore_pipelines.expression.md#expressionnotequalany) | | (Public Preview) Creates an expression that checks if this expression is not equal to any of the provided values or expressions. +```typescript +// Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' +field("status").notEqualAny(["pending", field("rejectedStatus")]); + +``` + | +| [notEqualAny(arrayExpression)](./firestore_pipelines.expression.md#expressionnotequalany) | | (Public Preview) Creates an expression that checks if this expression is not equal to any of the values in the evaluated expression. +```typescript +// Check if the 'status' field is not equal to any value in the field 'rejectedStatuses' +field("status").notEqualAny(field('rejectedStatuses')); + +``` + | +| [pow(exponent)](./firestore_pipelines.expression.md#expressionpow) | | (Public Preview) Creates an expression that returns the value of this expression raised to the power of another expression. +```typescript +// Raise the value of the 'base' field to the power of the 'exponent' field. +field("base").pow(field("exponent")); + +``` + | +| [pow(exponent)](./firestore_pipelines.expression.md#expressionpow) | | (Public Preview) Creates an expression that returns the value of this expression raised to the power of a constant value. +```typescript +// Raise the value of the 'base' field to the power of 2. +field("base").pow(2); + +``` + | +| [regexContains(pattern)](./firestore_pipelines.expression.md#expressionregexcontains) | | (Public Preview) Creates an expression that checks if a string contains a specified regular expression as a substring. +```typescript +// Check if the 'description' field contains "example" (case-insensitive) +field("description").regexContains("(?i)example"); + +``` + | +| [regexContains(pattern)](./firestore_pipelines.expression.md#expressionregexcontains) | | (Public Preview) Creates an expression that checks if a string contains a specified regular expression as a substring. +```typescript +// Check if the 'description' field contains the regular expression stored in field 'regex' +field("description").regexContains(field("regex")); + +``` + | +| [regexMatch(pattern)](./firestore_pipelines.expression.md#expressionregexmatch) | | (Public Preview) Creates an expression that checks if a string matches a specified regular expression. +```typescript +// Check if the 'email' field matches a valid email pattern +field("email").regexMatch("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"); + +``` + | +| [regexMatch(pattern)](./firestore_pipelines.expression.md#expressionregexmatch) | | (Public Preview) Creates an expression that checks if a string matches a specified regular expression. +```typescript +// Check if the 'email' field matches a regular expression stored in field 'regex' +field("email").regexMatch(field("regex")); + +``` + | +| [reverse()](./firestore_pipelines.expression.md#expressionreverse) | | (Public Preview) Creates an expression that reverses this string expression. +```typescript +// Reverse the value of the 'myString' field. +field("myString").reverse(); + +``` + A new representing the reversed string. | +| [round()](./firestore_pipelines.expression.md#expressionround) | | (Public Preview) Creates an expression that rounds a numeric value to the nearest whole number. +```typescript +// Round the value of the 'price' field. +field("price").round(); + +``` + A new Expr representing the rounded value. | +| [round(decimalPlaces)](./firestore_pipelines.expression.md#expressionround) | | (Public Preview) Creates an expression that rounds a numeric value to the specified number of decimal places. +```typescript +// Round the value of the 'price' field to two decimal places. +field("price").round(2); + +``` + | +| [round(decimalPlaces)](./firestore_pipelines.expression.md#expressionround) | | (Public Preview) Creates an expression that rounds a numeric value to the specified number of decimal places. +```typescript +// Round the value of the 'price' field to two decimal places. +field("price").round(constant(2)); + +``` + | +| [split(delimiter)](./firestore_pipelines.expression.md#expressionsplit) | | (Public Preview) Creates an expression that splits the result of this expression into an array of substrings based on the provided delimiter. | +| [split(delimiter)](./firestore_pipelines.expression.md#expressionsplit) | | (Public Preview) Creates an expression that splits the result of this expression into an array of substrings based on the provided delimiter. | +| [sqrt()](./firestore_pipelines.expression.md#expressionsqrt) | | (Public Preview) Creates an expression that computes the square root of a numeric value. +```typescript +// Compute the square root of the 'value' field. +field("value").sqrt(); + +``` + A new representing the square root of the numeric value. | +| [startsWith(prefix)](./firestore_pipelines.expression.md#expressionstartswith) | | (Public Preview) Creates an expression that checks if a string starts with a given prefix. +```typescript +// Check if the 'name' field starts with "Mr." +field("name").startsWith("Mr."); + +``` + | +| [startsWith(prefix)](./firestore_pipelines.expression.md#expressionstartswith) | | (Public Preview) Creates an expression that checks if a string starts with a given prefix (represented as an expression). +```typescript +// Check if the 'fullName' field starts with the value of the 'firstName' field +field("fullName").startsWith(field("firstName")); + +``` + | +| [stringConcat(secondString, otherStrings)](./firestore_pipelines.expression.md#expressionstringconcat) | | (Public Preview) Creates an expression that concatenates string expressions together. +```typescript +// Combine the 'firstName', " ", and 'lastName' fields into a single string +field("firstName").stringConcat(constant(" "), field("lastName")); + +``` + | +| [stringContains(substring)](./firestore_pipelines.expression.md#expressionstringcontains) | | (Public Preview) Creates an expression that checks if a string contains a specified substring. +```typescript +// Check if the 'description' field contains "example". +field("description").stringContains("example"); + +``` + | +| [stringContains(expr)](./firestore_pipelines.expression.md#expressionstringcontains) | | (Public Preview) Creates an expression that checks if a string contains the string represented by another expression. +```typescript +// Check if the 'description' field contains the value of the 'keyword' field. +field("description").stringContains(field("keyword")); + +``` + | +| [stringReverse()](./firestore_pipelines.expression.md#expressionstringreverse) | | (Public Preview) Creates an expression that reverses a string. +```typescript +// Reverse the value of the 'myString' field. +field("myString").stringReverse(); + +``` + A new representing the reversed string. | +| [substring(position, length)](./firestore_pipelines.expression.md#expressionsubstring) | | (Public Preview) Creates an expression that returns a substring of the results of this expression. | +| [substring(position, length)](./firestore_pipelines.expression.md#expressionsubstring) | | (Public Preview) Creates an expression that returns a substring of the results of this expression. | +| [subtract(subtrahend)](./firestore_pipelines.expression.md#expressionsubtract) | | (Public Preview) Creates an expression that subtracts another expression from this expression. +```typescript +// Subtract the 'discount' field from the 'price' field +field("price").subtract(field("discount")); + +``` + | +| [subtract(subtrahend)](./firestore_pipelines.expression.md#expressionsubtract) | | (Public Preview) Creates an expression that subtracts a constant value from this expression. +```typescript +// Subtract 20 from the value of the 'total' field +field("total").subtract(20); + +``` + | +| [sum()](./firestore_pipelines.expression.md#expressionsum) | | (Public Preview) Creates an aggregation that calculates the sum of a numeric field across multiple stage inputs. +```typescript +// Calculate the total revenue from a set of orders +field("orderAmount").sum().as("totalRevenue"); + +``` + A new AggregateFunction representing the 'sum' aggregation. | +| [timestampAdd(unit, amount)](./firestore_pipelines.expression.md#expressiontimestampadd) | | (Public Preview) Creates an expression that adds a specified amount of time to this timestamp expression. +```typescript +// Add some duration determined by field 'unit' and 'amount' to the 'timestamp' field. +field("timestamp").timestampAdd(field("unit"), field("amount")); + +``` + | +| [timestampAdd(unit, amount)](./firestore_pipelines.expression.md#expressiontimestampadd) | | (Public Preview) Creates an expression that adds a specified amount of time to this timestamp expression. +```typescript +// Add 1 day to the 'timestamp' field. +field("timestamp").timestampAdd("day", 1); + +``` + | +| [timestampSubtract(unit, amount)](./firestore_pipelines.expression.md#expressiontimestampsubtract) | | (Public Preview) Creates an expression that subtracts a specified amount of time from this timestamp expression. +```typescript +// Subtract some duration determined by field 'unit' and 'amount' from the 'timestamp' field. +field("timestamp").timestampSubtract(field("unit"), field("amount")); + +``` + | +| [timestampSubtract(unit, amount)](./firestore_pipelines.expression.md#expressiontimestampsubtract) | | (Public Preview) Creates an expression that subtracts a specified amount of time from this timestamp expression. +```typescript +// Subtract 1 day from the 'timestamp' field. +field("timestamp").timestampSubtract("day", 1); + +``` + | +| [timestampToUnixMicros()](./firestore_pipelines.expression.md#expressiontimestamptounixmicros) | | (Public Preview) Creates an expression that converts this timestamp expression to the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC). +```typescript +// Convert the 'timestamp' field to microseconds since epoch. +field("timestamp").timestampToUnixMicros(); + +``` + A new representing the number of microseconds since epoch. | +| [timestampToUnixMillis()](./firestore_pipelines.expression.md#expressiontimestamptounixmillis) | | (Public Preview) Creates an expression that converts this timestamp expression to the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC). +```typescript +// Convert the 'timestamp' field to milliseconds since epoch. +field("timestamp").timestampToUnixMillis(); + +``` + A new representing the number of milliseconds since epoch. | +| [timestampToUnixSeconds()](./firestore_pipelines.expression.md#expressiontimestamptounixseconds) | | (Public Preview) Creates an expression that converts this timestamp expression to the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC). +```typescript +// Convert the 'timestamp' field to seconds since epoch. +field("timestamp").timestampToUnixSeconds(); + +``` + A new representing the number of seconds since epoch. | +| [timestampTruncate(granularity, timezone)](./firestore_pipelines.expression.md#expressiontimestamptruncate) | | (Public Preview) Creates an expression that truncates a timestamp to a specified granularity. | +| [timestampTruncate(granularity, timezone)](./firestore_pipelines.expression.md#expressiontimestamptruncate) | | (Public Preview) Creates an expression that truncates a timestamp to a specified granularity. | +| [toLower()](./firestore_pipelines.expression.md#expressiontolower) | | (Public Preview) Creates an expression that converts a string to lowercase. +```typescript +// Convert the 'name' field to lowercase +field("name").toLower(); + +``` + A new Expr representing the lowercase string. | +| [toUpper()](./firestore_pipelines.expression.md#expressiontoupper) | | (Public Preview) Creates an expression that converts a string to uppercase. +```typescript +// Convert the 'title' field to uppercase +field("title").toUpper(); + +``` + A new Expr representing the uppercase string. | +| [trim(valueToTrim)](./firestore_pipelines.expression.md#expressiontrim) | | (Public Preview) Creates an expression that removes leading and trailing characters from a string or byte array. +```typescript +// Trim whitespace from the 'userInput' field +field("userInput").trim(); + +// Trim quotes from the 'userInput' field +field("userInput").trim('"'); + +``` + | +| [type()](./firestore_pipelines.expression.md#expressiontype) | | (Public Preview) Creates an expression that returns the data type of this expression's result, as a string. | +| [unixMicrosToTimestamp()](./firestore_pipelines.expression.md#expressionunixmicrostotimestamp) | | (Public Preview) Creates an expression that interprets this expression as the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. +```typescript +// Interpret the 'microseconds' field as microseconds since epoch. +field("microseconds").unixMicrosToTimestamp(); + +``` + A new representing the timestamp. | +| [unixMillisToTimestamp()](./firestore_pipelines.expression.md#expressionunixmillistotimestamp) | | (Public Preview) Creates an expression that interprets this expression as the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. +```typescript +// Interpret the 'milliseconds' field as milliseconds since epoch. +field("milliseconds").unixMillisToTimestamp(); + +``` + A new representing the timestamp. | +| [unixSecondsToTimestamp()](./firestore_pipelines.expression.md#expressionunixsecondstotimestamp) | | (Public Preview) Creates an expression that interprets this expression as the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. +```typescript +// Interpret the 'seconds' field as seconds since epoch. +field("seconds").unixSecondsToTimestamp(); + +``` + A new representing the timestamp. | +| [vectorLength()](./firestore_pipelines.expression.md#expressionvectorlength) | | (Public Preview) Creates an expression that calculates the length (number of dimensions) of this Firestore Vector expression. +```typescript +// Get the vector length (dimension) of the field 'embedding'. +field("embedding").vectorLength(); + +``` + A new representing the length of the vector. | + +## Expression.expressionType + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +abstract readonly expressionType: ExpressionType; +``` + +## Expression.abs() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the absolute value of a numeric value. + +```typescript +// Compute the absolute value of the 'price' field. +field("price").abs(); + +``` + A new representing the absolute value of the numeric value. + +Signature: + +```typescript +abs(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.add() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that adds this expression to another expression. + +```typescript +// Add the value of the 'quantity' field and the 'reserve' field. +field("quantity").add(field("reserve")); + +``` + +Signature: + +```typescript +add(second: Expression | unknown): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| second | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown | The expression or literal to add to this expression. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.arrayConcat() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that concatenates an array expression with one or more other arrays. + +```typescript +// Combine the 'items' array with another array field. +field("items").arrayConcat(field("otherItems")); + +``` + +Signature: + +```typescript +arrayConcat(secondArray: Expression | unknown[], ...otherArrays: Array): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| secondArray | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown\[\] | Second array expression or array literal to concatenate. | +| otherArrays | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown\[\]> | Optional additional array expressions or array literals to concatenate. A new Expr representing the concatenated array. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.arrayContains() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an array contains a specific element. + +```typescript +// Check if the 'sizes' array contains the value from the 'selectedSize' field +field("sizes").arrayContains(field("selectedSize")); + +``` + +Signature: + +```typescript +arrayContains(expression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The element to search for in the array. A new Expr representing the 'array\_contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.arrayContains() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an array contains a specific value. + +```typescript +// Check if the 'colors' array contains "red" +field("colors").arrayContains("red"); + +``` + +Signature: + +```typescript +arrayContains(value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | unknown | The element to search for in the array. A new Expr representing the 'array\_contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.arrayContainsAll() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an array contains all the specified elements. + +```typescript +// Check if the 'tags' array contains both the value in field "tag1" and the literal value "tag2" +field("tags").arrayContainsAll([field("tag1"), "tag2"]); + +``` + +Signature: + +```typescript +arrayContainsAll(values: Array): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| values | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | The elements to check for in the array. A new Expr representing the 'array\_contains\_all' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.arrayContainsAll() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an array contains all the specified elements. + +```typescript +// Check if the 'tags' array contains both of the values from field "tag1" and the literal value "tag2" +field("tags").arrayContainsAll(array([field("tag1"), "tag2"])); + +``` + +Signature: + +```typescript +arrayContainsAll(arrayExpression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| arrayExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The elements to check for in the array. A new Expr representing the 'array\_contains\_all' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.arrayContainsAny() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an array contains any of the specified elements. + +```typescript +// Check if the 'categories' array contains either values from field "cate1" or "cate2" +field("categories").arrayContainsAny([field("cate1"), field("cate2")]); + +``` + +Signature: + +```typescript +arrayContainsAny(values: Array): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| values | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | The elements to check for in the array. A new Expr representing the 'array\_contains\_any' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.arrayContainsAny() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an array contains any of the specified elements. + +```typescript +// Check if the 'groups' array contains either the value from the 'userGroup' field +// or the value "guest" +field("groups").arrayContainsAny(array([field("userGroup"), "guest"])); + +``` + +Signature: + +```typescript +arrayContainsAny(arrayExpression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| arrayExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The elements to check for in the array. A new Expr representing the 'array\_contains\_any' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.arrayGet() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that indexes into an array from the beginning or end and returns the element. If the offset exceeds the array length, an error is returned. A negative offset, starts from the end. + +```typescript +// Return the value in the 'tags' field array at index `1`. +field('tags').arrayGet(1); + +``` + +Signature: + +```typescript +arrayGet(offset: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| offset | number | The index of the element to return. A new Expr representing the 'arrayGet' operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.arrayGet() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that indexes into an array from the beginning or end and returns the element. If the offset exceeds the array length, an error is returned. A negative offset, starts from the end. + +```typescript +// Return the value in the tags field array at index specified by field +// 'favoriteTag'. +field('tags').arrayGet(field('favoriteTag')); + +``` + +Signature: + +```typescript +arrayGet(offsetExpr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| offsetExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | An Expr evaluating to the index of the element to return. A new Expr representing the 'arrayGet' operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.arrayLength() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the length of an array. + +```typescript +// Get the number of items in the 'cart' array +field("cart").arrayLength(); + +``` + A new `Expr` representing the length of the array. + +Signature: + +```typescript +arrayLength(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.arrayReverse() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that reverses an array. + +```typescript +// Reverse the value of the 'myArray' field. +field("myArray").arrayReverse(); + +``` + A new representing the reversed array. + +Signature: + +```typescript +arrayReverse(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.arraySum() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the sum of the elements in an array. + +```typescript +// Compute the sum of the elements in the 'scores' field. +field("scores").arraySum(); + +``` + A new representing the sum of the elements in the array. + +Signature: + +```typescript +arraySum(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.as() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Assigns an alias to this expression. + +Aliases are useful for renaming fields in the output of a stage or for giving meaningful names to calculated values. + +```typescript +// Calculate the total price and assign it the alias "totalPrice" and add it to the output. +firestore.pipeline().collection("items") + .addFields(field("price").multiply(field("quantity")).as("totalPrice")); + +``` + +Signature: + +```typescript +as(name: string): AliasedExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | The alias to assign to this expression. A new that wraps this expression and associates it with the provided alias. | + +Returns: + +[AliasedExpression](./firestore_pipelines.aliasedexpression.md#aliasedexpression_class) + +## Expression.asBoolean() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Wraps the expression in a \[BooleanExpression\]. + + A \[BooleanExpression\] representing the same expression. + +Signature: + +```typescript +asBoolean(): BooleanExpression; +``` +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.ascending() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an that sorts documents in ascending order based on this expression. + +```typescript +// Sort documents by the 'name' field in ascending order +pipeline().collection("users") + .sort(field("name").ascending()); + +``` + A new `Ordering` for ascending sorting. + +Signature: + +```typescript +ascending(): Ordering; +``` +Returns: + +[Ordering](./firestore_pipelines.ordering.md#ordering_class) + +## Expression.average() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that calculates the average (mean) of a numeric field across multiple stage inputs. + +```typescript +// Calculate the average age of users +field("age").average().as("averageAge"); + +``` + A new `AggregateFunction` representing the 'average' aggregation. + +Signature: + +```typescript +average(): AggregateFunction; +``` +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +## Expression.byteLength() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the length of this string expression in bytes. + +```typescript +// Calculate the length of the 'myString' field in bytes. +field("myString").byteLength(); + +``` + A new representing the length of the string in bytes. + +Signature: + +```typescript +byteLength(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.ceil() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the ceiling of a numeric value. + +```typescript +// Compute the ceiling of the 'price' field. +field("price").ceil(); + +``` + A new representing the ceiling of the numeric value. + +Signature: + +```typescript +ceil(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.charLength() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the character length of a string in UTF-8. + +```typescript +// Get the character length of the 'name' field in its UTF-8 form. +field("name").charLength(); + +``` + A new `Expr` representing the length of the string. + +Signature: + +```typescript +charLength(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.collectionId() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the collection ID from a path. + +```typescript +// Get the collection ID from a path. +field("__path__").collectionId(); + +``` + A new representing the collectionId operation. + +Signature: + +```typescript +collectionId(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.concat() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that concatenates expression results together. + +```typescript +// Combine the 'firstName', ' ', and 'lastName' fields into a single value. +field("firstName").concat(constant(" "), field("lastName")); + +``` + +Signature: + +```typescript +concat(second: Expression | unknown, ...others: Array): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| second | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown | The additional expression or literal to concatenate. | +| others | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | Optional additional expressions or literals to concatenate. A new Expr representing the concatenated value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.cosineDistance() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the cosine distance between two vectors. + +```typescript +// Calculate the cosine distance between the 'userVector' field and the 'itemVector' field +field("userVector").cosineDistance(field("itemVector")); + +``` + +Signature: + +```typescript +cosineDistance(vectorExpression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The other vector (represented as an Expr) to compare against. A new Expr representing the cosine distance between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.cosineDistance() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the Cosine distance between two vectors. + +```typescript +// Calculate the Cosine distance between the 'location' field and a target location +field("location").cosineDistance(new VectorValue([37.7749, -122.4194])); + +``` + +Signature: + +```typescript +cosineDistance(vector: VectorValue | number[]): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vector | [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) \| number\[\] | The other vector (as a VectorValue) to compare against. A new Expr representing the Cosine\* distance between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.count() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that counts the number of stage inputs with valid evaluations of the expression or field. + +```typescript +// Count the total number of products +field("productId").count().as("totalProducts"); + +``` + A new `AggregateFunction` representing the 'count' aggregation. + +Signature: + +```typescript +count(): AggregateFunction; +``` +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +## Expression.countDistinct() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that counts the number of distinct values of the expression or field. + +```typescript +// Count the distinct number of products +field("productId").countDistinct().as("distinctProducts"); + +``` + A new `AggregateFunction` representing the 'count\_distinct' aggregation. + +Signature: + +```typescript +countDistinct(): AggregateFunction; +``` +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +## Expression.descending() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an that sorts documents in descending order based on this expression. + +```typescript +// Sort documents by the 'createdAt' field in descending order +firestore.pipeline().collection("users") + .sort(field("createdAt").descending()); + +``` + A new `Ordering` for descending sorting. + +Signature: + +```typescript +descending(): Ordering; +``` +Returns: + +[Ordering](./firestore_pipelines.ordering.md#ordering_class) + +## Expression.divide() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that divides this expression by another expression. + +```typescript +// Divide the 'total' field by the 'count' field +field("total").divide(field("count")); + +``` + +Signature: + +```typescript +divide(divisor: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| divisor | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to divide by. A new Expr representing the division operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.divide() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that divides this expression by a constant value. + +```typescript +// Divide the 'value' field by 10 +field("value").divide(10); + +``` + +Signature: + +```typescript +divide(divisor: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| divisor | number | The constant value to divide by. A new Expr representing the division operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.documentId() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the document ID from a path. + +```typescript +// Get the document ID from a path. +field("__path__").documentId(); + +``` + A new representing the documentId operation. + +Signature: + +```typescript +documentId(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.dotProduct() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the dot product between two vectors. + +```typescript +// Calculate the dot product between a feature vector and a target vector +field("features").dotProduct([0.5, 0.8, 0.2]); + +``` + +Signature: + +```typescript +dotProduct(vectorExpression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The other vector (as an array of numbers) to calculate with. A new Expr representing the dot product between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.dotProduct() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the dot product between two vectors. + +```typescript +// Calculate the dot product between a feature vector and a target vector +field("features").dotProduct(new VectorValue([0.5, 0.8, 0.2])); + +``` + +Signature: + +```typescript +dotProduct(vector: VectorValue | number[]): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vector | [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) \| number\[\] | The other vector (as an array of numbers) to calculate with. A new Expr representing the dot product between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.endsWith() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string ends with a given postfix. + +```typescript +// Check if the 'filename' field ends with ".txt" +field("filename").endsWith(".txt"); + +``` + +Signature: + +```typescript +endsWith(suffix: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| suffix | string | The postfix to check for. A new Expr representing the 'ends with' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.endsWith() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string ends with a given postfix (represented as an expression). + +```typescript +// Check if the 'url' field ends with the value of the 'extension' field +field("url").endsWith(field("extension")); + +``` + +Signature: + +```typescript +endsWith(suffix: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| suffix | [Expression](./firestore_pipelines.expression.md#expression_class) | The postfix expression to check for. A new Expr representing the 'ends with' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.equal() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is equal to another expression. + +```typescript +// Check if the 'age' field is equal to 21 +field("age").equal(21); + +``` + +Signature: + +```typescript +equal(expression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare for equality. A new Expr representing the equality comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.equal() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is equal to a constant value. + +```typescript +// Check if the 'city' field is equal to "London" +field("city").equal("London"); + +``` + +Signature: + +```typescript +equal(value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | unknown | The constant value to compare for equality. A new Expr representing the equality comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.equalAny() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is equal to any of the provided values or expressions. + +```typescript +// Check if the 'category' field is either "Electronics" or value of field 'primaryType' +field("category").equalAny("Electronics", field("primaryType")); + +``` + +Signature: + +```typescript +equalAny(values: Array): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| values | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | The values or expressions to check against. A new Expr representing the 'IN' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.equalAny() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is equal to any of the provided values or expressions. + +```typescript +// Check if the 'category' field is either "Electronics" or value of field 'primaryType' +field("category").equalAny(array(["Electronics", field("primaryType")])); + +``` + +Signature: + +```typescript +equalAny(arrayExpression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| arrayExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression that evaluates to an array of values to check against. A new Expr representing the 'IN' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.euclideanDistance() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the Euclidean distance between two vectors. + +```typescript +// Calculate the Euclidean distance between the 'location' field and a target location +field("location").euclideanDistance([37.7749, -122.4194]); + +``` + +Signature: + +```typescript +euclideanDistance(vectorExpression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The other vector (as an array of numbers) to calculate with. A new Expr representing the Euclidean distance between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.euclideanDistance() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the Euclidean distance between two vectors. + +```typescript +// Calculate the Euclidean distance between the 'location' field and a target location +field("location").euclideanDistance(new VectorValue([37.7749, -122.4194])); + +``` + +Signature: + +```typescript +euclideanDistance(vector: VectorValue | number[]): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vector | [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) \| number\[\] | The other vector (as a VectorValue) to compare against. A new Expr representing the Euclidean distance between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.exists() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field exists in the document. + +```typescript +// Check if the document has a field named "phoneNumber" +field("phoneNumber").exists(); + +``` + A new `Expr` representing the 'exists' check. + +Signature: + +```typescript +exists(): BooleanExpression; +``` +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.exp() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes e to the power of this expression. + +```typescript +// Compute e to the power of the 'value' field. +field("value").exp(); + +``` + A new representing the exp of the numeric value. + +Signature: + +```typescript +exp(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.floor() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the floor of a numeric value. + +```typescript +// Compute the floor of the 'price' field. +field("price").floor(); + +``` + A new representing the floor of the numeric value. + +Signature: + +```typescript +floor(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.greaterThan() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is greater than another expression. + +```typescript +// Check if the 'age' field is greater than the 'limit' field +field("age").greaterThan(field("limit")); + +``` + +Signature: + +```typescript +greaterThan(expression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare for greater than. A new Expr representing the greater than comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.greaterThan() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is greater than a constant value. + +```typescript +// Check if the 'price' field is greater than 100 +field("price").greaterThan(100); + +``` + +Signature: + +```typescript +greaterThan(value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | unknown | The constant value to compare for greater than. A new Expr representing the greater than comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.greaterThanOrEqual() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is greater than or equal to another expression. + +```typescript +// Check if the 'quantity' field is greater than or equal to field 'requirement' plus 1 +field("quantity").greaterThanOrEqual(field('requirement').add(1)); + +``` + +Signature: + +```typescript +greaterThanOrEqual(expression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare for greater than or equal to. A new Expr representing the greater than or equal to comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.greaterThanOrEqual() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is greater than or equal to a constant value. + +```typescript +// Check if the 'score' field is greater than or equal to 80 +field("score").greaterThanOrEqual(80); + +``` + +Signature: + +```typescript +greaterThanOrEqual(value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | unknown | The constant value to compare for greater than or equal to. A new Expr representing the greater than or equal to comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.ifAbsent() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the `elseValue` argument if this expression results in an absent value, else return the result of the this expression evaluation. + +```typescript +// Returns the value of the optional field 'optional_field', or returns 'default_value' +// if the field is absent. +field("optional_field").ifAbsent("default_value") + +``` + +Signature: + +```typescript +ifAbsent(elseValue: unknown): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| elseValue | unknown | The value that will be returned if this Expression evaluates to an absent value. A new \[Expression\] representing the ifAbsent operation. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +## Expression.ifAbsent() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the `elseValue` argument if this expression results in an absent value, else return the result of this expression evaluation. + +```typescript +// Returns the value of the optional field 'optional_field', or if that is +// absent, then returns the value of the field ` +field("optional_field").ifAbsent(field('default_field')) + +``` + +Signature: + +```typescript +ifAbsent(elseExpression: unknown): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| elseExpression | unknown | The Expression that will be evaluated if this Expression evaluates to an absent value. A new \[Expression\] representing the ifAbsent operation. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +## Expression.ifError() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the result of the `catchExpr` argument if there is an error, else return the result of this expression. + +```typescript +// Returns the first item in the title field arrays, or returns +// the entire title field if the array is empty or the field is another type. +field("title").arrayGet(0).ifError(field("title")); + +``` + +Signature: + +```typescript +ifError(catchExpr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| catchExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | The catch expression that will be evaluated and returned if this expression produces an error. A new representing the 'ifError' operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.ifError() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the `catch` argument if there is an error, else return the result of this expression. + +```typescript +// Returns the first item in the title field arrays, or returns +// "Default Title" +field("title").arrayGet(0).ifError("Default Title"); + +``` + +Signature: + +```typescript +ifError(catchValue: unknown): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| catchValue | unknown | The value that will be returned if this expression produces an error. A new representing the 'ifError' operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.isAbsent() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns `true` if the result of this expression is absent. Otherwise, returns `false` even if the value is `null`. + +```typescript +// Check if the field `value` is absent. +field("value").isAbsent(); + +``` + A new representing the 'isAbsent' check. + +Signature: + +```typescript +isAbsent(): BooleanExpression; +``` +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.isError() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a given expression produces an error. + +```typescript +// Check if the result of a calculation is an error +field("title").arrayContains(1).isError(); + +``` + A new representing the 'isError' check. + +Signature: + +```typescript +isError(): BooleanExpression; +``` +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.join() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that joins the elements of an array into a string. + +```typescript +// Join the elements of the 'tags' field with the delimiter from the 'separator' field. +field("tags").join(field("separator")) + +``` + +Signature: + +```typescript +join(delimiterExpression: Expression): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| delimiterExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression that evaluates to the delimiter string. A new Expression representing the join operation. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +## Expression.join() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that joins the elements of an array field into a string. + +```typescript +// Join the elements of the 'tags' field with a comma and space. +field("tags").join(", ") + +``` + +Signature: + +```typescript +join(delimiter: string): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| delimiter | string | The string to use as a delimiter. A new Expression representing the join operation. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +## Expression.length() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the length of a string, array, map, vector, or bytes. + +```typescript +// Get the length of the 'name' field. +field("name").length(); + +// Get the number of items in the 'cart' array. +field("cart").length(); + +``` + A new `Expr` representing the length of the string, array, map, vector, or bytes. + +Signature: + +```typescript +length(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.lessThan() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is less than another expression. + +```typescript +// Check if the 'age' field is less than 'limit' +field("age").lessThan(field('limit')); + +``` + +Signature: + +```typescript +lessThan(experession: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| experession | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare for less than. A new Expr representing the less than comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.lessThan() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is less than a constant value. + +```typescript +// Check if the 'price' field is less than 50 +field("price").lessThan(50); + +``` + +Signature: + +```typescript +lessThan(value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | unknown | The constant value to compare for less than. A new Expr representing the less than comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.lessThanOrEqual() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is less than or equal to another expression. + +```typescript +// Check if the 'quantity' field is less than or equal to 20 +field("quantity").lessThan(constant(20)); + +``` + +Signature: + +```typescript +lessThanOrEqual(expression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare for less than or equal to. A new Expr representing the less than or equal to comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.lessThanOrEqual() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is less than or equal to a constant value. + +```typescript +// Check if the 'score' field is less than or equal to 70 +field("score").lessThan(70); + +``` + +Signature: + +```typescript +lessThanOrEqual(value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | unknown | The constant value to compare for less than or equal to. A new Expr representing the less than or equal to comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.like() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that performs a case-sensitive string comparison. + +```typescript +// Check if the 'title' field contains the word "guide" (case-sensitive) +field("title").like("%guide%"); + +``` + +Signature: + +```typescript +like(pattern: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| pattern | string | The pattern to search for. You can use "%" as a wildcard character. A new Expr representing the 'like' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.like() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that performs a case-sensitive string comparison. + +```typescript +// Check if the 'title' field contains the word "guide" (case-sensitive) +field("title").like("%guide%"); + +``` + +Signature: + +```typescript +like(pattern: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| pattern | [Expression](./firestore_pipelines.expression.md#expression_class) | The pattern to search for. You can use "%" as a wildcard character. A new Expr representing the 'like' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.ln() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the natural logarithm of a numeric value. + +```typescript +// Compute the natural logarithm of the 'value' field. +field("value").ln(); + +``` + A new representing the natural logarithm of the numeric value. + +Signature: + +```typescript +ln(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.log10() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the base-10 logarithm of a numeric value. + +```typescript +// Compute the base-10 logarithm of the 'value' field. +field("value").log10(); + +``` + A new representing the base-10 logarithm of the numeric value. + +Signature: + +```typescript +log10(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.logicalMaximum() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the larger value between this expression and another expression, based on Firestore's value type ordering. + +```typescript +// Returns the larger value between the 'timestamp' field and the current timestamp. +field("timestamp").logicalMaximum(Function.currentTimestamp()); + +``` + +Signature: + +```typescript +logicalMaximum(second: Expression | unknown, ...others: Array): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| second | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown | The second expression or literal to compare with. | +| others | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | Optional additional expressions or literals to compare with. A new representing the logical maximum operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.logicalMinimum() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the smaller value between this expression and another expression, based on Firestore's value type ordering. + +```typescript +// Returns the smaller value between the 'timestamp' field and the current timestamp. +field("timestamp").logicalMinimum(Function.currentTimestamp()); + +``` + +Signature: + +```typescript +logicalMinimum(second: Expression | unknown, ...others: Array): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| second | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown | The second expression or literal to compare with. | +| others | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | Optional additional expressions or literals to compare with. A new representing the logical minimum operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.mapGet() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Accesses a value from a map (object) field using the provided key. + +```typescript +// Get the 'city' value from the 'address' map field +field("address").mapGet("city"); + +``` + +Signature: + +```typescript +mapGet(subfield: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| subfield | string | The key to access in the map. A new Expr representing the value associated with the given key in the map. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.mapMerge() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that merges multiple map values. + +``` +// Merges the map in the settings field with, a map literal, and a map in +// that is conditionally returned by another expression +field('settings').mapMerge({ enabled: true }, conditional(field('isAdmin'), { admin: true}, {}) + +``` + +Signature: + +```typescript +mapMerge(secondMap: Record | Expression, ...otherMaps: Array | Expression>): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| secondMap | Record<string, unknown> \| [Expression](./firestore_pipelines.expression.md#expression_class) | A required second map to merge. Represented as a literal or an expression that returns a map. | +| otherMaps | Array<Record<string, unknown> \| [Expression](./firestore_pipelines.expression.md#expression_class)> | Optional additional maps to merge. Each map is represented as a literal or an expression that returns a map. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +A new representing the 'mapMerge' operation. + +## Expression.mapRemove() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that removes a key from the map produced by evaluating this expression. + +``` +// Removes the key 'baz' from the input map. +map({foo: 'bar', baz: true}).mapRemove('baz'); + +``` + +Signature: + +```typescript +mapRemove(key: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| key | string | The name of the key to remove from the input map. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +A new representing the 'mapRemove' operation. + +## Expression.mapRemove() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that removes a key from the map produced by evaluating this expression. + +``` +// Removes the key 'baz' from the input map. +map({foo: 'bar', baz: true}).mapRemove(constant('baz')); + +``` + +Signature: + +```typescript +mapRemove(keyExpr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| keyExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression that produces the name of the key to remove from the input map. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +A new representing the 'mapRemove' operation. + +## Expression.maximum() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that finds the maximum value of a field across multiple stage inputs. + +```typescript +// Find the highest score in a leaderboard +field("score").maximum().as("highestScore"); + +``` + A new `AggregateFunction` representing the 'maximum' aggregation. + +Signature: + +```typescript +maximum(): AggregateFunction; +``` +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +## Expression.minimum() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that finds the minimum value of a field across multiple stage inputs. + +```typescript +// Find the lowest price of all products +field("price").minimum().as("lowestPrice"); + +``` + A new `AggregateFunction` representing the 'minimum' aggregation. + +Signature: + +```typescript +minimum(): AggregateFunction; +``` +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +## Expression.mod() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the modulo (remainder) of dividing this expression by another expression. + +```typescript +// Calculate the remainder of dividing the 'value' field by the 'divisor' field +field("value").mod(field("divisor")); + +``` + +Signature: + +```typescript +mod(expression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to divide by. A new Expr representing the modulo operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.mod() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the modulo (remainder) of dividing this expression by a constant value. + +```typescript +// Calculate the remainder of dividing the 'value' field by 10 +field("value").mod(10); + +``` + +Signature: + +```typescript +mod(value: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | number | The constant value to divide by. A new Expr representing the modulo operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.multiply() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that multiplies this expression by another expression. + +```typescript +// Multiply the 'quantity' field by the 'price' field +field("quantity").multiply(field("price")); + +``` + +Signature: + +```typescript +multiply(second: Expression | number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| second | [Expression](./firestore_pipelines.expression.md#expression_class) \| number | The second expression or literal to multiply by. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.notEqual() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is not equal to another expression. + +```typescript +// Check if the 'status' field is not equal to "completed" +field("status").notEqual("completed"); + +``` + +Signature: + +```typescript +notEqual(expression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare for inequality. A new Expr representing the inequality comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.notEqual() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is not equal to a constant value. + +```typescript +// Check if the 'country' field is not equal to "USA" +field("country").notEqual("USA"); + +``` + +Signature: + +```typescript +notEqual(value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | unknown | The constant value to compare for inequality. A new Expr representing the inequality comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.notEqualAny() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is not equal to any of the provided values or expressions. + +```typescript +// Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' +field("status").notEqualAny(["pending", field("rejectedStatus")]); + +``` + +Signature: + +```typescript +notEqualAny(values: Array): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| values | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | The values or expressions to check against. A new Expr representing the 'notEqualAny' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.notEqualAny() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if this expression is not equal to any of the values in the evaluated expression. + +```typescript +// Check if the 'status' field is not equal to any value in the field 'rejectedStatuses' +field("status").notEqualAny(field('rejectedStatuses')); + +``` + +Signature: + +```typescript +notEqualAny(arrayExpression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| arrayExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The values or expressions to check against. A new Expr representing the 'notEqualAny' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.pow() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the value of this expression raised to the power of another expression. + +```typescript +// Raise the value of the 'base' field to the power of the 'exponent' field. +field("base").pow(field("exponent")); + +``` + +Signature: + +```typescript +pow(exponent: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| exponent | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to raise this expression to the power of. A new Expr representing the power operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.pow() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the value of this expression raised to the power of a constant value. + +```typescript +// Raise the value of the 'base' field to the power of 2. +field("base").pow(2); + +``` + +Signature: + +```typescript +pow(exponent: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| exponent | number | The constant value to raise this expression to the power of. A new Expr representing the power operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.regexContains() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string contains a specified regular expression as a substring. + +```typescript +// Check if the 'description' field contains "example" (case-insensitive) +field("description").regexContains("(?i)example"); + +``` + +Signature: + +```typescript +regexContains(pattern: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| pattern | string | The regular expression to use for the search. A new Expr representing the 'contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.regexContains() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string contains a specified regular expression as a substring. + +```typescript +// Check if the 'description' field contains the regular expression stored in field 'regex' +field("description").regexContains(field("regex")); + +``` + +Signature: + +```typescript +regexContains(pattern: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| pattern | [Expression](./firestore_pipelines.expression.md#expression_class) | The regular expression to use for the search. A new Expr representing the 'contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.regexMatch() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string matches a specified regular expression. + +```typescript +// Check if the 'email' field matches a valid email pattern +field("email").regexMatch("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"); + +``` + +Signature: + +```typescript +regexMatch(pattern: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| pattern | string | The regular expression to use for the match. A new Expr representing the regular expression match. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.regexMatch() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string matches a specified regular expression. + +```typescript +// Check if the 'email' field matches a regular expression stored in field 'regex' +field("email").regexMatch(field("regex")); + +``` + +Signature: + +```typescript +regexMatch(pattern: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| pattern | [Expression](./firestore_pipelines.expression.md#expression_class) | The regular expression to use for the match. A new Expr representing the regular expression match. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.reverse() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that reverses this string expression. + +```typescript +// Reverse the value of the 'myString' field. +field("myString").reverse(); + +``` + A new representing the reversed string. + +Signature: + +```typescript +reverse(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.round() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that rounds a numeric value to the nearest whole number. + +```typescript +// Round the value of the 'price' field. +field("price").round(); + +``` + A new `Expr` representing the rounded value. + +Signature: + +```typescript +round(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.round() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that rounds a numeric value to the specified number of decimal places. + +```typescript +// Round the value of the 'price' field to two decimal places. +field("price").round(2); + +``` + +Signature: + +```typescript +round(decimalPlaces: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| decimalPlaces | number | A constant specifying the rounding precision in decimal places. A new Expr representing the rounded value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.round() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that rounds a numeric value to the specified number of decimal places. + +```typescript +// Round the value of the 'price' field to two decimal places. +field("price").round(constant(2)); + +``` + +Signature: + +```typescript +round(decimalPlaces: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| decimalPlaces | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression specifying the rounding precision in decimal places. A new Expr representing the rounded value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.split() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that splits the result of this expression into an array of substrings based on the provided delimiter. + +Signature: + +```typescript +split(delimiter: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| delimiter | string | | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### Example + + +```typescript +// Split the 'scoresCsv' field on delimiter ',' +field('scoresCsv').split(',') + +``` + A new representing the split function. + +## Expression.split() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that splits the result of this expression into an array of substrings based on the provided delimiter. + +Signature: + +```typescript +split(delimiter: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| delimiter | [Expression](./firestore_pipelines.expression.md#expression_class) | | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### Example + + +```typescript +// Split the 'scores' field on delimiter ',' or ':' depending on the stored format +field('scores').split(conditional(field('format').equal('csv'), constant(','), constant(':')) + +``` + A new representing the split function. + +## Expression.sqrt() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the square root of a numeric value. + +```typescript +// Compute the square root of the 'value' field. +field("value").sqrt(); + +``` + A new representing the square root of the numeric value. + +Signature: + +```typescript +sqrt(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.startsWith() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string starts with a given prefix. + +```typescript +// Check if the 'name' field starts with "Mr." +field("name").startsWith("Mr."); + +``` + +Signature: + +```typescript +startsWith(prefix: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| prefix | string | The prefix to check for. A new Expr representing the 'starts with' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.startsWith() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string starts with a given prefix (represented as an expression). + +```typescript +// Check if the 'fullName' field starts with the value of the 'firstName' field +field("fullName").startsWith(field("firstName")); + +``` + +Signature: + +```typescript +startsWith(prefix: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| prefix | [Expression](./firestore_pipelines.expression.md#expression_class) | The prefix expression to check for. A new Expr representing the 'starts with' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.stringConcat() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that concatenates string expressions together. + +```typescript +// Combine the 'firstName', " ", and 'lastName' fields into a single string +field("firstName").stringConcat(constant(" "), field("lastName")); + +``` + +Signature: + +```typescript +stringConcat(secondString: Expression | string, ...otherStrings: Array): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| secondString | [Expression](./firestore_pipelines.expression.md#expression_class) \| string | The additional expression or string literal to concatenate. | +| otherStrings | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| string> | Optional additional expressions or string literals to concatenate. A new Expr representing the concatenated string. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.stringContains() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string contains a specified substring. + +```typescript +// Check if the 'description' field contains "example". +field("description").stringContains("example"); + +``` + +Signature: + +```typescript +stringContains(substring: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| substring | string | The substring to search for. A new Expr representing the 'contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.stringContains() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string contains the string represented by another expression. + +```typescript +// Check if the 'description' field contains the value of the 'keyword' field. +field("description").stringContains(field("keyword")); + +``` + +Signature: + +```typescript +stringContains(expr: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the substring to search for. A new Expr representing the 'contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## Expression.stringReverse() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that reverses a string. + +```typescript +// Reverse the value of the 'myString' field. +field("myString").stringReverse(); + +``` + A new representing the reversed string. + +Signature: + +```typescript +stringReverse(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.substring() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns a substring of the results of this expression. + +Signature: + +```typescript +substring(position: number, length?: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| position | number | Index of the first character of the substring. | +| length | number | Length of the substring. If not provided, the substring will end at the end of the input. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.substring() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns a substring of the results of this expression. + +Signature: + +```typescript +substring(position: Expression, length?: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| position | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression returning the index of the first character of the substring. | +| length | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression returning the length of the substring. If not provided the substring will end at the end of the input. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.subtract() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that subtracts another expression from this expression. + +```typescript +// Subtract the 'discount' field from the 'price' field +field("price").subtract(field("discount")); + +``` + +Signature: + +```typescript +subtract(subtrahend: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| subtrahend | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to subtract from this expression. A new Expr representing the subtraction operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.subtract() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that subtracts a constant value from this expression. + +```typescript +// Subtract 20 from the value of the 'total' field +field("total").subtract(20); + +``` + +Signature: + +```typescript +subtract(subtrahend: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| subtrahend | number | The constant value to subtract. A new Expr representing the subtraction operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.sum() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that calculates the sum of a numeric field across multiple stage inputs. + +```typescript +// Calculate the total revenue from a set of orders +field("orderAmount").sum().as("totalRevenue"); + +``` + A new `AggregateFunction` representing the 'sum' aggregation. + +Signature: + +```typescript +sum(): AggregateFunction; +``` +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +## Expression.timestampAdd() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that adds a specified amount of time to this timestamp expression. + +```typescript +// Add some duration determined by field 'unit' and 'amount' to the 'timestamp' field. +field("timestamp").timestampAdd(field("unit"), field("amount")); + +``` + +Signature: + +```typescript +timestampAdd(unit: Expression, amount: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| unit | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression evaluates to unit of time, must be one of 'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'. | +| amount | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression evaluates to amount of the unit. A new representing the resulting timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.timestampAdd() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that adds a specified amount of time to this timestamp expression. + +```typescript +// Add 1 day to the 'timestamp' field. +field("timestamp").timestampAdd("day", 1); + +``` + +Signature: + +```typescript +timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| unit | 'microsecond' \| 'millisecond' \| 'second' \| 'minute' \| 'hour' \| 'day' | The unit of time to add (e.g., "day", "hour"). | +| amount | number | The amount of time to add. A new representing the resulting timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.timestampSubtract() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that subtracts a specified amount of time from this timestamp expression. + +```typescript +// Subtract some duration determined by field 'unit' and 'amount' from the 'timestamp' field. +field("timestamp").timestampSubtract(field("unit"), field("amount")); + +``` + +Signature: + +```typescript +timestampSubtract(unit: Expression, amount: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| unit | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression evaluates to unit of time, must be one of 'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'. | +| amount | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression evaluates to amount of the unit. A new representing the resulting timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.timestampSubtract() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that subtracts a specified amount of time from this timestamp expression. + +```typescript +// Subtract 1 day from the 'timestamp' field. +field("timestamp").timestampSubtract("day", 1); + +``` + +Signature: + +```typescript +timestampSubtract(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| unit | 'microsecond' \| 'millisecond' \| 'second' \| 'minute' \| 'hour' \| 'day' | The unit of time to subtract (e.g., "day", "hour"). | +| amount | number | The amount of time to subtract. A new representing the resulting timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.timestampToUnixMicros() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that converts this timestamp expression to the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + +```typescript +// Convert the 'timestamp' field to microseconds since epoch. +field("timestamp").timestampToUnixMicros(); + +``` + A new representing the number of microseconds since epoch. + +Signature: + +```typescript +timestampToUnixMicros(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.timestampToUnixMillis() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that converts this timestamp expression to the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + +```typescript +// Convert the 'timestamp' field to milliseconds since epoch. +field("timestamp").timestampToUnixMillis(); + +``` + A new representing the number of milliseconds since epoch. + +Signature: + +```typescript +timestampToUnixMillis(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.timestampToUnixSeconds() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that converts this timestamp expression to the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC). + +```typescript +// Convert the 'timestamp' field to seconds since epoch. +field("timestamp").timestampToUnixSeconds(); + +``` + A new representing the number of seconds since epoch. + +Signature: + +```typescript +timestampToUnixSeconds(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.timestampTruncate() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that truncates a timestamp to a specified granularity. + +Signature: + +```typescript +timestampTruncate(granularity: TimeGranularity, timezone?: string | Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| granularity | [TimeGranularity](./firestore_pipelines.md#timegranularity) | The granularity to truncate to. | +| timezone | string \| [Expression](./firestore_pipelines.expression.md#expression_class) | The timezone to use for truncation. Valid values are from the TZ database (e.g., "America/Los\_Angeles") or in the format "Etc/GMT-1". A new {Expression} representing the truncated timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### Example + + +```typescript +// Truncate the 'createdAt' timestamp to the beginning of the day. +field('createdAt').timestampTruncate('day') + +``` + +## Expression.timestampTruncate() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that truncates a timestamp to a specified granularity. + +Signature: + +```typescript +timestampTruncate(granularity: Expression, timezone?: string | Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| granularity | [Expression](./firestore_pipelines.expression.md#expression_class) | The granularity to truncate to. | +| timezone | string \| [Expression](./firestore_pipelines.expression.md#expression_class) | The timezone to use for truncation. Valid values are from the TZ database (e.g., "America/Los\_Angeles") or in the format "Etc/GMT-1". A new {Expression} representing the truncated timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### Example + + +```typescript +// Truncate the 'createdAt' timestamp to the granularity specified in the field 'granularity'. +field('createdAt').timestampTruncate(field('granularity')) + +``` + +## Expression.toLower() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that converts a string to lowercase. + +```typescript +// Convert the 'name' field to lowercase +field("name").toLower(); + +``` + A new `Expr` representing the lowercase string. + +Signature: + +```typescript +toLower(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.toUpper() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that converts a string to uppercase. + +```typescript +// Convert the 'title' field to uppercase +field("title").toUpper(); + +``` + A new `Expr` representing the uppercase string. + +Signature: + +```typescript +toUpper(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.trim() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that removes leading and trailing characters from a string or byte array. + +```typescript +// Trim whitespace from the 'userInput' field +field("userInput").trim(); + +// Trim quotes from the 'userInput' field +field("userInput").trim('"'); + +``` + +Signature: + +```typescript +trim(valueToTrim?: string | Expression | Bytes): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| valueToTrim | string \| [Expression](./firestore_pipelines.expression.md#expression_class) \| [Bytes](./firestore_.bytes.md#bytes_class) | Optional This parameter is treated as a set of characters or bytes that will be trimmed from the input. If not specified, then whitespace will be trimmed. A new Expr representing the trimmed string or byte array. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.type() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the data type of this expression's result, as a string. + +Signature: + +```typescript +type(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### Example + + +```typescript +// Get the data type of the value in field 'title' +field('title').type() + +``` + A new {Expression} representing the data type. + +## Expression.unixMicrosToTimestamp() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that interprets this expression as the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. + +```typescript +// Interpret the 'microseconds' field as microseconds since epoch. +field("microseconds").unixMicrosToTimestamp(); + +``` + A new representing the timestamp. + +Signature: + +```typescript +unixMicrosToTimestamp(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.unixMillisToTimestamp() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that interprets this expression as the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. + +```typescript +// Interpret the 'milliseconds' field as milliseconds since epoch. +field("milliseconds").unixMillisToTimestamp(); + +``` + A new representing the timestamp. + +Signature: + +```typescript +unixMillisToTimestamp(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.unixSecondsToTimestamp() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that interprets this expression as the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. + +```typescript +// Interpret the 'seconds' field as seconds since epoch. +field("seconds").unixSecondsToTimestamp(); + +``` + A new representing the timestamp. + +Signature: + +```typescript +unixSecondsToTimestamp(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## Expression.vectorLength() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the length (number of dimensions) of this Firestore Vector expression. + +```typescript +// Get the vector length (dimension) of the field 'embedding'. +field("embedding").vectorLength(); + +``` + A new representing the length of the vector. + +Signature: + +```typescript +vectorLength(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + diff --git a/docs-devsite/firestore_pipelines.field.md b/docs-devsite/firestore_pipelines.field.md new file mode 100644 index 0000000000..de4e5fb7b2 --- /dev/null +++ b/docs-devsite/firestore_pipelines.field.md @@ -0,0 +1,103 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Field class +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Represents a reference to a field in a Firestore document, or outputs of a stage. + +

Field references are used to access document field values in expressions and to specify fields for sorting, filtering, and projecting data in Firestore pipelines. + +

You can create a `Field` instance using the static method: + +```typescript +// Create a Field instance for the 'name' field +const nameField = field("name"); + +// Create a Field instance for a nested field 'address.city' +const cityField = field("address.city"); + +``` + +Signature: + +```typescript +export declare class Field extends Expression implements Selectable +``` +Extends: [Expression](./firestore_pipelines.expression.md#expression_class) + +Implements: [Selectable](./firestore_pipelines.selectable.md#selectable_interface) + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [alias](./firestore_pipelines.field.md#fieldalias) | | string | (Public Preview) | +| [expr](./firestore_pipelines.field.md#fieldexpr) | | [Expression](./firestore_pipelines.expression.md#expression_class) | (Public Preview) | +| [expressionType](./firestore_pipelines.field.md#fieldexpressiontype) | | [ExpressionType](./firestore_pipelines.md#expressiontype) | (Public Preview) | +| [fieldName](./firestore_pipelines.field.md#fieldfieldname) | | string | (Public Preview) | +| [selectable](./firestore_pipelines.field.md#fieldselectable) | | true | (Public Preview) | + +## Field.alias + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +get alias(): string; +``` + +## Field.expr + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +get expr(): Expression; +``` + +## Field.expressionType + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +readonly expressionType: ExpressionType; +``` + +## Field.fieldName + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +get fieldName(): string; +``` + +## Field.selectable + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +selectable: true; +``` diff --git a/docs-devsite/firestore_pipelines.fieldpath.md b/docs-devsite/firestore_pipelines.fieldpath.md new file mode 100644 index 0000000000..38bfdadd73 --- /dev/null +++ b/docs-devsite/firestore_pipelines.fieldpath.md @@ -0,0 +1,72 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FieldPath class +A `FieldPath` refers to a field in a document. The path may consist of a single field name (referring to a top-level field in the document), or a list of field names (referring to a nested field in the document). + +Create a `FieldPath` by providing field names. If more than one field name is provided, the path will point to a nested field in a document. + +Signature: + +```typescript +export declare class FieldPath +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(fieldNames)](./firestore_pipelines.fieldpath.md#fieldpathconstructor) | | Creates a FieldPath from the provided field names. If more than one field name is provided, the path will point to a nested field in a document. | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [isEqual(other)](./firestore_pipelines.fieldpath.md#fieldpathisequal) | | Returns true if this FieldPath is equal to the provided one. | + +## FieldPath.(constructor) + +Creates a `FieldPath` from the provided field names. If more than one field name is provided, the path will point to a nested field in a document. + +Signature: + +```typescript +constructor(...fieldNames: string[]); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldNames | string\[\] | A list of field names. | + +## FieldPath.isEqual() + +Returns true if this `FieldPath` is equal to the provided one. + +Signature: + +```typescript +isEqual(other: FieldPath): boolean; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| other | [FieldPath](./firestore_.fieldpath.md#fieldpath_class) | The FieldPath to compare against. | + +Returns: + +boolean + +true if this `FieldPath` is equal to the provided one. + diff --git a/docs-devsite/firestore_pipelines.fieldvalue.md b/docs-devsite/firestore_pipelines.fieldvalue.md new file mode 100644 index 0000000000..2ca4ce713d --- /dev/null +++ b/docs-devsite/firestore_pipelines.fieldvalue.md @@ -0,0 +1,46 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FieldValue class +Sentinel values that can be used when writing document fields with `set()` or `update()`. + +Signature: + +```typescript +export declare abstract class FieldValue +``` + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [isEqual(other)](./firestore_pipelines.fieldvalue.md#fieldvalueisequal) | | Compares FieldValues for equality. | + +## FieldValue.isEqual() + +Compares `FieldValue`s for equality. + +Signature: + +```typescript +abstract isEqual(other: FieldValue): boolean; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| other | [FieldValue](./firestore_.fieldvalue.md#fieldvalue_class) | | + +Returns: + +boolean + diff --git a/docs-devsite/firestore_pipelines.firestore.md b/docs-devsite/firestore_pipelines.firestore.md new file mode 100644 index 0000000000..8d43257921 --- /dev/null +++ b/docs-devsite/firestore_pipelines.firestore.md @@ -0,0 +1,68 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Firestore class +The Cloud Firestore service interface. + +Do not call this constructor directly. Instead, use [getFirestore()](./firestore_.md#getfirestore). + +Signature: + +```typescript +export declare class Firestore +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [app](./firestore_pipelines.firestore.md#firestoreapp) | | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | The [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) associated with this Firestore service instance. | +| [type](./firestore_pipelines.firestore.md#firestoretype) | | 'firestore-lite' \| 'firestore' | Whether it's a [Firestore](./firestore_.firestore.md#firestore_class) or Firestore Lite instance. | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [toJSON()](./firestore_pipelines.firestore.md#firestoretojson) | | Returns a JSON-serializable representation of this Firestore instance. | + +## Firestore.app + +The [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) associated with this `Firestore` service instance. + +Signature: + +```typescript +get app(): FirebaseApp; +``` + +## Firestore.type + +Whether it's a [Firestore](./firestore_.firestore.md#firestore_class) or Firestore Lite instance. + +Signature: + +```typescript +type: 'firestore-lite' | 'firestore'; +``` + +## Firestore.toJSON() + +Returns a JSON-serializable representation of this `Firestore` instance. + +Signature: + +```typescript +toJSON(): object; +``` +Returns: + +object + diff --git a/docs-devsite/firestore_pipelines.firestoredataconverter.md b/docs-devsite/firestore_pipelines.firestoredataconverter.md new file mode 100644 index 0000000000..1be9d180bd --- /dev/null +++ b/docs-devsite/firestore_pipelines.firestoredataconverter.md @@ -0,0 +1,264 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FirestoreDataConverter interface +Converter used by `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. + +In this context, an "AppModel" is a class that is used in an application to package together related information and functionality. Such a class could, for example, have properties with complex, nested data types, properties used for memoization, properties of types not supported by Firestore (such as `symbol` and `bigint`), and helper functions that perform compound operations. Such classes are not suitable and/or possible to store into a Firestore database. Instead, instances of such classes need to be converted to "plain old JavaScript objects" (POJOs) with exclusively primitive properties, potentially nested inside other POJOs or arrays of POJOs. In this context, this type is referred to as the "DbModel" and would be an object suitable for persisting into Firestore. For convenience, applications can implement `FirestoreDataConverter` and register the converter with Firestore objects, such as `DocumentReference` or `Query`, to automatically convert `AppModel` to `DbModel` when storing into Firestore, and convert `DbModel` to `AppModel` when retrieving from Firestore. + +Signature: + +```typescript +export declare interface FirestoreDataConverter +``` + +## Methods + +| Method | Description | +| --- | --- | +| [fromFirestore(snapshot, options)](./firestore_pipelines.firestoredataconverter.md#firestoredataconverterfromfirestore) | Called by the Firestore SDK to convert Firestore data into an object of type AppModelType. You can access your data by calling: snapshot.data(options).Generally, the data returned from snapshot.data() can be cast to DbModelType; however, this is not guaranteed because Firestore does not enforce a schema on the database. For example, writes from a previous version of the application or writes from another client that did not use a type converter could have written data with different properties and/or property types. The implementation will need to choose whether to gracefully recover from non-conforming data or throw an error.To override this method, see . | +| [toFirestore(modelObject)](./firestore_pipelines.firestoredataconverter.md#firestoredataconvertertofirestore) | 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) of type DbModelType. To use set() with merge and mergeFields, toFirestore() must be defined with PartialWithFieldValue<AppModelType>.The WithFieldValue<T> type extends T to also allow FieldValues such as [deleteField()](./firestore_.md#deletefield) to be used as property values. | +| [toFirestore(modelObject, options)](./firestore_pipelines.firestoredataconverter.md#firestoredataconvertertofirestore) | 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) of type DbModelType. Used with [setDoc()](./firestore_.md#setdoc_ee215ad), and with merge:true or mergeFields.The PartialWithFieldValue<T> type extends Partial<T> to allow FieldValues such as [arrayUnion()](./firestore_.md#arrayunion_7d853aa) to be used as property values. It also supports nested Partial by allowing nested fields to be omitted. | + +## FirestoreDataConverter.fromFirestore() + +Called by the Firestore SDK to convert Firestore data into an object of type `AppModelType`. You can access your data by calling: `snapshot.data(options)`. + +Generally, the data returned from `snapshot.data()` can be cast to `DbModelType`; however, this is not guaranteed because Firestore does not enforce a schema on the database. For example, writes from a previous version of the application or writes from another client that did not use a type converter could have written data with different properties and/or property types. The implementation will need to choose whether to gracefully recover from non-conforming data or throw an error. + +To override this method, see . + +Signature: + +```typescript +fromFirestore(snapshot: QueryDocumentSnapshot, options?: SnapshotOptions): AppModelType; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| snapshot | [QueryDocumentSnapshot](./firestore_.querydocumentsnapshot.md#querydocumentsnapshot_class)<[DocumentData](./firestore_.documentdata.md#documentdata_interface), [DocumentData](./firestore_.documentdata.md#documentdata_interface)> | A QueryDocumentSnapshot containing your data and metadata. | +| options | [SnapshotOptions](./firestore_.snapshotoptions.md#snapshotoptions_interface) | The SnapshotOptions from the initial call to data(). | + +Returns: + +AppModelType + +## FirestoreDataConverter.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) of type `DbModelType`. To use `set()` with `merge` and `mergeFields`, `toFirestore()` must be defined with `PartialWithFieldValue`. + +The `WithFieldValue` type extends `T` to also allow FieldValues such as [deleteField()](./firestore_.md#deletefield) to be used as property values. + +Signature: + +```typescript +toFirestore(modelObject: WithFieldValue): WithFieldValue; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| modelObject | [WithFieldValue](./firestore_.md#withfieldvalue)<AppModelType> | | + +Returns: + +[WithFieldValue](./firestore_.md#withfieldvalue)<DbModelType> + +## FirestoreDataConverter.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) of type `DbModelType`. Used with [setDoc()](./firestore_.md#setdoc_ee215ad), and with `merge:true` or `mergeFields`. + +The `PartialWithFieldValue` type extends `Partial` to allow FieldValues such as [arrayUnion()](./firestore_.md#arrayunion_7d853aa) to be used as property values. It also supports nested `Partial` by allowing nested fields to be omitted. + +Signature: + +```typescript +toFirestore(modelObject: PartialWithFieldValue, options: SetOptions): PartialWithFieldValue; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| modelObject | [PartialWithFieldValue](./firestore_.md#partialwithfieldvalue)<AppModelType> | | +| options | [SetOptions](./firestore_.md#setoptions) | | + +Returns: + +[PartialWithFieldValue](./firestore_.md#partialwithfieldvalue)<DbModelType> + +### Example + +Simple Example + +```typescript +const numberConverter = { + toFirestore(value: WithFieldValue) { + return { value }; + }, + fromFirestore(snapshot: QueryDocumentSnapshot, options: SnapshotOptions) { + return snapshot.data(options).value as number; + } +}; + +async function simpleDemo(db: Firestore): Promise { + const documentRef = doc(db, 'values/value123').withConverter(numberConverter); + + // converters are used with `setDoc`, `addDoc`, and `getDoc` + await setDoc(documentRef, 42); + const snapshot1 = await getDoc(documentRef); + assertEqual(snapshot1.data(), 42); + + // converters are not used when writing data with `updateDoc` + await updateDoc(documentRef, { value: 999 }); + const snapshot2 = await getDoc(documentRef); + assertEqual(snapshot2.data(), 999); +} + +``` +Advanced Example + +```typescript +// The Post class is a model that is used by our application. +// This class may have properties and methods that are specific +// to our application execution, which do not need to be persisted +// to Firestore. +class Post { + constructor( + readonly title: string, + readonly author: string, + readonly lastUpdatedMillis: number + ) {} + toString(): string { + return `${this.title} by ${this.author}`; + } +} + +// The PostDbModel represents how we want our posts to be stored +// in Firestore. This DbModel has different properties (`ttl`, +// `aut`, and `lut`) from the Post class we use in our application. +interface PostDbModel { + ttl: string; + aut: { firstName: string; lastName: string }; + lut: Timestamp; +} + +// The `PostConverter` implements `FirestoreDataConverter` and specifies +// how the Firestore SDK can convert `Post` objects to `PostDbModel` +// objects and vice versa. +class PostConverter implements FirestoreDataConverter { + toFirestore(post: WithFieldValue): WithFieldValue { + return { + ttl: post.title, + aut: this._autFromAuthor(post.author), + lut: this._lutFromLastUpdatedMillis(post.lastUpdatedMillis) + }; + } + + fromFirestore(snapshot: QueryDocumentSnapshot, options: SnapshotOptions): Post { + const data = snapshot.data(options) as PostDbModel; + const author = `${data.aut.firstName} ${data.aut.lastName}`; + return new Post(data.ttl, author, data.lut.toMillis()); + } + + _autFromAuthor( + author: string | FieldValue + ): { firstName: string; lastName: string } | FieldValue { + if (typeof author !== 'string') { + // `author` is a FieldValue, so just return it. + return author; + } + const [firstName, lastName] = author.split(' '); + return {firstName, lastName}; + } + + _lutFromLastUpdatedMillis( + lastUpdatedMillis: number | FieldValue + ): Timestamp | FieldValue { + if (typeof lastUpdatedMillis !== 'number') { + // `lastUpdatedMillis` must be a FieldValue, so just return it. + return lastUpdatedMillis; + } + return Timestamp.fromMillis(lastUpdatedMillis); + } +} + +async function advancedDemo(db: Firestore): Promise { + // Create a `DocumentReference` with a `FirestoreDataConverter`. + const documentRef = doc(db, 'posts/post123').withConverter(new PostConverter()); + + // The `data` argument specified to `setDoc()` is type checked by the + // TypeScript compiler to be compatible with `Post`. Since the `data` + // argument is typed as `WithFieldValue` rather than just `Post`, + // this allows properties of the `data` argument to also be special + // Firestore values that perform server-side mutations, such as + // `arrayRemove()`, `deleteField()`, and `serverTimestamp()`. + await setDoc(documentRef, { + title: 'My Life', + author: 'Foo Bar', + lastUpdatedMillis: serverTimestamp() + }); + + // The TypeScript compiler will fail to compile if the `data` argument to + // `setDoc()` is _not_ compatible with `WithFieldValue`. This + // type checking prevents the caller from specifying objects with incorrect + // properties or property values. + // @ts-expect-error "Argument of type { ttl: string; } is not assignable + // to parameter of type WithFieldValue" + await setDoc(documentRef, { ttl: 'The Title' }); + + // When retrieving a document with `getDoc()` the `DocumentSnapshot` + // object's `data()` method returns a `Post`, rather than a generic object, + // which would have been returned if the `DocumentReference` did _not_ have a + // `FirestoreDataConverter` attached to it. + const snapshot1: DocumentSnapshot = await getDoc(documentRef); + const post1: Post = snapshot1.data()!; + if (post1) { + assertEqual(post1.title, 'My Life'); + assertEqual(post1.author, 'Foo Bar'); + } + + // The `data` argument specified to `updateDoc()` is type checked by the + // TypeScript compiler to be compatible with `PostDbModel`. Note that + // unlike `setDoc()`, whose `data` argument must be compatible with `Post`, + // the `data` argument to `updateDoc()` must be compatible with + // `PostDbModel`. Similar to `setDoc()`, since the `data` argument is typed + // as `WithFieldValue` rather than just `PostDbModel`, this + // allows properties of the `data` argument to also be those special + // Firestore values, like `arrayRemove()`, `deleteField()`, and + // `serverTimestamp()`. + await updateDoc(documentRef, { + 'aut.firstName': 'NewFirstName', + lut: serverTimestamp() + }); + + // The TypeScript compiler will fail to compile if the `data` argument to + // `updateDoc()` is _not_ compatible with `WithFieldValue`. + // This type checking prevents the caller from specifying objects with + // incorrect properties or property values. + // @ts-expect-error "Argument of type { title: string; } is not assignable + // to parameter of type WithFieldValue" + await updateDoc(documentRef, { title: 'New Title' }); + const snapshot2: DocumentSnapshot = await getDoc(documentRef); + const post2: Post = snapshot2.data()!; + if (post2) { + assertEqual(post2.title, 'My Life'); + assertEqual(post2.author, 'NewFirstName Bar'); + } +} + +``` + diff --git a/docs-devsite/firestore_pipelines.functionexpression.md b/docs-devsite/firestore_pipelines.functionexpression.md new file mode 100644 index 0000000000..6ba0bb3367 --- /dev/null +++ b/docs-devsite/firestore_pipelines.functionexpression.md @@ -0,0 +1,90 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FunctionExpression class +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +This class defines the base class for Firestore functions, which can be evaluated within pipeline execution. + +Typically, you would not use this class or its children directly. Use either the functions like [and()](./firestore_.md#and_e72c712), , or the methods on (, , etc.) to construct new Function instances. + +Signature: + +```typescript +export declare class FunctionExpression extends Expression +``` +Extends: [Expression](./firestore_pipelines.expression.md#expression_class) + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(name, params)](./firestore_pipelines.functionexpression.md#functionexpressionconstructor) | | (Public Preview) Constructs a new instance of the FunctionExpression class | +| [(constructor)(name, params, \_methodName)](./firestore_pipelines.functionexpression.md#functionexpressionconstructor) | | (Public Preview) Constructs a new instance of the FunctionExpression class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [expressionType](./firestore_pipelines.functionexpression.md#functionexpressionexpressiontype) | | [ExpressionType](./firestore_pipelines.md#expressiontype) | (Public Preview) | + +## FunctionExpression.(constructor) + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Constructs a new instance of the `FunctionExpression` class + +Signature: + +```typescript +constructor(name: string, params: Expression[]); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | +| params | [Expression](./firestore_pipelines.expression.md#expression_class)\[\] | | + +## FunctionExpression.(constructor) + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Constructs a new instance of the `FunctionExpression` class + +Signature: + +```typescript +constructor(name: string, params: Expression[], _methodName: string | undefined); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | +| params | [Expression](./firestore_pipelines.expression.md#expression_class)\[\] | | +| \_methodName | string \| undefined | | + +## FunctionExpression.expressionType + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +readonly expressionType: ExpressionType; +``` diff --git a/docs-devsite/firestore_pipelines.geopoint.md b/docs-devsite/firestore_pipelines.geopoint.md new file mode 100644 index 0000000000..d8563428fb --- /dev/null +++ b/docs-devsite/firestore_pipelines.geopoint.md @@ -0,0 +1,143 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# GeoPoint class +An immutable object representing a geographic location in Firestore. The location is represented as latitude/longitude pair. + +Latitude values are in the range of \[-90, 90\]. Longitude values are in the range of \[-180, 180\]. + +Signature: + +```typescript +export declare class GeoPoint +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(latitude, longitude)](./firestore_pipelines.geopoint.md#geopointconstructor) | | Creates a new immutable GeoPoint object with the provided latitude and longitude values. | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [latitude](./firestore_pipelines.geopoint.md#geopointlatitude) | | number | The latitude of this GeoPoint instance. | +| [longitude](./firestore_pipelines.geopoint.md#geopointlongitude) | | number | The longitude of this GeoPoint instance. | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [fromJSON(json)](./firestore_pipelines.geopoint.md#geopointfromjson) | static | Builds a GeoPoint instance from a JSON object created by [GeoPoint.toJSON()](./firestore_.geopoint.md#geopointtojson). | +| [isEqual(other)](./firestore_pipelines.geopoint.md#geopointisequal) | | Returns true if this GeoPoint is equal to the provided one. | +| [toJSON()](./firestore_pipelines.geopoint.md#geopointtojson) | | Returns a JSON-serializable representation of this GeoPoint instance. | + +## GeoPoint.(constructor) + +Creates a new immutable `GeoPoint` object with the provided latitude and longitude values. + +Signature: + +```typescript +constructor(latitude: number, longitude: number); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| latitude | number | The latitude as number between -90 and 90. | +| longitude | number | The longitude as number between -180 and 180. | + +## GeoPoint.latitude + +The latitude of this `GeoPoint` instance. + +Signature: + +```typescript +get latitude(): number; +``` + +## GeoPoint.longitude + +The longitude of this `GeoPoint` instance. + +Signature: + +```typescript +get longitude(): number; +``` + +## GeoPoint.fromJSON() + +Builds a `GeoPoint` instance from a JSON object created by [GeoPoint.toJSON()](./firestore_.geopoint.md#geopointtojson). + +Signature: + +```typescript +static fromJSON(json: object): GeoPoint; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| json | object | a JSON object represention of a GeoPoint instance | + +Returns: + +[GeoPoint](./firestore_.geopoint.md#geopoint_class) + +an instance of [GeoPoint](./firestore_.geopoint.md#geopoint_class) if the JSON object could be parsed. Throws a [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class) if an error occurs. + +## GeoPoint.isEqual() + +Returns true if this `GeoPoint` is equal to the provided one. + +Signature: + +```typescript +isEqual(other: GeoPoint): boolean; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| other | [GeoPoint](./firestore_.geopoint.md#geopoint_class) | The GeoPoint to compare against. | + +Returns: + +boolean + +true if this `GeoPoint` is equal to the provided one. + +## GeoPoint.toJSON() + +Returns a JSON-serializable representation of this `GeoPoint` instance. + +Signature: + +```typescript +toJSON(): { + latitude: number; + longitude: number; + type: string; + }; +``` +Returns: + +{ latitude: number; longitude: number; type: string; } + +a JSON representation of this object. + diff --git a/docs-devsite/firestore_pipelines.md b/docs-devsite/firestore_pipelines.md new file mode 100644 index 0000000000..2a3ba65e1f --- /dev/null +++ b/docs-devsite/firestore_pipelines.md @@ -0,0 +1,9899 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# @firebase/firestore/pipelines + +## Functions + +| Function | Description | +| --- | --- | +| function() | +| [countAll()](./firestore_pipelines.md#countall) | (Public Preview) Creates an aggregation that counts the total number of stage inputs. +```typescript +// Count the total number of input documents +countAll().as("totalDocument"); + +``` + A new representing the 'countAll' aggregation. | +| [currentTimestamp()](./firestore_pipelines.md#currenttimestamp) | (Public Preview) Creates an expression that evaluates to the current server timestamp. +```typescript +// Get the current server timestamp +currentTimestamp() + +``` + A new Expression representing the current server timestamp. | +| function(array, ...) | +| [arrayContains(array, element)](./firestore_pipelines.md#arraycontains_a00ea48) | (Public Preview) Creates an expression that checks if an array expression contains a specific element. +```typescript +// Check if the 'colors' array contains the value of field 'selectedColor' +arrayContains(field("colors"), field("selectedColor")); + +``` + | +| [arrayContains(array, element)](./firestore_pipelines.md#arraycontains_7328608) | (Public Preview) Creates an expression that checks if an array expression contains a specific element. +```typescript +// Check if the 'colors' array contains "red" +arrayContains(field("colors"), "red"); + +``` + | +| [arrayContainsAll(array, values)](./firestore_pipelines.md#arraycontainsall_c658ad5) | (Public Preview) Creates an expression that checks if an array expression contains all the specified elements. +```typescript +// Check if the "tags" array contains all of the values: "SciFi", "Adventure", and the value from field "tag1" +arrayContainsAll(field("tags"), [field("tag1"), constant("SciFi"), "Adventure"]); + +``` + | +| [arrayContainsAll(array, arrayExpression)](./firestore_pipelines.md#arraycontainsall_7b535db) | (Public Preview) Creates an expression that checks if an array expression contains all the specified elements. +```typescript +// Check if the "tags" array contains all of the values: "SciFi", "Adventure", and the value from field "tag1" +arrayContainsAll(field("tags"), [field("tag1"), constant("SciFi"), "Adventure"]); + +``` + | +| [arrayContainsAny(array, values)](./firestore_pipelines.md#arraycontainsany_c658ad5) | (Public Preview) Creates an expression that checks if an array expression contains any of the specified elements. +```typescript +// Check if the 'categories' array contains either values from field "cate1" or "Science" +arrayContainsAny(field("categories"), [field("cate1"), "Science"]); + +``` + | +| [arrayContainsAny(array, values)](./firestore_pipelines.md#arraycontainsany_c381a96) | (Public Preview) Creates an expression that checks if an array expression contains any of the specified elements. +```typescript +// Check if the 'categories' array contains either values from field "cate1" or "Science" +arrayContainsAny(field("categories"), array([field("cate1"), "Science"])); + +``` + | +| [arrayLength(array)](./firestore_pipelines.md#arraylength_195e339) | (Public Preview) Creates an expression that calculates the length of an array expression. +```typescript +// Get the number of items in the 'cart' array +arrayLength(field("cart")); + +``` + | +| function(arrayExpression, ...) | +| [arrayGet(arrayExpression, offset)](./firestore_pipelines.md#arrayget_f2e27cc) | (Public Preview) Creates an expression that indexes into an array from the beginning or end and return the element. If the offset exceeds the array length, an error is returned. A negative offset, starts from the end. +```typescript +// Return the value in the tags field array at index 1. +arrayGet(field('tags'), 1); + +``` + | +| [arrayGet(arrayExpression, offsetExpr)](./firestore_pipelines.md#arrayget_484550d) | (Public Preview) Creates an expression that indexes into an array from the beginning or end and return the element. If the offset exceeds the array length, an error is returned. A negative offset, starts from the end. +```typescript +// Return the value in the tags field array at index specified by field +// 'favoriteTag'. +arrayGet(field('tags'), field('favoriteTag')); + +``` + | +| [join(arrayExpression, delimiterExpression)](./firestore_pipelines.md#join_313e6aa) | (Public Preview) Creates an expression that joins the elements of an array into a string. +```typescript +// Join an array of string using the delimiter from the 'separator' field. +join(array(['foo', 'bar']), field("separator")) + +``` + | +| [join(arrayExpression, delimiter)](./firestore_pipelines.md#join_d088d29) | (Public Preview) Creates an expression that joins the elements of an array into a string. +```typescript +// Join the elements of the 'tags' field with a comma and space. +join(field("tags"), ", ") + +``` + | +| function(arrayField, ...) | +| [arrayGet(arrayField, offset)](./firestore_pipelines.md#arrayget_3f58471) | (Public Preview) Creates an expression that indexes into an array from the beginning or end and return the element. If the offset exceeds the array length, an error is returned. A negative offset, starts from the end. +```typescript +// Return the value in the tags field array at index 1. +arrayGet('tags', 1); + +``` + | +| [arrayGet(arrayField, offsetExpr)](./firestore_pipelines.md#arrayget_1904c9a) | (Public Preview) Creates an expression that indexes into an array from the beginning or end and return the element. If the offset exceeds the array length, an error is returned. A negative offset, starts from the end. +```typescript +// Return the value in the tags field array at index specified by field +// 'favoriteTag'. +arrayGet('tags', field('favoriteTag')); + +``` + | +| function(arrayFieldName, ...) | +| [join(arrayFieldName, delimiter)](./firestore_pipelines.md#join_478ef36) | (Public Preview) Creates an expression that joins the elements of an array into a string. +```typescript +// Join the elements of the 'tags' field with a comma and space. +join("tags", ", ") + +``` + | +| [join(arrayFieldName, delimiterExpression)](./firestore_pipelines.md#join_829294c) | (Public Preview) Creates an expression that joins the elements of an array into a string. +```typescript +// Join the elements of the 'tags' field with the delimiter from the 'separator' field. +join('tags', field("separator")) + +``` + | +| function(base, ...) | +| [pow(base, exponent)](./firestore_pipelines.md#pow_e4a9e64) | (Public Preview) Creates an expression that returns the value of the base expression raised to the power of the exponent expression. +```typescript +// Raise the value of the 'base' field to the power of the 'exponent' field. +pow(field("base"), field("exponent")); + +``` + | +| [pow(base, exponent)](./firestore_pipelines.md#pow_93eae7f) | (Public Preview) Creates an expression that returns the value of the base expression raised to the power of the exponent. +```typescript +// Raise the value of the 'base' field to the power of 2. +pow(field("base"), 2); + +``` + | +| [pow(base, exponent)](./firestore_pipelines.md#pow_a237721) | (Public Preview) Creates an expression that returns the value of the base field raised to the power of the exponent expression. +```typescript +// Raise the value of the 'base' field to the power of the 'exponent' field. +pow("base", field("exponent")); + +``` + | +| [pow(base, exponent)](./firestore_pipelines.md#pow_f4d7908) | (Public Preview) Creates an expression that returns the value of the base field raised to the power of the exponent. +```typescript +// Raise the value of the 'base' field to the power of 2. +pow("base", 2); + +``` + | +| function(booleanExpr, ...) | +| [countIf(booleanExpr)](./firestore_pipelines.md#countif_c5b8fb1) | (Public Preview) Creates an aggregation that counts the number of stage inputs where the provided boolean expression evaluates to true. +```typescript +// Count the number of documents where 'is_active' field equals true +countIf(field("is_active").equal(true)).as("numActiveDocuments"); + +``` + | +| [not(booleanExpr)](./firestore_pipelines.md#not_c5b8fb1) | (Public Preview) Creates an expression that negates a filter condition. +```typescript +// Find documents where the 'completed' field is NOT true +not(equal("completed", true)); + +``` + | +| function(condition, ...) | +| [conditional(condition, thenExpr, elseExpr)](./firestore_pipelines.md#conditional_07a206d) | (Public Preview) Creates a conditional expression that evaluates to a 'then' expression if a condition is true and an 'else' expression if the condition is false. +```typescript +// If 'age' is greater than 18, return "Adult"; otherwise, return "Minor". +conditional( + greaterThan("age", 18), constant("Adult"), constant("Minor")); + +``` + | +| function(documentPath, ...) | +| [documentId(documentPath)](./firestore_pipelines.md#documentid_cef293c) | (Public Preview) Creates an expression that returns the document ID from a path. +```typescript +// Get the document ID from a path. +documentId(myDocumentReference); + +``` + A new representing the documentId operation. | +| function(documentPathExpr, ...) | +| [documentId(documentPathExpr)](./firestore_pipelines.md#documentid_9a69021) | (Public Preview) Creates an expression that returns the document ID from a path. +```typescript +// Get the document ID from a path. +documentId(field("__path__")); + +``` + A new representing the documentId operation. | +| function(element, ...) | +| [notEqualAny(element, values)](./firestore_pipelines.md#notequalany_c2c5bcb) | (Public Preview) Creates an expression that checks if an expression is not equal to any of the provided values or expressions. +```typescript +// Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' +notEqualAny(field("status"), ["pending", field("rejectedStatus")]); + +``` + | +| [notEqualAny(element, arrayExpression)](./firestore_pipelines.md#notequalany_16b2851) | (Public Preview) Creates an expression that checks if an expression is not equal to any of the provided values or expressions. +```typescript +// Check if the 'status' field is neither "pending" nor the value of the field 'rejectedStatus' +notEqualAny(field("status"), ["pending", field("rejectedStatus")]); + +``` + | +| function(elements, ...) | +| [array(elements)](./firestore_pipelines.md#array_7d853aa) | (Public Preview) Creates an expression that creates a Firestore array value from an input array. +```typescript +// Create an array value from the input array and reference the 'baz' field value from the input document. +array(['bar', Field.of('baz')]).as('foo'); + +``` + | +| [map(elements)](./firestore_pipelines.md#map_ce5dee1) | (Public Preview) Creates an expression that creates a Firestore map value from an input object. +```typescript +// Create a map from the input object and reference the 'baz' field value from the input document. +map({foo: 'bar', baz: Field.of('baz')}).as('data'); + +``` + | +| function(expr, ...) | +| [abs(expr)](./firestore_pipelines.md#abs_005f3d4) | (Public Preview) Creates an expression that computes the absolute value of a numeric value. | +| [ascending(expr)](./firestore_pipelines.md#ascending_005f3d4) | (Public Preview) Creates an that sorts documents in ascending order based on an expression. +```typescript +// Sort documents by the 'name' field in lowercase in ascending order +firestore.pipeline().collection("users") + .sort(ascending(field("name").toLower())); + +``` + | +| [byteLength(expr)](./firestore_pipelines.md#bytelength_005f3d4) | (Public Preview) Creates an expression that calculates the byte length of a string in UTF-8, or just the length of a Blob. +```typescript +// Calculate the length of the 'myString' field in bytes. +byteLength(field("myString")); + +``` + | +| [countDistinct(expr)](./firestore_pipelines.md#countdistinct_3c28b08) | (Public Preview) Creates an aggregation that counts the number of distinct values of a field. | +| [descending(expr)](./firestore_pipelines.md#descending_005f3d4) | (Public Preview) Creates an that sorts documents in descending order based on an expression. +```typescript +// Sort documents by the 'name' field in lowercase in descending order +firestore.pipeline().collection("users") + .sort(descending(field("name").toLower())); + +``` + | +| [floor(expr)](./firestore_pipelines.md#floor_005f3d4) | (Public Preview) Creates an expression that computes the floor of a numeric value. | +| [timestampToUnixMicros(expr)](./firestore_pipelines.md#timestamptounixmicros_005f3d4) | (Public Preview) Creates an expression that converts a timestamp expression to the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC). +```typescript +// Convert the 'timestamp' field to microseconds since epoch. +timestampToUnixMicros(field("timestamp")); + +``` + | +| [timestampToUnixMillis(expr)](./firestore_pipelines.md#timestamptounixmillis_005f3d4) | (Public Preview) Creates an expression that converts a timestamp expression to the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC). +```typescript +// Convert the 'timestamp' field to milliseconds since epoch. +timestampToUnixMillis(field("timestamp")); + +``` + | +| [timestampToUnixSeconds(expr)](./firestore_pipelines.md#timestamptounixseconds_005f3d4) | (Public Preview) Creates an expression that converts a timestamp expression to the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC). +```typescript +// Convert the 'timestamp' field to seconds since epoch. +timestampToUnixSeconds(field("timestamp")); + +``` + | +| [unixMicrosToTimestamp(expr)](./firestore_pipelines.md#unixmicrostotimestamp_005f3d4) | (Public Preview) Creates an expression that interprets an expression as the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. +```typescript +// Interpret the 'microseconds' field as microseconds since epoch. +unixMicrosToTimestamp(field("microseconds")); + +``` + | +| [unixMillisToTimestamp(expr)](./firestore_pipelines.md#unixmillistotimestamp_005f3d4) | (Public Preview) Creates an expression that interprets an expression as the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. +```typescript +// Interpret the 'milliseconds' field as milliseconds since epoch. +unixMillisToTimestamp(field("milliseconds")); + +``` + | +| [unixSecondsToTimestamp(expr)](./firestore_pipelines.md#unixsecondstotimestamp_005f3d4) | (Public Preview) Creates an expression that interprets an expression as the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. +```typescript +// Interpret the 'seconds' field as seconds since epoch. +unixSecondsToTimestamp(field("seconds")); + +``` + | +| function(expression, ...) | +| [arraySum(expression)](./firestore_pipelines.md#arraysum_1138a27) | (Public Preview) Creates an expression that computes the sum of the elements in an array. +```typescript +// Compute the sum of the elements in the 'scores' field. +arraySum(field("scores")); + +``` + | +| [average(expression)](./firestore_pipelines.md#average_1138a27) | (Public Preview) Creates an aggregation that calculates the average (mean) of values from an expression across multiple stage inputs. +```typescript +// Calculate the average age of users +average(field("age")).as("averageAge"); + +``` + | +| [ceil(expression)](./firestore_pipelines.md#ceil_1138a27) | (Public Preview) Creates an expression that computes the ceiling of a numeric value. +```typescript +// Compute the ceiling of the 'price' field. +ceil(field("price")); + +``` + | +| [collectionId(expression)](./firestore_pipelines.md#collectionid_1138a27) | (Public Preview) Creates an expression that returns the collection ID from a path. +```typescript +// Get the collection ID from a path. +collectionId(field("__name__")); + +``` + | +| [count(expression)](./firestore_pipelines.md#count_1138a27) | (Public Preview) Creates an aggregation that counts the number of stage inputs with valid evaluations of the provided expression. +```typescript +// Count the number of items where the price is greater than 10 +count(field("price").greaterThan(10)).as("expensiveItemCount"); + +``` + | +| [divide(expression, value)](./firestore_pipelines.md#divide_01df3cf) | (Public Preview) Creates an expression that divides an expression by a constant value. +```typescript +// Divide the 'value' field by 10 +divide(field("value"), 10); + +``` + | +| [equal(expression, value)](./firestore_pipelines.md#equal_01df3cf) | (Public Preview) Creates an expression that checks if an expression is equal to a constant value. +```typescript +// Check if the 'age' field is equal to 21 +equal(field("age"), 21); + +``` + | +| [equalAny(expression, values)](./firestore_pipelines.md#equalany_7e759b5) | (Public Preview) Creates an expression that checks if an expression, when evaluated, is equal to any of the provided values or expressions. +```typescript +// Check if the 'category' field is either "Electronics" or value of field 'primaryType' +equalAny(field("category"), [constant("Electronics"), field("primaryType")]); + +``` + | +| [equalAny(expression, arrayExpression)](./firestore_pipelines.md#equalany_214ce68) | (Public Preview) Creates an expression that checks if an expression is equal to any of the provided values. +```typescript +// Check if the 'category' field is set to a value in the disabledCategories field +equalAny(field("category"), field('disabledCategories')); + +``` + | +| [exp(expression)](./firestore_pipelines.md#exp_1138a27) | (Public Preview) Creates an expression that computes e to the power of the expression's result. +```typescript +// Compute e to the power of 2. +exp(constant(2)); + +``` + A new representing the exp of the numeric value. | +| [greaterThan(expression, value)](./firestore_pipelines.md#greaterthan_01df3cf) | (Public Preview) Creates an expression that checks if an expression is greater than a constant value. +```typescript +// Check if the 'age' field is greater than 18 +greaterThan(field("age"), 18); + +``` + | +| [greaterThanOrEqual(expression, value)](./firestore_pipelines.md#greaterthanorequal_01df3cf) | (Public Preview) Creates an expression that checks if an expression is greater than or equal to a constant value. +```typescript +// Check if the 'quantity' field is greater than or equal to 10 +greaterThanOrEqual(field("quantity"), 10); + +``` + | +| [length\_2(expression)](./firestore_pipelines.md#length_2_1138a27) | (Public Preview) Creates an expression that calculates the length of a string, array, map, vector, or bytes. +```typescript +// Get the length of the 'name' field. +length(field("name")); + +// Get the number of items in the 'cart' array. +length(field("cart")); + +``` + | +| [lessThan(expression, value)](./firestore_pipelines.md#lessthan_01df3cf) | (Public Preview) Creates an expression that checks if an expression is less than a constant value. +```typescript +// Check if the 'age' field is less than 30 +lessThan(field("age"), 30); + +``` + | +| [lessThanOrEqual(expression, value)](./firestore_pipelines.md#lessthanorequal_01df3cf) | (Public Preview) Creates an expression that checks if an expression is less than or equal to a constant value. +```typescript +// Check if the 'quantity' field is less than or equal to 20 +lessThan(field("quantity"), 20); + +``` + | +| [ln(expression)](./firestore_pipelines.md#ln_1138a27) | (Public Preview) Creates an expression that computes the natural logarithm of a numeric value. +```typescript +// Compute the natural logarithm of the 'value' field. +ln(field("value")); + +``` + | +| [log(expression, base)](./firestore_pipelines.md#log_ac183e2) | (Public Preview) Creates an expression that computes the logarithm of an expression to a given base. +```typescript +// Compute the logarithm of the 'value' field with base 10. +log(field("value"), 10); + +``` + | +| [log(expression, base)](./firestore_pipelines.md#log_1894737) | (Public Preview) Creates an expression that computes the logarithm of an expression to a given base. +```typescript +// Compute the logarithm of the 'value' field with the base in the 'base' field. +log(field("value"), field("base")); + +``` + | +| [log10(expression)](./firestore_pipelines.md#log10_1138a27) | (Public Preview) Creates an expression that computes the base-10 logarithm of a numeric value. +```typescript +// Compute the base-10 logarithm of the 'value' field. +log10(field("value")); + +``` + | +| [maximum(expression)](./firestore_pipelines.md#maximum_1138a27) | (Public Preview) Creates an aggregation that finds the maximum value of an expression across multiple stage inputs. +```typescript +// Find the highest score in a leaderboard +maximum(field("score")).as("highestScore"); + +``` + | +| [minimum(expression)](./firestore_pipelines.md#minimum_1138a27) | (Public Preview) Creates an aggregation that finds the minimum value of an expression across multiple stage inputs. +```typescript +// Find the lowest price of all products +minimum(field("price")).as("lowestPrice"); + +``` + | +| [mod(expression, value)](./firestore_pipelines.md#mod_01df3cf) | (Public Preview) Creates an expression that calculates the modulo (remainder) of dividing an expression by a constant. +```typescript +// Calculate the remainder of dividing 'field1' by 5. +mod(field("field1"), 5); + +``` + | +| [notEqual(expression, value)](./firestore_pipelines.md#notequal_01df3cf) | (Public Preview) Creates an expression that checks if an expression is not equal to a constant value. +```typescript +// Check if the 'status' field is not equal to "completed" +notEqual(field("status"), "completed"); + +``` + | +| [round(expression)](./firestore_pipelines.md#round_1138a27) | (Public Preview) Creates an expression that rounds a numeric value to the nearest whole number. +```typescript +// Round the value of the 'price' field. +round(field("price")); + +``` + | +| [round(expression, decimalPlaces)](./firestore_pipelines.md#round_a3a92d0) | (Public Preview) Creates an expression that rounds a numeric value to the specified number of decimal places. +```typescript +// Round the value of the 'price' field to two decimal places. +round(field("price"), constant(2)); + +``` + | +| [split(expression, delimiter)](./firestore_pipelines.md#split_5b5612b) | (Public Preview) Creates an expression that splits a string into an array of substrings based on the provided delimiter. | +| [split(expression, delimiter)](./firestore_pipelines.md#split_5a171ed) | (Public Preview) Creates an expression that splits a string into an array of substrings based on the provided delimiter. | +| [sqrt(expression)](./firestore_pipelines.md#sqrt_1138a27) | (Public Preview) Creates an expression that computes the square root of a numeric value. +```typescript +// Compute the square root of the 'value' field. +sqrt(field("value")); + +``` + | +| [subtract(expression, value)](./firestore_pipelines.md#subtract_01df3cf) | (Public Preview) Creates an expression that subtracts a constant value from an expression. +```typescript +// Subtract the constant value 2 from the 'value' field +subtract(field("value"), 2); + +``` + | +| [sum(expression)](./firestore_pipelines.md#sum_1138a27) | (Public Preview) Creates an aggregation that calculates the sum of values from an expression across multiple stage inputs. +```typescript +// Calculate the total revenue from a set of orders +sum(field("orderAmount")).as("totalRevenue"); + +``` + | +| [type(expression)](./firestore_pipelines.md#type_1138a27) | (Public Preview) Creates an expression that returns the data type of an expression's result. | +| function(field, ...) | +| [isAbsent(field)](./firestore_pipelines.md#isabsent_0fb8cd4) | (Public Preview) Creates an expression that returns true if a field is absent. Otherwise, returns false even if the field value is null. +```typescript +// Check if the field `value` is absent. +isAbsent("value"); + +``` + | +| [reverse(field)](./firestore_pipelines.md#reverse_0fb8cd4) | (Public Preview) Creates an expression that reverses a string value in the specified field. +```typescript +// Reverse the value of the 'myString' field. +reverse("myString"); + +``` + | +| [stringReverse(field)](./firestore_pipelines.md#stringreverse_0fb8cd4) | (Public Preview) Creates an expression that reverses a string value in the specified field. +```typescript +// Reverse the value of the 'myString' field. +strReverse("myString"); + +``` + | +| [substring(field, position, length)](./firestore_pipelines.md#substring_0d9573a) | (Public Preview) Creates an expression that returns a substring of a string or byte array. | +| [substring(field, position, length)](./firestore_pipelines.md#substring_05cb14e) | (Public Preview) Creates an expression that returns a substring of a string or byte array. | +| function(fieldName, ...) | +| [abs(fieldName)](./firestore_pipelines.md#abs_e5b0480) | (Public Preview) Creates an expression that computes the absolute value of a numeric value. | +| [add(fieldName, second)](./firestore_pipelines.md#add_b75bb8b) | (Public Preview) Creates an expression that adds a field's value to an expression. +```typescript +// Add the value of the 'quantity' field and the 'reserve' field. +add("quantity", field("reserve")); + +``` + | +| [arrayContains(fieldName, element)](./firestore_pipelines.md#arraycontains_aaace4a) | (Public Preview) Creates an expression that checks if a field's array value contains a specific element. +```typescript +// Check if the 'colors' array contains the value of field 'selectedColor' +arrayContains("colors", field("selectedColor")); + +``` + | +| [arrayContains(fieldName, element)](./firestore_pipelines.md#arraycontains_999590f) | (Public Preview) Creates an expression that checks if a field's array value contains a specific value. +```typescript +// Check if the 'colors' array contains "red" +arrayContains("colors", "red"); + +``` + | +| [arrayContainsAll(fieldName, values)](./firestore_pipelines.md#arraycontainsall_8060b23) | (Public Preview) Creates an expression that checks if a field's array value contains all the specified values or expressions. +```typescript +// Check if the 'tags' array contains both of the values from field 'tag1', the value "SciFi", and "Adventure" +arrayContainsAll("tags", [field("tag1"), "SciFi", "Adventure"]); + +``` + | +| [arrayContainsAll(fieldName, arrayExpression)](./firestore_pipelines.md#arraycontainsall_48da8d9) | (Public Preview) Creates an expression that checks if a field's array value contains all the specified values or expressions. +```typescript +// Check if the 'tags' array contains both of the values from field 'tag1', the value "SciFi", and "Adventure" +arrayContainsAll("tags", [field("tag1"), "SciFi", "Adventure"]); + +``` + | +| [arrayContainsAny(fieldName, values)](./firestore_pipelines.md#arraycontainsany_8060b23) | (Public Preview) Creates an expression that checks if a field's array value contains any of the specified elements. +```typescript +// Check if the 'groups' array contains either the value from the 'userGroup' field +// or the value "guest" +arrayContainsAny("categories", [field("cate1"), "Science"]); + +``` + | +| [arrayContainsAny(fieldName, values)](./firestore_pipelines.md#arraycontainsany_1b4f7cd) | (Public Preview) Creates an expression that checks if a field's array value contains any of the specified elements. +```typescript +// Check if the 'groups' array contains either the value from the 'userGroup' field +// or the value "guest" +arrayContainsAny("categories", array([field("cate1"), "Science"])); + +``` + | +| [arrayLength(fieldName)](./firestore_pipelines.md#arraylength_e5b0480) | (Public Preview) Creates an expression that calculates the length of an array in a specified field. +```typescript +// Get the number of items in field 'cart' +arrayLength('cart'); + +``` + | +| [arraySum(fieldName)](./firestore_pipelines.md#arraysum_e5b0480) | (Public Preview) Creates an expression that computes the sum of the elements in an array. +```typescript +// Compute the sum of the elements in the 'scores' field. +arraySum("scores"); + +``` + | +| [ascending(fieldName)](./firestore_pipelines.md#ascending_e5b0480) | (Public Preview) Creates an that sorts documents in ascending order based on a field. +```typescript +// Sort documents by the 'name' field in ascending order +firestore.pipeline().collection("users") + .sort(ascending("name")); + +``` + | +| [average(fieldName)](./firestore_pipelines.md#average_e5b0480) | (Public Preview) Creates an aggregation that calculates the average (mean) of a field's values across multiple stage inputs. +```typescript +// Calculate the average age of users +average("age").as("averageAge"); + +``` + | +| [byteLength(fieldName)](./firestore_pipelines.md#bytelength_e5b0480) | (Public Preview) Creates an expression that calculates the length of a string represented by a field in UTF-8 bytes, or just the length of a Blob. +```typescript +// Calculate the length of the 'myString' field in bytes. +byteLength("myString"); + +``` + | +| [ceil(fieldName)](./firestore_pipelines.md#ceil_e5b0480) | (Public Preview) Creates an expression that computes the ceiling of a numeric value. +```typescript +// Compute the ceiling of the 'price' field. +ceil("price"); + +``` + | +| [charLength(fieldName)](./firestore_pipelines.md#charlength_e5b0480) | (Public Preview) Creates an expression that calculates the character length of a string field in UTF8. +```typescript +// Get the character length of the 'name' field in UTF-8. +strLength("name"); + +``` + | +| [collectionId(fieldName)](./firestore_pipelines.md#collectionid_e5b0480) | (Public Preview) Creates an expression that returns the collection ID from a path. +```typescript +// Get the collection ID from a path. +collectionId("__name__"); + +``` + | +| [concat(fieldName, second, others)](./firestore_pipelines.md#concat_828272e) | (Public Preview) Creates an expression that concatenates strings, arrays, or blobs. Types cannot be mixed. +```typescript +// Concatenate a field with a literal string. +concat(field("firstName"), "Doe") + +``` + | +| [cosineDistance(fieldName, vector)](./firestore_pipelines.md#cosinedistance_463a23e) | (Public Preview) Calculates the Cosine distance between a field's vector value and a literal vector value. +```typescript +// Calculate the Cosine distance between the 'location' field and a target location +cosineDistance("location", [37.7749, -122.4194]); + +``` + | +| [cosineDistance(fieldName, vectorExpression)](./firestore_pipelines.md#cosinedistance_ed766a1) | (Public Preview) Calculates the Cosine distance between a field's vector value and a vector expression. +```typescript +// Calculate the cosine distance between the 'userVector' field and the 'itemVector' field +cosineDistance("userVector", field("itemVector")); + +``` + | +| [count(fieldName)](./firestore_pipelines.md#count_e5b0480) | (Public Preview) Creates an aggregation that counts the number of stage inputs where the input field exists. +```typescript +// Count the total number of products +count("productId").as("totalProducts"); + +``` + | +| [descending(fieldName)](./firestore_pipelines.md#descending_e5b0480) | (Public Preview) Creates an that sorts documents in descending order based on a field. +```typescript +// Sort documents by the 'name' field in descending order +firestore.pipeline().collection("users") + .sort(descending("name")); + +``` + | +| [divide(fieldName, expressions)](./firestore_pipelines.md#divide_cf36e43) | (Public Preview) Creates an expression that divides a field's value by an expression. +```typescript +// Divide the 'total' field by the 'count' field +divide("total", field("count")); + +``` + | +| [divide(fieldName, value)](./firestore_pipelines.md#divide_65e2f32) | (Public Preview) Creates an expression that divides a field's value by a constant value. +```typescript +// Divide the 'value' field by 10 +divide("value", 10); + +``` + | +| [dotProduct(fieldName, vector)](./firestore_pipelines.md#dotproduct_463a23e) | (Public Preview) Calculates the dot product between a field's vector value and a double array. +```typescript +// Calculate the dot product distance between a feature vector and a target vector +dotProduct("features", [0.5, 0.8, 0.2]); + +``` + | +| [dotProduct(fieldName, vectorExpression)](./firestore_pipelines.md#dotproduct_ed766a1) | (Public Preview) Calculates the dot product between a field's vector value and a vector expression. +```typescript +// Calculate the dot product distance between two document vectors: 'docVector1' and 'docVector2' +dotProduct("docVector1", field("docVector2")); + +``` + | +| [endsWith(fieldName, suffix)](./firestore_pipelines.md#endswith_05ca3b0) | (Public Preview) Creates an expression that checks if a field's value ends with a given postfix. +```typescript +// Check if the 'filename' field ends with ".txt" +endsWith("filename", ".txt"); + +``` + | +| [endsWith(fieldName, suffix)](./firestore_pipelines.md#endswith_8fc0ebc) | (Public Preview) Creates an expression that checks if a field's value ends with a given postfix. +```typescript +// Check if the 'url' field ends with the value of the 'extension' field +endsWith("url", field("extension")); + +``` + | +| [equal(fieldName, expression)](./firestore_pipelines.md#equal_1e91657) | (Public Preview) Creates an expression that checks if a field's value is equal to an expression. +```typescript +// Check if the 'age' field is equal to the 'limit' field +equal("age", field("limit")); + +``` + | +| [equal(fieldName, value)](./firestore_pipelines.md#equal_65e2f32) | (Public Preview) Creates an expression that checks if a field's value is equal to a constant value. +```typescript +// Check if the 'city' field is equal to string constant "London" +equal("city", "London"); + +``` + | +| [equalAny(fieldName, values)](./firestore_pipelines.md#equalany_8060b23) | (Public Preview) Creates an expression that checks if a field's value is equal to any of the provided values or expressions. +```typescript +// Check if the 'category' field is either "Electronics" or value of field 'primaryType' +equalAny("category", [constant("Electronics"), field("primaryType")]); + +``` + | +| [equalAny(fieldName, arrayExpression)](./firestore_pipelines.md#equalany_48da8d9) | (Public Preview) Creates an expression that checks if a field's value is equal to any of the provided values or expressions. +```typescript +// Check if the 'category' field is either "Electronics" or value of field 'primaryType' +equalAny("category", ["Electronics", field("primaryType")]); + +``` + | +| [euclideanDistance(fieldName, vector)](./firestore_pipelines.md#euclideandistance_463a23e) | (Public Preview) Calculates the Euclidean distance between a field's vector value and a double array. +```typescript +// Calculate the Euclidean distance between the 'location' field and a target location +euclideanDistance("location", [37.7749, -122.4194]); + +``` + | +| [euclideanDistance(fieldName, vectorExpression)](./firestore_pipelines.md#euclideandistance_ed766a1) | (Public Preview) Calculates the Euclidean distance between a field's vector value and a vector expression. +```typescript +// Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB' +euclideanDistance("pointA", field("pointB")); + +``` + | +| [exists(fieldName)](./firestore_pipelines.md#exists_e5b0480) | (Public Preview) Creates an expression that checks if a field exists. +```typescript +// Check if the document has a field named "phoneNumber" +exists("phoneNumber"); + +``` + | +| [exp(fieldName)](./firestore_pipelines.md#exp_e5b0480) | (Public Preview) Creates an expression that computes e to the power of the expression's result. +```typescript +// Compute e to the power of the 'value' field. +exp('value'); + +``` + A new representing the exp of the numeric value. | +| [floor(fieldName)](./firestore_pipelines.md#floor_e5b0480) | (Public Preview) Creates an expression that computes the floor of a numeric value. | +| [greaterThan(fieldName, expression)](./firestore_pipelines.md#greaterthan_1e91657) | (Public Preview) Creates an expression that checks if a field's value is greater than an expression. +```typescript +// Check if the value of field 'age' is greater than the value of field 'limit' +greaterThan("age", field("limit")); + +``` + | +| [greaterThan(fieldName, value)](./firestore_pipelines.md#greaterthan_65e2f32) | (Public Preview) Creates an expression that checks if a field's value is greater than a constant value. +```typescript +// Check if the 'price' field is greater than 100 +greaterThan("price", 100); + +``` + | +| [greaterThanOrEqual(fieldName, value)](./firestore_pipelines.md#greaterthanorequal_2e16acb) | (Public Preview) Creates an expression that checks if a field's value is greater than or equal to an expression. +```typescript +// Check if the value of field 'age' is greater than or equal to the value of field 'limit' +greaterThanOrEqual("age", field("limit")); + +``` + | +| [greaterThanOrEqual(fieldName, value)](./firestore_pipelines.md#greaterthanorequal_65e2f32) | (Public Preview) Creates an expression that checks if a field's value is greater than or equal to a constant value. +```typescript +// Check if the 'score' field is greater than or equal to 80 +greaterThanOrEqual("score", 80); + +``` + | +| [length\_2(fieldName)](./firestore_pipelines.md#length_2_e5b0480) | (Public Preview) Creates an expression that calculates the length of a string, array, map, vector, or bytes. +```typescript +// Get the length of the 'name' field. +length("name"); + +// Get the number of items in the 'cart' array. +length("cart"); + +``` + | +| [lessThan(fieldName, expression)](./firestore_pipelines.md#lessthan_1e91657) | (Public Preview) Creates an expression that checks if a field's value is less than an expression. +```typescript +// Check if the 'age' field is less than the 'limit' field +lessThan("age", field("limit")); + +``` + | +| [lessThan(fieldName, value)](./firestore_pipelines.md#lessthan_65e2f32) | (Public Preview) Creates an expression that checks if a field's value is less than a constant value. +```typescript +// Check if the 'price' field is less than 50 +lessThan("price", 50); + +``` + | +| [lessThanOrEqual(fieldName, expression)](./firestore_pipelines.md#lessthanorequal_1e91657) | (Public Preview) Creates an expression that checks if a field's value is less than or equal to an expression. +```typescript +// Check if the 'quantity' field is less than or equal to the 'limit' field +lessThan("quantity", field("limit")); + +``` + | +| [lessThanOrEqual(fieldName, value)](./firestore_pipelines.md#lessthanorequal_65e2f32) | (Public Preview) Creates an expression that checks if a field's value is less than or equal to a constant value. +```typescript +// Check if the 'score' field is less than or equal to 70 +lessThan("score", 70); + +``` + | +| [like(fieldName, pattern)](./firestore_pipelines.md#like_67f7432) | (Public Preview) Creates an expression that performs a case-sensitive wildcard string comparison against a field. +```typescript +// Check if the 'title' field contains the string "guide" +like("title", "%guide%"); + +``` + | +| [like(fieldName, pattern)](./firestore_pipelines.md#like_cb1318d) | (Public Preview) Creates an expression that performs a case-sensitive wildcard string comparison against a field. +```typescript +// Check if the 'title' field contains the string "guide" +like("title", field("pattern")); + +``` + | +| [ln(fieldName)](./firestore_pipelines.md#ln_e5b0480) | (Public Preview) Creates an expression that computes the natural logarithm of a numeric value. +```typescript +// Compute the natural logarithm of the 'value' field. +ln("value"); + +``` + | +| [log(fieldName, base)](./firestore_pipelines.md#log_a89e21b) | (Public Preview) Creates an expression that computes the logarithm of a field to a given base. +```typescript +// Compute the logarithm of the 'value' field with base 10. +log("value", 10); + +``` + | +| [log(fieldName, base)](./firestore_pipelines.md#log_805b11f) | (Public Preview) Creates an expression that computes the logarithm of a field to a given base. +```typescript +// Compute the logarithm of the 'value' field with the base in the 'base' field. +log("value", field("base")); + +``` + | +| [log10(fieldName)](./firestore_pipelines.md#log10_e5b0480) | (Public Preview) Creates an expression that computes the base-10 logarithm of a numeric value. +```typescript +// Compute the base-10 logarithm of the 'value' field. +log10("value"); + +``` + | +| [logicalMaximum(fieldName, second, others)](./firestore_pipelines.md#logicalmaximum_828272e) | (Public Preview) Creates an expression that returns the largest value between multiple input expressions or literal values. Based on Firestore's value type ordering. +```typescript +// Returns the largest value between the 'field1' field, the 'field2' field, +// and 1000. +logicalMaximum("field1", field("field2"), 1000); + +``` + | +| [logicalMinimum(fieldName, second, others)](./firestore_pipelines.md#logicalminimum_828272e) | (Public Preview) Creates an expression that returns the smallest value between a field's value and other input expressions or literal values. Based on Firestore's value type ordering. +```typescript +// Returns the smallest value between the 'field1' field, the 'field2' field, +// and 1000. +logicalMinimum("field1", field("field2"), 1000); + +``` + | +| [mapGet(fieldName, subField)](./firestore_pipelines.md#mapget_06663cf) | (Public Preview) Accesses a value from a map (object) field using the provided key. +```typescript +// Get the 'city' value from the 'address' map field +mapGet("address", "city"); + +``` + | +| [maximum(fieldName)](./firestore_pipelines.md#maximum_e5b0480) | (Public Preview) Creates an aggregation that finds the maximum value of a field across multiple stage inputs. +```typescript +// Find the highest score in a leaderboard +maximum("score").as("highestScore"); + +``` + | +| [minimum(fieldName)](./firestore_pipelines.md#minimum_e5b0480) | (Public Preview) Creates an aggregation that finds the minimum value of a field across multiple stage inputs. +```typescript +// Find the lowest price of all products +minimum("price").as("lowestPrice"); + +``` + | +| [mod(fieldName, expression)](./firestore_pipelines.md#mod_1e91657) | (Public Preview) Creates an expression that calculates the modulo (remainder) of dividing a field's value by an expression. +```typescript +// Calculate the remainder of dividing 'field1' by 'field2'. +mod("field1", field("field2")); + +``` + | +| [mod(fieldName, value)](./firestore_pipelines.md#mod_65e2f32) | (Public Preview) Creates an expression that calculates the modulo (remainder) of dividing a field's value by a constant. +```typescript +// Calculate the remainder of dividing 'field1' by 5. +mod("field1", 5); + +``` + | +| [multiply(fieldName, second)](./firestore_pipelines.md#multiply_b75bb8b) | (Public Preview) Creates an expression that multiplies a field's value by an expression. +```typescript +// Multiply the 'quantity' field by the 'price' field +multiply("quantity", field("price")); + +``` + | +| [notEqual(fieldName, expression)](./firestore_pipelines.md#notequal_1e91657) | (Public Preview) Creates an expression that checks if a field's value is not equal to an expression. +```typescript +// Check if the 'status' field is not equal to the value of 'expectedStatus' +notEqual("status", field("expectedStatus")); + +``` + | +| [notEqual(fieldName, value)](./firestore_pipelines.md#notequal_65e2f32) | (Public Preview) Creates an expression that checks if a field's value is not equal to a constant value. +```typescript +// Check if the 'country' field is not equal to "USA" +notEqual("country", "USA"); + +``` + | +| [notEqualAny(fieldName, values)](./firestore_pipelines.md#notequalany_8060b23) | (Public Preview) Creates an expression that checks if a field's value is not equal to any of the provided values or expressions. +```typescript +// Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' +notEqualAny("status", [constant("pending"), field("rejectedStatus")]); + +``` + | +| [notEqualAny(fieldName, arrayExpression)](./firestore_pipelines.md#notequalany_48da8d9) | (Public Preview) Creates an expression that checks if a field's value is not equal to any of the values in the evaluated expression. +```typescript +// Check if the 'status' field is not equal to any value in the field 'rejectedStatuses' +notEqualAny("status", field("rejectedStatuses")); + +``` + | +| [regexContains(fieldName, pattern)](./firestore_pipelines.md#regexcontains_67f7432) | (Public Preview) Creates an expression that checks if a string field contains a specified regular expression as a substring. +```typescript +// Check if the 'description' field contains "example" (case-insensitive) +regexContains("description", "(?i)example"); + +``` + | +| [regexContains(fieldName, pattern)](./firestore_pipelines.md#regexcontains_cb1318d) | (Public Preview) Creates an expression that checks if a string field contains a specified regular expression as a substring. +```typescript +// Check if the 'description' field contains "example" (case-insensitive) +regexContains("description", field("pattern")); + +``` + | +| [regexMatch(fieldName, pattern)](./firestore_pipelines.md#regexmatch_67f7432) | (Public Preview) Creates an expression that checks if a string field matches a specified regular expression. +```typescript +// Check if the 'email' field matches a valid email pattern +regexMatch("email", "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"); + +``` + | +| [regexMatch(fieldName, pattern)](./firestore_pipelines.md#regexmatch_cb1318d) | (Public Preview) Creates an expression that checks if a string field matches a specified regular expression. +```typescript +// Check if the 'email' field matches a valid email pattern +regexMatch("email", field("pattern")); + +``` + | +| [round(fieldName)](./firestore_pipelines.md#round_e5b0480) | (Public Preview) Creates an expression that rounds a numeric value to the nearest whole number. +```typescript +// Round the value of the 'price' field. +round("price"); + +``` + | +| [round(fieldName, decimalPlaces)](./firestore_pipelines.md#round_07d0cf0) | (Public Preview) Creates an expression that rounds a numeric value to the specified number of decimal places. +```typescript +// Round the value of the 'price' field to two decimal places. +round("price", 2); + +``` + | +| [split(fieldName, delimiter)](./firestore_pipelines.md#split_2cfdd37) | (Public Preview) Creates an expression that splits the value of a field on the provided delimiter. | +| [split(fieldName, delimiter)](./firestore_pipelines.md#split_f4fe06a) | (Public Preview) Creates an expression that splits the value of a field on the provided delimiter. | +| [sqrt(fieldName)](./firestore_pipelines.md#sqrt_e5b0480) | (Public Preview) Creates an expression that computes the square root of a numeric value. +```typescript +// Compute the square root of the 'value' field. +sqrt("value"); + +``` + | +| [startsWith(fieldName, prefix)](./firestore_pipelines.md#startswith_89325cc) | (Public Preview) Creates an expression that checks if a field's value starts with a given prefix. +```typescript +// Check if the 'name' field starts with "Mr." +startsWith("name", "Mr."); + +``` + | +| [startsWith(fieldName, prefix)](./firestore_pipelines.md#startswith_266c338) | (Public Preview) Creates an expression that checks if a field's value starts with a given prefix. +```typescript +// Check if the 'fullName' field starts with the value of the 'firstName' field +startsWith("fullName", field("firstName")); + +``` + | +| [stringConcat(fieldName, secondString, otherStrings)](./firestore_pipelines.md#stringconcat_d80077e) | (Public Preview) Creates an expression that concatenates string functions, fields or constants together. +```typescript +// Combine the 'firstName', " ", and 'lastName' fields into a single string +stringConcat("firstName", " ", field("lastName")); + +``` + | +| [stringContains(fieldName, substring)](./firestore_pipelines.md#stringcontains_5b94cfe) | (Public Preview) Creates an expression that checks if a string field contains a specified substring. +```typescript +// Check if the 'description' field contains "example". +stringContains("description", "example"); + +``` + | +| [stringContains(fieldName, substring)](./firestore_pipelines.md#stringcontains_ac3ba47) | (Public Preview) Creates an expression that checks if a string field contains a substring specified by an expression. +```typescript +// Check if the 'description' field contains the value of the 'keyword' field. +stringContains("description", field("keyword")); + +``` + | +| [subtract(fieldName, expression)](./firestore_pipelines.md#subtract_1e91657) | (Public Preview) Creates an expression that subtracts an expression from a field's value. +```typescript +// Subtract the 'discount' field from the 'price' field +subtract("price", field("discount")); + +``` + | +| [subtract(fieldName, value)](./firestore_pipelines.md#subtract_65e2f32) | (Public Preview) Creates an expression that subtracts a constant value from a field's value. +```typescript +// Subtract 20 from the value of the 'total' field +subtract("total", 20); + +``` + | +| [sum(fieldName)](./firestore_pipelines.md#sum_e5b0480) | (Public Preview) Creates an aggregation that calculates the sum of a field's values across multiple stage inputs. +```typescript +// Calculate the total revenue from a set of orders +sum("orderAmount").as("totalRevenue"); + +``` + | +| [timestampAdd(fieldName, unit, amount)](./firestore_pipelines.md#timestampadd_341fe7d) | (Public Preview) Creates an expression that adds a specified amount of time to a timestamp represented by a field. +```typescript +// Add 1 day to the 'timestamp' field. +timestampAdd("timestamp", "day", 1); + +``` + | +| [timestampSubtract(fieldName, unit, amount)](./firestore_pipelines.md#timestampsubtract_341fe7d) | (Public Preview) Creates an expression that subtracts a specified amount of time from a timestamp represented by a field. +```typescript +// Subtract 1 day from the 'timestamp' field. +timestampSubtract("timestamp", "day", 1); + +``` + | +| [timestampToUnixMicros(fieldName)](./firestore_pipelines.md#timestamptounixmicros_e5b0480) | (Public Preview) Creates an expression that converts a timestamp field to the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC). +```typescript +// Convert the 'timestamp' field to microseconds since epoch. +timestampToUnixMicros("timestamp"); + +``` + | +| [timestampToUnixMillis(fieldName)](./firestore_pipelines.md#timestamptounixmillis_e5b0480) | (Public Preview) Creates an expression that converts a timestamp field to the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC). +```typescript +// Convert the 'timestamp' field to milliseconds since epoch. +timestampToUnixMillis("timestamp"); + +``` + | +| [timestampToUnixSeconds(fieldName)](./firestore_pipelines.md#timestamptounixseconds_e5b0480) | (Public Preview) Creates an expression that converts a timestamp field to the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC). +```typescript +// Convert the 'timestamp' field to seconds since epoch. +timestampToUnixSeconds("timestamp"); + +``` + | +| [timestampTruncate(fieldName, granularity, timezone)](./firestore_pipelines.md#timestamptruncate_b6c7512) | (Public Preview) Creates an expression that truncates a timestamp to a specified granularity. | +| [timestampTruncate(fieldName, granularity, timezone)](./firestore_pipelines.md#timestamptruncate_ed83d46) | (Public Preview) Creates an expression that truncates a timestamp to a specified granularity. | +| [toLower(fieldName)](./firestore_pipelines.md#tolower_e5b0480) | (Public Preview) Creates an expression that converts a string field to lowercase. +```typescript +// Convert the 'name' field to lowercase +toLower("name"); + +``` + | +| [toUpper(fieldName)](./firestore_pipelines.md#toupper_e5b0480) | (Public Preview) Creates an expression that converts a string field to uppercase. +```typescript +// Convert the 'title' field to uppercase +toUpper("title"); + +``` + | +| [trim(fieldName, valueToTrim)](./firestore_pipelines.md#trim_c9f90ee) | (Public Preview) Creates an expression that removes leading and trailing whitespace from a string or byte array. +```typescript +// Trim whitespace from the 'userInput' field +trim("userInput"); + +// Trim quotes from the 'userInput' field +trim("userInput", '"'); + +``` + | +| [type(fieldName)](./firestore_pipelines.md#type_e5b0480) | (Public Preview) Creates an expression that returns the data type of the data in the specified field. | +| [unixMicrosToTimestamp(fieldName)](./firestore_pipelines.md#unixmicrostotimestamp_e5b0480) | (Public Preview) Creates an expression that interprets a field's value as the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. +```typescript +// Interpret the 'microseconds' field as microseconds since epoch. +unixMicrosToTimestamp("microseconds"); + +``` + | +| [unixMillisToTimestamp(fieldName)](./firestore_pipelines.md#unixmillistotimestamp_e5b0480) | (Public Preview) Creates an expression that interprets a field's value as the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. +```typescript +// Interpret the 'milliseconds' field as milliseconds since epoch. +unixMillisToTimestamp("milliseconds"); + +``` + | +| [unixSecondsToTimestamp(fieldName)](./firestore_pipelines.md#unixsecondstotimestamp_e5b0480) | (Public Preview) Creates an expression that interprets a field's value as the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. +```typescript +// Interpret the 'seconds' field as seconds since epoch. +unixSecondsToTimestamp("seconds"); + +``` + | +| [vectorLength(fieldName)](./firestore_pipelines.md#vectorlength_e5b0480) | (Public Preview) Creates an expression that calculates the length of a Firestore Vector represented by a field. +```typescript +// Get the vector length (dimension) of the field 'embedding'. +vectorLength("embedding"); + +``` + | +| function(first, ...) | +| [add(first, second)](./firestore_pipelines.md#add_846ca1b) | (Public Preview) Creates an expression that adds two expressions together. +```typescript +// Add the value of the 'quantity' field and the 'reserve' field. +add(field("quantity"), field("reserve")); + +``` + | +| [and(first, second, more)](./firestore_pipelines.md#and_e0c48bd) | (Public Preview) Creates an expression that performs a logical 'AND' operation on multiple filter conditions. +```typescript +// Check if the 'age' field is greater than 18 AND the 'city' field is "London" AND +// the 'status' field is "active" +const condition = and(greaterThan("age", 18), equal("city", "London"), equal("status", "active")); + +``` + | +| [concat(first, second, others)](./firestore_pipelines.md#concat_83be015) | (Public Preview) Creates an expression that concatenates strings, arrays, or blobs. Types cannot be mixed. +```typescript +// Concatenate the 'firstName' and 'lastName' fields with a space in between. +concat(field("firstName"), " ", field("lastName")) + +``` + | +| [logicalMaximum(first, second, others)](./firestore_pipelines.md#logicalmaximum_83be015) | (Public Preview) Creates an expression that returns the largest value between multiple input expressions or literal values. Based on Firestore's value type ordering. +```typescript +// Returns the largest value between the 'field1' field, the 'field2' field, +// and 1000 +logicalMaximum(field("field1"), field("field2"), 1000); + +``` + | +| [logicalMinimum(first, second, others)](./firestore_pipelines.md#logicalminimum_83be015) | (Public Preview) Creates an expression that returns the smallest value between multiple input expressions and literal values. Based on Firestore's value type ordering. +```typescript +// Returns the smallest value between the 'field1' field, the 'field2' field, +// and 1000. +logicalMinimum(field("field1"), field("field2"), 1000); + +``` + | +| [multiply(first, second)](./firestore_pipelines.md#multiply_846ca1b) | (Public Preview) Creates an expression that multiplies two expressions together. +```typescript +// Multiply the 'quantity' field by the 'price' field +multiply(field("quantity"), field("price")); + +``` + | +| [or(first, second, more)](./firestore_pipelines.md#or_e0c48bd) | (Public Preview) Creates an expression that performs a logical 'OR' operation on multiple filter conditions. +```typescript +// Check if the 'age' field is greater than 18 OR the 'city' field is "London" OR +// the 'status' field is "active" +const condition = or(greaterThan("age", 18), equal("city", "London"), equal("status", "active")); + +``` + | +| [xor(first, second, additionalConditions)](./firestore_pipelines.md#xor_8197113) | (Public Preview) Creates an expression that performs a logical 'XOR' (exclusive OR) operation on multiple BooleanExpressions. +```typescript +// Check if only one of the conditions is true: 'age' greater than 18, 'city' is "London", +// or 'status' is "active". +const condition = xor( + greaterThan("age", 18), + equal("city", "London"), + equal("status", "active")); + +``` + | +| function(firstArray, ...) | +| [arrayConcat(firstArray, secondArray, otherArrays)](./firestore_pipelines.md#arrayconcat_c00d5d7) | (Public Preview) Creates an expression that concatenates an array expression with other arrays. +```typescript +// Combine the 'items' array with two new item arrays +arrayConcat(field("items"), [field("newItems"), field("otherItems")]); + +``` + | +| function(firstArrayField, ...) | +| [arrayConcat(firstArrayField, secondArray, otherArrays)](./firestore_pipelines.md#arrayconcat_f92063d) | (Public Preview) Creates an expression that concatenates a field's array value with other arrays. +```typescript +// Combine the 'items' array with two new item arrays +arrayConcat("items", [field("newItems"), field("otherItems")]); + +``` + | +| function(firstMap, ...) | +| [mapMerge(firstMap, secondMap, otherMaps)](./firestore_pipelines.md#mapmerge_cfe77ce) | (Public Preview) Creates an expression that merges multiple map values. +``` +// Merges the map in the settings field with, a map literal, and a map in +// that is conditionally returned by another expression +mapMerge(field('settings'), { enabled: true }, conditional(field('isAdmin'), { admin: true}, {}) + +``` + | +| function(firstString, ...) | +| [stringConcat(firstString, secondString, otherStrings)](./firestore_pipelines.md#stringconcat_8a8d1b6) | (Public Preview) Creates an expression that concatenates string expressions together. +```typescript +// Combine the 'firstName', " ", and 'lastName' fields into a single string +stringConcat(field("firstName"), " ", field("lastName")); + +``` + | +| function(ifExpr, ...) | +| [ifAbsent(ifExpr, elseExpr)](./firestore_pipelines.md#ifabsent_0e6d161) | (Public Preview) Creates an expression that returns the elseExpr argument if ifExpr is absent, else return the result of the ifExpr argument evaluation. +```typescript +// Returns the value of the optional field 'optional_field', or returns 'default_value' +// if the field is absent. +ifAbsent(field("optional_field"), constant("default_value")) + +``` + | +| [ifAbsent(ifExpr, elseValue)](./firestore_pipelines.md#ifabsent_c34e5ed) | (Public Preview) Creates an expression that returns the elseValue argument if ifExpr is absent, else return the result of the ifExpr argument evaluation. +```typescript +// Returns the value of the optional field 'optional_field', or returns 'default_value' +// if the field is absent. +ifAbsent(field("optional_field"), "default_value") + +``` + | +| function(ifFieldName, ...) | +| [ifAbsent(ifFieldName, elseExpr)](./firestore_pipelines.md#ifabsent_e6dabea) | (Public Preview) Creates an expression that returns the elseExpr argument if ifFieldName is absent, else return the value of the field. +```typescript +// Returns the value of the optional field 'optional_field', or returns the value of +// 'default_field' if 'optional_field' is absent. +ifAbsent("optional_field", field("default_field")) + +``` + | +| [ifAbsent(ifFieldName, elseValue)](./firestore_pipelines.md#ifabsent_d8f2823) | (Public Preview) Creates an expression that returns the elseValue argument if ifFieldName is absent, else return the value of the field. +```typescript +// Returns the value of the optional field 'optional_field', or returns 'default_value' +// if the field is absent. +ifAbsent("optional_field", "default_value") + +``` + | +| function(input, ...) | +| [substring(input, position, length)](./firestore_pipelines.md#substring_e6e0aa3) | (Public Preview) Creates an expression that returns a substring of a string or byte array. | +| [substring(input, position, length)](./firestore_pipelines.md#substring_ab56dc6) | (Public Preview) Creates an expression that returns a substring of a string or byte array. | +| function(left, ...) | +| [divide(left, right)](./firestore_pipelines.md#divide_b3c3382) | (Public Preview) Creates an expression that divides two expressions. +```typescript +// Divide the 'total' field by the 'count' field +divide(field("total"), field("count")); + +``` + | +| [equal(left, right)](./firestore_pipelines.md#equal_b3c3382) | (Public Preview) Creates an expression that checks if two expressions are equal. +```typescript +// Check if the 'age' field is equal to an expression +equal(field("age"), field("minAge").add(10)); + +``` + | +| [greaterThan(left, right)](./firestore_pipelines.md#greaterthan_b3c3382) | (Public Preview) Creates an expression that checks if the first expression is greater than the second expression. +```typescript +// Check if the 'age' field is greater than 18 +greaterThan(field("age"), Constant(9).add(9)); + +``` + | +| [greaterThanOrEqual(left, right)](./firestore_pipelines.md#greaterthanorequal_b3c3382) | (Public Preview) Creates an expression that checks if the first expression is greater than or equal to the second expression. +```typescript +// Check if the 'quantity' field is greater than or equal to the field "threshold" +greaterThanOrEqual(field("quantity"), field("threshold")); + +``` + | +| [lessThan(left, right)](./firestore_pipelines.md#lessthan_b3c3382) | (Public Preview) Creates an expression that checks if the first expression is less than the second expression. +```typescript +// Check if the 'age' field is less than 30 +lessThan(field("age"), field("limit")); + +``` + | +| [lessThanOrEqual(left, right)](./firestore_pipelines.md#lessthanorequal_b3c3382) | (Public Preview) Creates an expression that checks if the first expression is less than or equal to the second expression. +```typescript +// Check if the 'quantity' field is less than or equal to 20 +lessThan(field("quantity"), field("limit")); + +``` + | +| [mod(left, right)](./firestore_pipelines.md#mod_b3c3382) | (Public Preview) Creates an expression that calculates the modulo (remainder) of dividing two expressions. +```typescript +// Calculate the remainder of dividing 'field1' by 'field2'. +mod(field("field1"), field("field2")); + +``` + | +| [notEqual(left, right)](./firestore_pipelines.md#notequal_b3c3382) | (Public Preview) Creates an expression that checks if two expressions are not equal. +```typescript +// Check if the 'status' field is not equal to field 'finalState' +notEqual(field("status"), field("finalState")); + +``` + | +| [pipelineResultEqual(left, right)](./firestore_pipelines.md#pipelineresultequal_707a755) | (Public Preview) Test equality of two PipelineResults. | +| [subtract(left, right)](./firestore_pipelines.md#subtract_b3c3382) | (Public Preview) Creates an expression that subtracts two expressions. +```typescript +// Subtract the 'discount' field from the 'price' field +subtract(field("price"), field("discount")); + +``` + | +| function(mapExpr, ...) | +| [mapRemove(mapExpr, key)](./firestore_pipelines.md#mapremove_23c7d51) | (Public Preview) Creates an expression that removes a key from the map produced by evaluating an expression. +``` +// Removes the key 'baz' from the input map. +mapRemove(map({foo: 'bar', baz: true}), 'baz'); + +``` + | +| [mapRemove(mapExpr, keyExpr)](./firestore_pipelines.md#mapremove_9fbcaa3) | (Public Preview) Creates an expression that removes a key from the map produced by evaluating an expression. +``` +// Removes the key 'baz' from the input map. +mapRemove(map({foo: 'bar', baz: true}), constant('baz')); + +``` + | +| function(mapExpression, ...) | +| [mapGet(mapExpression, subField)](./firestore_pipelines.md#mapget_688c050) | (Public Preview) Accesses a value from a map (object) expression using the provided key. +```typescript +// Get the 'city' value from the 'address' map field +mapGet(field("address"), "city"); + +``` + | +| function(mapField, ...) | +| [mapMerge(mapField, secondMap, otherMaps)](./firestore_pipelines.md#mapmerge_70a564b) | (Public Preview) Creates an expression that merges multiple map values. +``` +// Merges the map in the settings field with, a map literal, and a map in +// that is conditionally returned by another expression +mapMerge('settings', { enabled: true }, conditional(field('isAdmin'), { admin: true}, {}) + +``` + | +| [mapRemove(mapField, key)](./firestore_pipelines.md#mapremove_bd5726e) | (Public Preview) Creates an expression that removes a key from the map at the specified field name. +``` +// Removes the key 'city' field from the map in the address field of the input document. +mapRemove('address', 'city'); + +``` + | +| [mapRemove(mapField, keyExpr)](./firestore_pipelines.md#mapremove_8406d13) | (Public Preview) Creates an expression that removes a key from the map at the specified field name. +``` +// Removes the key 'city' field from the map in the address field of the input document. +mapRemove('address', constant('city')); + +``` + | +| function(name, ...) | +| [field(name)](./firestore_pipelines.md#field_1eaaff4) | (Public Preview) Creates a instance representing the field at the given path.The path can be a simple field name (e.g., "name") or a dot-separated path to a nested field (e.g., "address.city"). +```typescript +// Create a Field instance for the 'title' field +const titleField = field("title"); + +// Create a Field instance for a nested field 'author.firstName' +const authorFirstNameField = field("author.firstName"); + +``` + | +| function(options, ...) | +| [execute(options)](./firestore_pipelines.md#execute_9e87e31) | (Public Preview) Executes a pipeline and returns a Promise to represent the asynchronous operation.The returned Promise can be used to track the progress of the pipeline execution and retrieve the results (or handle any errors) asynchronously.The pipeline results are returned as a that contains a list of objects. Each typically represents a single key/value map that has passed through all the stages of the pipeline, however this might differ depending on the stages involved in the pipeline. For example:

  • If there are no stages or only transformation stages, each represents a single document.
  • If there is an aggregation, only a single is returned, representing the aggregated results over the entire dataset .
  • If there is an aggregation stage with grouping, each represents a distinct group and its associated aggregated values.

Example: +```typescript +const snapshot: PipelineSnapshot = await execute(firestore.pipeline().collection("books") + .where(gt(field("rating"), 4.5)) + .select("title", "author", "rating")); + +const results: PipelineResults = snapshot.results; + +``` + | +| function(path, ...) | +| [field(path)](./firestore_pipelines.md#field_34ee07d) | (Public Preview) Creates a instance representing the field at the given path. | +| function(pipeline, ...) | +| [execute(pipeline)](./firestore_pipelines.md#execute_01df620) | (Public Preview) Executes a pipeline and returns a Promise to represent the asynchronous operation.The returned Promise can be used to track the progress of the pipeline execution and retrieve the results (or handle any errors) asynchronously.The pipeline results are returned as a that contains a list of objects. Each typically represents a single key/value map that has passed through all the stages of the pipeline, however this might differ depending on the stages involved in the pipeline. For example:

  • If there are no stages or only transformation stages, each represents a single document.
  • If there is an aggregation, only a single is returned, representing the aggregated results over the entire dataset .
  • If there is an aggregation stage with grouping, each represents a distinct group and its associated aggregated values.

Example: +```typescript +const snapshot: PipelineSnapshot = await execute(firestore.pipeline().collection("books") + .where(gt(field("rating"), 4.5)) + .select("title", "author", "rating")); + +const results: PipelineResults = snapshot.results; + +``` + | +| function(stringExpression, ...) | +| [charLength(stringExpression)](./firestore_pipelines.md#charlength_c25a54a) | (Public Preview) Creates an expression that calculates the character length of a string expression in UTF-8. +```typescript +// Get the character length of the 'name' field in UTF-8. +strLength(field("name")); + +``` + | +| [endsWith(stringExpression, suffix)](./firestore_pipelines.md#endswith_0a0b889) | (Public Preview) Creates an expression that checks if a string expression ends with a given postfix. +```typescript +// Check if the result of concatenating 'firstName' and 'lastName' fields ends with "Jr." +endsWith(field("fullName"), "Jr."); + +``` + | +| [endsWith(stringExpression, suffix)](./firestore_pipelines.md#endswith_13aee0d) | (Public Preview) Creates an expression that checks if a string expression ends with a given postfix. +```typescript +// Check if the result of concatenating 'firstName' and 'lastName' fields ends with "Jr." +endsWith(field("fullName"), constant("Jr.")); + +``` + | +| [like(stringExpression, pattern)](./firestore_pipelines.md#like_a84c581) | (Public Preview) Creates an expression that performs a case-sensitive wildcard string comparison. +```typescript +// Check if the 'title' field contains the string "guide" +like(field("title"), "%guide%"); + +``` + | +| [like(stringExpression, pattern)](./firestore_pipelines.md#like_b534848) | (Public Preview) Creates an expression that performs a case-sensitive wildcard string comparison. +```typescript +// Check if the 'title' field contains the string "guide" +like(field("title"), field("pattern")); + +``` + | +| [regexContains(stringExpression, pattern)](./firestore_pipelines.md#regexcontains_a84c581) | (Public Preview) Creates an expression that checks if a string expression contains a specified regular expression as a substring. +```typescript +// Check if the 'description' field contains "example" (case-insensitive) +regexContains(field("description"), "(?i)example"); + +``` + | +| [regexContains(stringExpression, pattern)](./firestore_pipelines.md#regexcontains_b534848) | (Public Preview) Creates an expression that checks if a string expression contains a specified regular expression as a substring. +```typescript +// Check if the 'description' field contains "example" (case-insensitive) +regexContains(field("description"), field("pattern")); + +``` + | +| [regexMatch(stringExpression, pattern)](./firestore_pipelines.md#regexmatch_a84c581) | (Public Preview) Creates an expression that checks if a string expression matches a specified regular expression. +```typescript +// Check if the 'email' field matches a valid email pattern +regexMatch(field("email"), "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"); + +``` + | +| [regexMatch(stringExpression, pattern)](./firestore_pipelines.md#regexmatch_b534848) | (Public Preview) Creates an expression that checks if a string expression matches a specified regular expression. +```typescript +// Check if the 'email' field matches a valid email pattern +regexMatch(field("email"), field("pattern")); + +``` + | +| [reverse(stringExpression)](./firestore_pipelines.md#reverse_c25a54a) | (Public Preview) Creates an expression that reverses a string. +```typescript +// Reverse the value of the 'myString' field. +reverse(field("myString")); + +``` + | +| [startsWith(stringExpression, prefix)](./firestore_pipelines.md#startswith_75c3dbb) | (Public Preview) Creates an expression that checks if a string expression starts with a given prefix. +```typescript +// Check if the result of concatenating 'firstName' and 'lastName' fields starts with "Mr." +startsWith(field("fullName"), "Mr."); + +``` + | +| [startsWith(stringExpression, prefix)](./firestore_pipelines.md#startswith_52f218a) | (Public Preview) Creates an expression that checks if a string expression starts with a given prefix. +```typescript +// Check if the result of concatenating 'firstName' and 'lastName' fields starts with "Mr." +startsWith(field("fullName"), field("prefix")); + +``` + | +| [stringContains(stringExpression, substring)](./firestore_pipelines.md#stringcontains_3e9ff32) | (Public Preview) Creates an expression that checks if a string expression contains a specified substring. +```typescript +// Check if the 'description' field contains "example". +stringContains(field("description"), "example"); + +``` + | +| [stringContains(stringExpression, substring)](./firestore_pipelines.md#stringcontains_cc6ee02) | (Public Preview) Creates an expression that checks if a string expression contains a substring specified by another expression. +```typescript +// Check if the 'description' field contains the value of the 'keyword' field. +stringContains(field("description"), field("keyword")); + +``` + | +| [stringReverse(stringExpression)](./firestore_pipelines.md#stringreverse_c25a54a) | (Public Preview) Creates an expression that reverses a string. +```typescript +// Reverse the value of the 'myString' field. +strReverse(field("myString")); + +``` + | +| [toLower(stringExpression)](./firestore_pipelines.md#tolower_c25a54a) | (Public Preview) Creates an expression that converts a string expression to lowercase. +```typescript +// Convert the 'name' field to lowercase +toLower(field("name")); + +``` + | +| [toUpper(stringExpression)](./firestore_pipelines.md#toupper_c25a54a) | (Public Preview) Creates an expression that converts a string expression to uppercase. +```typescript +// Convert the 'title' field to uppercase +toUppercase(field("title")); + +``` + | +| [trim(stringExpression, valueToTrim)](./firestore_pipelines.md#trim_dd54322) | (Public Preview) Creates an expression that removes leading and trailing characters from a string or byte array expression. +```typescript +// Trim whitespace from the 'userInput' field +trim(field("userInput")); + +// Trim quotes from the 'userInput' field +trim(field("userInput"), '"'); + +``` + | +| function(timestamp, ...) | +| [timestampAdd(timestamp, unit, amount)](./firestore_pipelines.md#timestampadd_98418f9) | (Public Preview) Creates an expression that adds a specified amount of time to a timestamp. +```typescript +// Add some duration determined by field 'unit' and 'amount' to the 'timestamp' field. +timestampAdd(field("timestamp"), field("unit"), field("amount")); + +``` + | +| [timestampAdd(timestamp, unit, amount)](./firestore_pipelines.md#timestampadd_ffe8e57) | (Public Preview) Creates an expression that adds a specified amount of time to a timestamp. +```typescript +// Add 1 day to the 'timestamp' field. +timestampAdd(field("timestamp"), "day", 1); + +``` + | +| [timestampSubtract(timestamp, unit, amount)](./firestore_pipelines.md#timestampsubtract_98418f9) | (Public Preview) Creates an expression that subtracts a specified amount of time from a timestamp. +```typescript +// Subtract some duration determined by field 'unit' and 'amount' from the 'timestamp' field. +timestampSubtract(field("timestamp"), field("unit"), field("amount")); + +``` + | +| [timestampSubtract(timestamp, unit, amount)](./firestore_pipelines.md#timestampsubtract_ffe8e57) | (Public Preview) Creates an expression that subtracts a specified amount of time from a timestamp. +```typescript +// Subtract 1 day from the 'timestamp' field. +timestampSubtract(field("timestamp"), "day", 1); + +``` + | +| function(timestampExpression, ...) | +| [timestampTruncate(timestampExpression, granularity, timezone)](./firestore_pipelines.md#timestamptruncate_ad5d843) | (Public Preview) Creates an expression that truncates a timestamp to a specified granularity. | +| [timestampTruncate(timestampExpression, granularity, timezone)](./firestore_pipelines.md#timestamptruncate_d6ab2a4) | (Public Preview) Creates an expression that truncates a timestamp to a specified granularity. | +| function(tryExpr, ...) | +| [ifError(tryExpr, catchExpr)](./firestore_pipelines.md#iferror_a99a327) | (Public Preview) Creates an expression that returns the catch argument if there is an error, else return the result of the try argument evaluation.This overload is useful when a BooleanExpression is required. +```typescript +// Create an expression that protects against a divide by zero error +// but always returns a boolean expression. +ifError(constant(50).divide('length').gt(1), constant(false)); + +``` + | +| [ifError(tryExpr, catchExpr)](./firestore_pipelines.md#iferror_756c12e) | (Public Preview) Creates an expression that returns the catch argument if there is an error, else return the result of the try argument evaluation. +```typescript +// Returns the first item in the title field arrays, or returns +// the entire title field if the array is empty or the field is another type. +ifError(field("title").arrayGet(0), field("title")); + +``` + | +| [ifError(tryExpr, catchValue)](./firestore_pipelines.md#iferror_dc532f9) | (Public Preview) Creates an expression that returns the catch argument if there is an error, else return the result of the try argument evaluation. +```typescript +// Returns the first item in the title field arrays, or returns +// "Default Title" +ifError(field("title").arrayGet(0), "Default Title"); + +``` + | +| function(value, ...) | +| [constant(value)](./firestore_pipelines.md#constant_0c00f91) | (Public Preview) Creates a Constant instance for a number value. | +| [constant(value)](./firestore_pipelines.md#constant_6dac335) | (Public Preview) Creates a Constant instance for a VectorValue value. | +| [constant(value)](./firestore_pipelines.md#constant_7c807cd) | (Public Preview) Creates a Constant instance for a string value. | +| [constant(value)](./firestore_pipelines.md#constant_b2e4f8d) | (Public Preview) Creates a BooleanExpression instance for a boolean value. | +| [constant(value)](./firestore_pipelines.md#constant_73ebd84) | (Public Preview) Creates a Constant instance for a null value. | +| [constant(value)](./firestore_pipelines.md#constant_72a76cb) | (Public Preview) Creates a Constant instance for a GeoPoint value. | +| [constant(value)](./firestore_pipelines.md#constant_000477d) | (Public Preview) Creates a Constant instance for a Timestamp value. | +| [constant(value)](./firestore_pipelines.md#constant_5131bf7) | (Public Preview) Creates a Constant instance for a Date value. | +| [constant(value)](./firestore_pipelines.md#constant_fdf565d) | (Public Preview) Creates a Constant instance for a Bytes value. | +| [constant(value)](./firestore_pipelines.md#constant_bcd2b0b) | (Public Preview) Creates a Constant instance for a DocumentReference value. | +| [exists(value)](./firestore_pipelines.md#exists_f3daf14) | (Public Preview) Creates an expression that checks if a field exists. +```typescript +// Check if the document has a field named "phoneNumber" +exists(field("phoneNumber")); + +``` + | +| [isAbsent(value)](./firestore_pipelines.md#isabsent_f3daf14) | (Public Preview) Creates an expression that returns true if a value is absent. Otherwise, returns false even if the value is null. +```typescript +// Check if the field `value` is absent. +isAbsent(field("value")); + +``` + | +| [isError(value)](./firestore_pipelines.md#iserror_f3daf14) | (Public Preview) Creates an expression that checks if a given expression produces an error. +```typescript +// Check if the result of a calculation is an error +isError(field("title").arrayContains(1)); + +``` + | +| function(vectorExpression, ...) | +| [cosineDistance(vectorExpression, vector)](./firestore_pipelines.md#cosinedistance_3a80317) | (Public Preview) Calculates the Cosine distance between a vector expression and a vector literal. +```typescript +// Calculate the cosine distance between the 'location' field and a target location +cosineDistance(field("location"), [37.7749, -122.4194]); + +``` + | +| [cosineDistance(vectorExpression, otherVectorExpression)](./firestore_pipelines.md#cosinedistance_17b5bcc) | (Public Preview) Calculates the Cosine distance between two vector expressions. +```typescript +// Calculate the cosine distance between the 'userVector' field and the 'itemVector' field +cosineDistance(field("userVector"), field("itemVector")); + +``` + | +| [dotProduct(vectorExpression, vector)](./firestore_pipelines.md#dotproduct_3a80317) | (Public Preview) Calculates the dot product between a vector expression and a double array. +```typescript +// Calculate the dot product between a feature vector and a target vector +dotProduct(field("features"), [0.5, 0.8, 0.2]); + +``` + | +| [dotProduct(vectorExpression, otherVectorExpression)](./firestore_pipelines.md#dotproduct_17b5bcc) | (Public Preview) Calculates the dot product between two vector expressions. +```typescript +// Calculate the dot product between two document vectors: 'docVector1' and 'docVector2' +dotProduct(field("docVector1"), field("docVector2")); + +``` + | +| [euclideanDistance(vectorExpression, vector)](./firestore_pipelines.md#euclideandistance_3a80317) | (Public Preview) Calculates the Euclidean distance between a vector expression and a double array. +```typescript +// Calculate the Euclidean distance between the 'location' field and a target location + +euclideanDistance(field("location"), [37.7749, -122.4194]); + +``` + | +| [euclideanDistance(vectorExpression, otherVectorExpression)](./firestore_pipelines.md#euclideandistance_17b5bcc) | (Public Preview) Calculates the Euclidean distance between two vector expressions. +```typescript +// Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB' +euclideanDistance(field("pointA"), field("pointB")); + +``` + | +| [vectorLength(vectorExpression)](./firestore_pipelines.md#vectorlength_58a039b) | (Public Preview) Creates an expression that calculates the length of a Firestore Vector. +```typescript +// Get the vector length (dimension) of the field 'embedding'. +vectorLength(field("embedding")); + +``` + | + +## Classes + +| Class | Description | +| --- | --- | +| [AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) | (Public Preview) A class that represents an aggregate function. | +| [AliasedAggregate](./firestore_pipelines.aliasedaggregate.md#aliasedaggregate_class) | (Public Preview) An AggregateFunction with alias. | +| [AliasedExpression](./firestore_pipelines.aliasedexpression.md#aliasedexpression_class) | (Public Preview) | +| [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) | (Public Preview) An interface that represents a filter condition. | +| [Bytes](./firestore_pipelines.bytes.md#bytes_class) | An immutable object representing an array of bytes. | +| [CollectionReference](./firestore_pipelines.collectionreference.md#collectionreference_class) | A CollectionReference object can be used for adding documents, getting document references, and querying for documents (using [query()](./firestore_.md#query_9f7b0f4)). | +| [DocumentReference](./firestore_pipelines.documentreference.md#documentreference_class) | 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. | +| [DocumentSnapshot](./firestore_pipelines.documentsnapshot.md#documentsnapshot_class) | A DocumentSnapshot contains data read from a document in your Firestore database. The data can be extracted with .data() or .get(<field>) to get a specific field.For a DocumentSnapshot that points to a non-existing document, any data access will return 'undefined'. You can use the exists() method to explicitly verify a document's existence. | +| [Expression](./firestore_pipelines.expression.md#expression_class) | (Public Preview) Represents an expression that can be evaluated to a value within the execution of a .Expressions are the building blocks for creating complex queries and transformations in Firestore pipelines. They can represent:- \*\*Field references:\*\* Access values from document fields. - \*\*Literals:\*\* Represent constant values (strings, numbers, booleans). - \*\*Function calls:\*\* Apply functions to one or more expressions.The Expr class provides a fluent API for building expressions. You can chain together method calls to create complex expressions. | +| [Field](./firestore_pipelines.field.md#field_class) | (Public Preview) Represents a reference to a field in a Firestore document, or outputs of a stage.

Field references are used to access document field values in expressions and to specify fields for sorting, filtering, and projecting data in Firestore pipelines.

You can create a Field instance using the static method: +```typescript +// Create a Field instance for the 'name' field +const nameField = field("name"); + +// Create a Field instance for a nested field 'address.city' +const cityField = field("address.city"); + +``` + | +| [FieldPath](./firestore_pipelines.fieldpath.md#fieldpath_class) | A FieldPath refers to a field in a document. The path may consist of a single field name (referring to a top-level field in the document), or a list of field names (referring to a nested field in the document).Create a FieldPath by providing field names. If more than one field name is provided, the path will point to a nested field in a document. | +| [FieldValue](./firestore_pipelines.fieldvalue.md#fieldvalue_class) | Sentinel values that can be used when writing document fields with set() or update(). | +| [Firestore](./firestore_pipelines.firestore.md#firestore_class) | The Cloud Firestore service interface.Do not call this constructor directly. Instead, use [getFirestore()](./firestore_.md#getfirestore). | +| [FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) | (Public Preview) This class defines the base class for Firestore functions, which can be evaluated within pipeline execution.Typically, you would not use this class or its children directly. Use either the functions like [and()](./firestore_.md#and_e72c712), , or the methods on (, , etc.) to construct new Function instances. | +| [GeoPoint](./firestore_pipelines.geopoint.md#geopoint_class) | An immutable object representing a geographic location in Firestore. The location is represented as latitude/longitude pair.Latitude values are in the range of \[-90, 90\]. Longitude values are in the range of \[-180, 180\]. | +| [Ordering](./firestore_pipelines.ordering.md#ordering_class) | (Public Preview) Represents an ordering criterion for sorting documents in a Firestore pipeline.You create Ordering instances using the ascending and descending helper functions. | +| [Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) | (Public Preview) | +| [PipelineResult](./firestore_pipelines.pipelineresult.md#pipelineresult_class) | (Public Preview) A PipelineResult contains data read from a Firestore Pipeline. The data can be extracted with the or methods.

If the PipelineResult represents a non-document result, ref will return a undefined value. | +| [PipelineSnapshot](./firestore_pipelines.pipelinesnapshot.md#pipelinesnapshot_class) | (Public Preview) Represents the results of a Firestore pipeline execution.A PipelineSnapshot contains zero or more objects representing the documents returned by a pipeline query. It provides methods to iterate over the documents and access metadata about the query results. | +| [PipelineSource](./firestore_pipelines.pipelinesource.md#pipelinesource_class) | (Public Preview) Provides the entry point for defining the data source of a Firestore .Use the methods of this class (e.g., , , , or ) to specify the initial data for your pipeline, such as a collection, a collection group, the entire database, or a set of specific documents. | +| [Query](./firestore_pipelines.query.md#query_class) | A Query refers to a query which you can read or listen to. You can also construct refined Query objects by adding filters and ordering. | +| [QueryDocumentSnapshot](./firestore_pipelines.querydocumentsnapshot.md#querydocumentsnapshot_class) | 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() or .get(<field>) to get a specific field.A QueryDocumentSnapshot offers the same API surface as a DocumentSnapshot. Since query results contain only existing documents, the exists property will always be true and data() will never return 'undefined'. | +| [SnapshotMetadata](./firestore_pipelines.snapshotmetadata.md#snapshotmetadata_class) | Metadata about a snapshot, describing the state of the snapshot. | +| [Timestamp](./firestore_pipelines.timestamp.md#timestamp_class) | A Timestamp represents a point in time independent of any time zone or calendar, represented as seconds and fractions of seconds at nanosecond resolution in UTC Epoch time.It is encoded using the Proleptic Gregorian Calendar which extends the Gregorian calendar backwards to year one. It is encoded assuming all minutes are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z.For examples and further specifications, refer to the [Timestamp definition](https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto). | +| [VectorValue](./firestore_pipelines.vectorvalue.md#vectorvalue_class) | Represents a vector type in Firestore documents. Create an instance with [vector()](./firestore_.md#vector_0dbdaf2). VectorValue | + +## Interfaces + +| Interface | Description | +| --- | --- | +| [DocumentData](./firestore_pipelines.documentdata.md#documentdata_interface) | Document data (for use with [setDoc()](./firestore_lite.md#setdoc_ee215ad)) consists of fields mapped to values. | +| [FirestoreDataConverter](./firestore_pipelines.firestoredataconverter.md#firestoredataconverter_interface) | Converter used by 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.In this context, an "AppModel" is a class that is used in an application to package together related information and functionality. Such a class could, for example, have properties with complex, nested data types, properties used for memoization, properties of types not supported by Firestore (such as symbol and bigint), and helper functions that perform compound operations. Such classes are not suitable and/or possible to store into a Firestore database. Instead, instances of such classes need to be converted to "plain old JavaScript objects" (POJOs) with exclusively primitive properties, potentially nested inside other POJOs or arrays of POJOs. In this context, this type is referred to as the "DbModel" and would be an object suitable for persisting into Firestore. For convenience, applications can implement FirestoreDataConverter and register the converter with Firestore objects, such as DocumentReference or Query, to automatically convert AppModel to DbModel when storing into Firestore, and convert DbModel to AppModel when retrieving from Firestore. | +| [PipelineExecuteOptions](./firestore_pipelines.pipelineexecuteoptions.md#pipelineexecuteoptions_interface) | (Public Preview) Options defining Pipeline execution. | +| [Selectable](./firestore_pipelines.selectable.md#selectable_interface) | (Public Preview) An interface that represents a selectable expression. | +| [SnapshotOptions](./firestore_pipelines.snapshotoptions.md#snapshotoptions_interface) | Options that configure how data is retrieved from a DocumentSnapshot (for example the desired behavior for server timestamps that have not yet been set to their final value). | +| [StageOptions](./firestore_pipelines.stageoptions.md#stageoptions_interface) | (Public Preview) Options defining how a Stage is evaluated. | + +## Type Aliases + +| Type Alias | Description | +| --- | --- | +| [AddFieldsStageOptions](./firestore_pipelines.md#addfieldsstageoptions) | (Public Preview) Options defining how an AddFieldsStage is evaluated. See . | +| [AggregateStageOptions](./firestore_pipelines.md#aggregatestageoptions) | (Public Preview) Options defining how an AggregateStage is evaluated. See . | +| [CollectionGroupStageOptions](./firestore_pipelines.md#collectiongroupstageoptions) | (Public Preview) Defines the configuration options for a within a pipeline. This type extends and provides specific settings for how a collection group is identified and processed during pipeline execution. | +| [CollectionStageOptions](./firestore_pipelines.md#collectionstageoptions) | (Public Preview) Options defining how a CollectionStage is evaluated. See . | +| [DatabaseStageOptions](./firestore_pipelines.md#databasestageoptions) | (Public Preview) Options defining how a DatabaseStage is evaluated. See . | +| [DistinctStageOptions](./firestore_pipelines.md#distinctstageoptions) | (Public Preview) Options defining how a DistinctStage is evaluated. See . | +| [DocumentsStageOptions](./firestore_pipelines.md#documentsstageoptions) | (Public Preview) Options defining how a DocumentsStage is evaluated. See . | +| [ExpressionType](./firestore_pipelines.md#expressiontype) | (Public Preview) An enumeration of the different types of expressions. | +| [FindNearestStageOptions](./firestore_pipelines.md#findneareststageoptions) | (Public Preview) Options defining how a FindNearestStage is evaluated. See . | +| [LimitStageOptions](./firestore_pipelines.md#limitstageoptions) | (Public Preview) Options defining how a LimitStage is evaluated. See . | +| [OffsetStageOptions](./firestore_pipelines.md#offsetstageoptions) | (Public Preview) Options defining how an OffsetStage is evaluated. See . | +| [OneOf](./firestore_pipelines.md#oneof) | (Public Preview) Utility type to create an type that only allows one property of the Type param T to be set.type XorY = OneOf<{ x: unknown, y: unknown}> let a = { x: "foo" } // OK let b = { y: "foo" } // OK let c = { a: "foo", y: "foo" } // Not OK | +| [PartialWithFieldValue](./firestore_pipelines.md#partialwithfieldvalue) | Similar to TypeScript's Partial<T>, but allows nested fields to be omitted and FieldValues to be passed in as property values. | +| [Primitive](./firestore_pipelines.md#primitive) | Primitive types. | +| [RemoveFieldsStageOptions](./firestore_pipelines.md#removefieldsstageoptions) | (Public Preview) Options defining how a RemoveFieldsStage is evaluated. See . | +| [ReplaceWithStageOptions](./firestore_pipelines.md#replacewithstageoptions) | (Public Preview) Options defining how a ReplaceWithStage is evaluated. See . | +| [SampleStageOptions](./firestore_pipelines.md#samplestageoptions) | (Public Preview) Defines the options for evaluating a sample stage within a pipeline. This type combines common with a specific configuration where only one of the defined sampling methods can be applied.See to create a sample stage.. | +| [SelectStageOptions](./firestore_pipelines.md#selectstageoptions) | (Public Preview) Options defining how a SelectStage is evaluated. See . | +| [SetOptions](./firestore_pipelines.md#setoptions) | An options object that configures the behavior of [setDoc()](./firestore_lite.md#setdoc_ee215ad), and calls. These calls can be configured to perform granular merges instead of overwriting the target documents in their entirety by providing a SetOptions with merge: true. | +| [SortStageOptions](./firestore_pipelines.md#sortstageoptions) | (Public Preview) Options defining how a SortStage is evaluated. See . | +| [TimeGranularity](./firestore_pipelines.md#timegranularity) | (Public Preview) Specify time granularity for expressions. | +| [UnionStageOptions](./firestore_pipelines.md#unionstageoptions) | (Public Preview) Options defining how a UnionStage is evaluated. See . | +| [UnnestStageOptions](./firestore_pipelines.md#unneststageoptions) | (Public Preview) Represents the specific options available for configuring an UnnestStage within a pipeline. | +| [WhereStageOptions](./firestore_pipelines.md#wherestageoptions) | (Public Preview) Options defining how a WhereStage is evaluated. See . | +| [WithFieldValue](./firestore_pipelines.md#withfieldvalue) | Allows FieldValues to be passed in as a property value while maintaining type safety. | + +## function() + +### countAll() {:#countall} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that counts the total number of stage inputs. + +```typescript +// Count the total number of input documents +countAll().as("totalDocument"); + +``` + A new representing the 'countAll' aggregation. + +Signature: + +```typescript +export declare function countAll(): AggregateFunction; +``` +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +### currentTimestamp() {:#currenttimestamp} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that evaluates to the current server timestamp. + +```typescript +// Get the current server timestamp +currentTimestamp() + +``` + A new Expression representing the current server timestamp. + +Signature: + +```typescript +export declare function currentTimestamp(): FunctionExpression; +``` +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(array, ...) + +### arrayContains(array, element) {:#arraycontains_a00ea48} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an array expression contains a specific element. + +```typescript +// Check if the 'colors' array contains the value of field 'selectedColor' +arrayContains(field("colors"), field("selectedColor")); + +``` + +Signature: + +```typescript +export declare function arrayContains(array: Expression, element: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| array | [Expression](./firestore_pipelines.expression.md#expression_class) | The array expression to check. | +| element | [Expression](./firestore_pipelines.expression.md#expression_class) | The element to search for in the array. A new representing the 'array\_contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### arrayContains(array, element) {:#arraycontains_7328608} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an array expression contains a specific element. + +```typescript +// Check if the 'colors' array contains "red" +arrayContains(field("colors"), "red"); + +``` + +Signature: + +```typescript +export declare function arrayContains(array: Expression, element: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| array | [Expression](./firestore_pipelines.expression.md#expression_class) | The array expression to check. | +| element | unknown | The element to search for in the array. A new representing the 'array\_contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### arrayContainsAll(array, values) {:#arraycontainsall_c658ad5} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an array expression contains all the specified elements. + +```typescript +// Check if the "tags" array contains all of the values: "SciFi", "Adventure", and the value from field "tag1" +arrayContainsAll(field("tags"), [field("tag1"), constant("SciFi"), "Adventure"]); + +``` + +Signature: + +```typescript +export declare function arrayContainsAll(array: Expression, values: Array): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| array | [Expression](./firestore_pipelines.expression.md#expression_class) | The array expression to check. | +| values | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | The elements to check for in the array. A new representing the 'array\_contains\_all' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### arrayContainsAll(array, arrayExpression) {:#arraycontainsall_7b535db} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an array expression contains all the specified elements. + +```typescript +// Check if the "tags" array contains all of the values: "SciFi", "Adventure", and the value from field "tag1" +arrayContainsAll(field("tags"), [field("tag1"), constant("SciFi"), "Adventure"]); + +``` + +Signature: + +```typescript +export declare function arrayContainsAll(array: Expression, arrayExpression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| array | [Expression](./firestore_pipelines.expression.md#expression_class) | The array expression to check. | +| arrayExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The elements to check for in the array. A new representing the 'array\_contains\_all' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### arrayContainsAny(array, values) {:#arraycontainsany_c658ad5} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an array expression contains any of the specified elements. + +```typescript +// Check if the 'categories' array contains either values from field "cate1" or "Science" +arrayContainsAny(field("categories"), [field("cate1"), "Science"]); + +``` + +Signature: + +```typescript +export declare function arrayContainsAny(array: Expression, values: Array): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| array | [Expression](./firestore_pipelines.expression.md#expression_class) | The array expression to check. | +| values | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | The elements to check for in the array. A new representing the 'array\_contains\_any' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### arrayContainsAny(array, values) {:#arraycontainsany_c381a96} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an array expression contains any of the specified elements. + +```typescript +// Check if the 'categories' array contains either values from field "cate1" or "Science" +arrayContainsAny(field("categories"), array([field("cate1"), "Science"])); + +``` + +Signature: + +```typescript +export declare function arrayContainsAny(array: Expression, values: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| array | [Expression](./firestore_pipelines.expression.md#expression_class) | The array expression to check. | +| values | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression that evaluates to an array, whose elements to check for in the array. A new representing the 'array\_contains\_any' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### arrayLength(array) {:#arraylength_195e339} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the length of an array expression. + +```typescript +// Get the number of items in the 'cart' array +arrayLength(field("cart")); + +``` + +Signature: + +```typescript +export declare function arrayLength(array: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| array | [Expression](./firestore_pipelines.expression.md#expression_class) | The array expression to calculate the length of. A new representing the length of the array. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(arrayExpression, ...) + +### arrayGet(arrayExpression, offset) {:#arrayget_f2e27cc} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that indexes into an array from the beginning or end and return the element. If the offset exceeds the array length, an error is returned. A negative offset, starts from the end. + +```typescript +// Return the value in the tags field array at index 1. +arrayGet(field('tags'), 1); + +``` + +Signature: + +```typescript +export declare function arrayGet(arrayExpression: Expression, offset: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| arrayExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | An Expr evaluating to an array. | +| offset | number | The index of the element to return. A new Expr representing the 'arrayGet' operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### arrayGet(arrayExpression, offsetExpr) {:#arrayget_484550d} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that indexes into an array from the beginning or end and return the element. If the offset exceeds the array length, an error is returned. A negative offset, starts from the end. + +```typescript +// Return the value in the tags field array at index specified by field +// 'favoriteTag'. +arrayGet(field('tags'), field('favoriteTag')); + +``` + +Signature: + +```typescript +export declare function arrayGet(arrayExpression: Expression, offsetExpr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| arrayExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | An Expr evaluating to an array. | +| offsetExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | An Expr evaluating to the index of the element to return. A new Expr representing the 'arrayGet' operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### join(arrayExpression, delimiterExpression) {:#join_313e6aa} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that joins the elements of an array into a string. + +```typescript +// Join an array of string using the delimiter from the 'separator' field. +join(array(['foo', 'bar']), field("separator")) + +``` + +Signature: + +```typescript +export declare function join(arrayExpression: Expression, delimiterExpression: Expression): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| arrayExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression that evaluates to an array. | +| delimiterExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression that evaluates to the delimiter string. A new Expression representing the join operation. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +### join(arrayExpression, delimiter) {:#join_d088d29} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that joins the elements of an array into a string. + +```typescript +// Join the elements of the 'tags' field with a comma and space. +join(field("tags"), ", ") + +``` + +Signature: + +```typescript +export declare function join(arrayExpression: Expression, delimiter: string): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| arrayExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression that evaluates to an array. | +| delimiter | string | The string to use as a delimiter. A new Expression representing the join operation. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +## function(arrayField, ...) + +### arrayGet(arrayField, offset) {:#arrayget_3f58471} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that indexes into an array from the beginning or end and return the element. If the offset exceeds the array length, an error is returned. A negative offset, starts from the end. + +```typescript +// Return the value in the tags field array at index 1. +arrayGet('tags', 1); + +``` + +Signature: + +```typescript +export declare function arrayGet(arrayField: string, offset: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| arrayField | string | The name of the array field. | +| offset | number | The index of the element to return. A new Expr representing the 'arrayGet' operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### arrayGet(arrayField, offsetExpr) {:#arrayget_1904c9a} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that indexes into an array from the beginning or end and return the element. If the offset exceeds the array length, an error is returned. A negative offset, starts from the end. + +```typescript +// Return the value in the tags field array at index specified by field +// 'favoriteTag'. +arrayGet('tags', field('favoriteTag')); + +``` + +Signature: + +```typescript +export declare function arrayGet(arrayField: string, offsetExpr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| arrayField | string | The name of the array field. | +| offsetExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | An Expr evaluating to the index of the element to return. A new Expr representing the 'arrayGet' operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(arrayFieldName, ...) + +### join(arrayFieldName, delimiter) {:#join_478ef36} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that joins the elements of an array into a string. + +```typescript +// Join the elements of the 'tags' field with a comma and space. +join("tags", ", ") + +``` + +Signature: + +```typescript +export declare function join(arrayFieldName: string, delimiter: string): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| arrayFieldName | string | The name of the field containing the array. | +| delimiter | string | The string to use as a delimiter. A new Expression representing the join operation. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +### join(arrayFieldName, delimiterExpression) {:#join_829294c} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that joins the elements of an array into a string. + +```typescript +// Join the elements of the 'tags' field with the delimiter from the 'separator' field. +join('tags', field("separator")) + +``` + +Signature: + +```typescript +export declare function join(arrayFieldName: string, delimiterExpression: Expression): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| arrayFieldName | string | The name of the field containing the array. | +| delimiterExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression that evaluates to the delimiter string. A new Expression representing the join operation. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +## function(base, ...) + +### pow(base, exponent) {:#pow_e4a9e64} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the value of the base expression raised to the power of the exponent expression. + +```typescript +// Raise the value of the 'base' field to the power of the 'exponent' field. +pow(field("base"), field("exponent")); + +``` + +Signature: + +```typescript +export declare function pow(base: Expression, exponent: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| base | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to raise to the power of the exponent. | +| exponent | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to raise the base to the power of. A new Expr representing the power operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### pow(base, exponent) {:#pow_93eae7f} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the value of the base expression raised to the power of the exponent. + +```typescript +// Raise the value of the 'base' field to the power of 2. +pow(field("base"), 2); + +``` + +Signature: + +```typescript +export declare function pow(base: Expression, exponent: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| base | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to raise to the power of the exponent. | +| exponent | number | The constant value to raise the base to the power of. A new Expr representing the power operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### pow(base, exponent) {:#pow_a237721} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the value of the base field raised to the power of the exponent expression. + +```typescript +// Raise the value of the 'base' field to the power of the 'exponent' field. +pow("base", field("exponent")); + +``` + +Signature: + +```typescript +export declare function pow(base: string, exponent: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| base | string | The name of the field to raise to the power of the exponent. | +| exponent | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to raise the base to the power of. A new Expr representing the power operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### pow(base, exponent) {:#pow_f4d7908} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the value of the base field raised to the power of the exponent. + +```typescript +// Raise the value of the 'base' field to the power of 2. +pow("base", 2); + +``` + +Signature: + +```typescript +export declare function pow(base: string, exponent: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| base | string | The name of the field to raise to the power of the exponent. | +| exponent | number | The constant value to raise the base to the power of. A new Expr representing the power operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(booleanExpr, ...) + +### countIf(booleanExpr) {:#countif_c5b8fb1} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that counts the number of stage inputs where the provided boolean expression evaluates to true. + +```typescript +// Count the number of documents where 'is_active' field equals true +countIf(field("is_active").equal(true)).as("numActiveDocuments"); + +``` + +Signature: + +```typescript +export declare function countIf(booleanExpr: BooleanExpression): AggregateFunction; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| booleanExpr | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) | The boolean expression to evaluate on each input. | + +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +A new `AggregateFunction` representing the 'countIf' aggregation. + +### not(booleanExpr) {:#not_c5b8fb1} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that negates a filter condition. + +```typescript +// Find documents where the 'completed' field is NOT true +not(equal("completed", true)); + +``` + +Signature: + +```typescript +export declare function not(booleanExpr: BooleanExpression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| booleanExpr | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) | The filter condition to negate. A new representing the negated filter condition. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## function(condition, ...) + +### conditional(condition, thenExpr, elseExpr) {:#conditional_07a206d} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates a conditional expression that evaluates to a 'then' expression if a condition is true and an 'else' expression if the condition is false. + +```typescript +// If 'age' is greater than 18, return "Adult"; otherwise, return "Minor". +conditional( + greaterThan("age", 18), constant("Adult"), constant("Minor")); + +``` + +Signature: + +```typescript +export declare function conditional(condition: BooleanExpression, thenExpr: Expression, elseExpr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| condition | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) | The condition to evaluate. | +| thenExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to evaluate if the condition is true. | +| elseExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to evaluate if the condition is false. A new representing the conditional expression. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(documentPath, ...) + +### documentId(documentPath) {:#documentid_cef293c} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the document ID from a path. + +```typescript +// Get the document ID from a path. +documentId(myDocumentReference); + +``` + A new representing the documentId operation. + +Signature: + +```typescript +export declare function documentId(documentPath: string | DocumentReference): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| documentPath | string \| [DocumentReference](./firestore_.documentreference.md#documentreference_class) | | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(documentPathExpr, ...) + +### documentId(documentPathExpr) {:#documentid_9a69021} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the document ID from a path. + +```typescript +// Get the document ID from a path. +documentId(field("__path__")); + +``` + A new representing the documentId operation. + +Signature: + +```typescript +export declare function documentId(documentPathExpr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| documentPathExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(element, ...) + +### notEqualAny(element, values) {:#notequalany_c2c5bcb} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an expression is not equal to any of the provided values or expressions. + +```typescript +// Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' +notEqualAny(field("status"), ["pending", field("rejectedStatus")]); + +``` + +Signature: + +```typescript +export declare function notEqualAny(element: Expression, values: Array): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| element | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare. | +| values | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | The values to check against. A new representing the 'NOT IN' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### notEqualAny(element, arrayExpression) {:#notequalany_16b2851} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an expression is not equal to any of the provided values or expressions. + +```typescript +// Check if the 'status' field is neither "pending" nor the value of the field 'rejectedStatus' +notEqualAny(field("status"), ["pending", field("rejectedStatus")]); + +``` + +Signature: + +```typescript +export declare function notEqualAny(element: Expression, arrayExpression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| element | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare. | +| arrayExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The values to check against. A new representing the 'NOT IN' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## function(elements, ...) + +### array(elements) {:#array_7d853aa} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that creates a Firestore array value from an input array. + +```typescript +// Create an array value from the input array and reference the 'baz' field value from the input document. +array(['bar', Field.of('baz')]).as('foo'); + +``` + +Signature: + +```typescript +export declare function array(elements: unknown[]): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| elements | unknown\[\] | The input array to evaluate in the expression. A new representing the array function. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### map(elements) {:#map_ce5dee1} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that creates a Firestore map value from an input object. + +```typescript +// Create a map from the input object and reference the 'baz' field value from the input document. +map({foo: 'bar', baz: Field.of('baz')}).as('data'); + +``` + +Signature: + +```typescript +export declare function map(elements: Record): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| elements | Record<string, unknown> | The input map to evaluate in the expression. A new representing the map function. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(expr, ...) + +### abs(expr) {:#abs_005f3d4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the absolute value of a numeric value. + +Signature: + +```typescript +export declare function abs(expr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compute the absolute value of. A new representing the absolute value of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### ascending(expr) {:#ascending_005f3d4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an that sorts documents in ascending order based on an expression. + +```typescript +// Sort documents by the 'name' field in lowercase in ascending order +firestore.pipeline().collection("users") + .sort(ascending(field("name").toLower())); + +``` + +Signature: + +```typescript +export declare function ascending(expr: Expression): Ordering; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to create an ascending ordering for. A new Ordering for ascending sorting. | + +Returns: + +[Ordering](./firestore_pipelines.ordering.md#ordering_class) + +### byteLength(expr) {:#bytelength_005f3d4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the byte length of a string in UTF-8, or just the length of a Blob. + +```typescript +// Calculate the length of the 'myString' field in bytes. +byteLength(field("myString")); + +``` + +Signature: + +```typescript +export declare function byteLength(expr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the string. A new representing the length of the string in bytes. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### countDistinct(expr) {:#countdistinct_3c28b08} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that counts the number of distinct values of a field. + +Signature: + +```typescript +export declare function countDistinct(expr: Expression | string): AggregateFunction; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) \| string | The expression or field to count distinct values of. A new AggregateFunction representing the 'count\_distinct' aggregation. | + +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +### descending(expr) {:#descending_005f3d4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an that sorts documents in descending order based on an expression. + +```typescript +// Sort documents by the 'name' field in lowercase in descending order +firestore.pipeline().collection("users") + .sort(descending(field("name").toLower())); + +``` + +Signature: + +```typescript +export declare function descending(expr: Expression): Ordering; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to create a descending ordering for. A new Ordering for descending sorting. | + +Returns: + +[Ordering](./firestore_pipelines.ordering.md#ordering_class) + +### floor(expr) {:#floor_005f3d4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the floor of a numeric value. + +Signature: + +```typescript +export declare function floor(expr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compute the floor of. A new representing the floor of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### timestampToUnixMicros(expr) {:#timestamptounixmicros_005f3d4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that converts a timestamp expression to the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + +```typescript +// Convert the 'timestamp' field to microseconds since epoch. +timestampToUnixMicros(field("timestamp")); + +``` + +Signature: + +```typescript +export declare function timestampToUnixMicros(expr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the timestamp. A new representing the number of microseconds since epoch. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### timestampToUnixMillis(expr) {:#timestamptounixmillis_005f3d4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that converts a timestamp expression to the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + +```typescript +// Convert the 'timestamp' field to milliseconds since epoch. +timestampToUnixMillis(field("timestamp")); + +``` + +Signature: + +```typescript +export declare function timestampToUnixMillis(expr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the timestamp. A new representing the number of milliseconds since epoch. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### timestampToUnixSeconds(expr) {:#timestamptounixseconds_005f3d4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that converts a timestamp expression to the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC). + +```typescript +// Convert the 'timestamp' field to seconds since epoch. +timestampToUnixSeconds(field("timestamp")); + +``` + +Signature: + +```typescript +export declare function timestampToUnixSeconds(expr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the timestamp. A new representing the number of seconds since epoch. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### unixMicrosToTimestamp(expr) {:#unixmicrostotimestamp_005f3d4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that interprets an expression as the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. + +```typescript +// Interpret the 'microseconds' field as microseconds since epoch. +unixMicrosToTimestamp(field("microseconds")); + +``` + +Signature: + +```typescript +export declare function unixMicrosToTimestamp(expr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the number of microseconds since epoch. A new representing the timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### unixMillisToTimestamp(expr) {:#unixmillistotimestamp_005f3d4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that interprets an expression as the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. + +```typescript +// Interpret the 'milliseconds' field as milliseconds since epoch. +unixMillisToTimestamp(field("milliseconds")); + +``` + +Signature: + +```typescript +export declare function unixMillisToTimestamp(expr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the number of milliseconds since epoch. A new representing the timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### unixSecondsToTimestamp(expr) {:#unixsecondstotimestamp_005f3d4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that interprets an expression as the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. + +```typescript +// Interpret the 'seconds' field as seconds since epoch. +unixSecondsToTimestamp(field("seconds")); + +``` + +Signature: + +```typescript +export declare function unixSecondsToTimestamp(expr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the number of seconds since epoch. A new representing the timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(expression, ...) + +### arraySum(expression) {:#arraysum_1138a27} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the sum of the elements in an array. + +```typescript +// Compute the sum of the elements in the 'scores' field. +arraySum(field("scores")); + +``` + +Signature: + +```typescript +export declare function arraySum(expression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression evaluating to a numeric array, which the sum will be computed for. A new Expr representing the sum of the elements in the array. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### average(expression) {:#average_1138a27} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that calculates the average (mean) of values from an expression across multiple stage inputs. + +```typescript +// Calculate the average age of users +average(field("age")).as("averageAge"); + +``` + +Signature: + +```typescript +export declare function average(expression: Expression): AggregateFunction; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the values to average. A new representing the 'average' aggregation. | + +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +### ceil(expression) {:#ceil_1138a27} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the ceiling of a numeric value. + +```typescript +// Compute the ceiling of the 'price' field. +ceil(field("price")); + +``` + +Signature: + +```typescript +export declare function ceil(expression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression evaluating to a numeric value, which the ceiling will be computed for. A new representing the ceiling of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### collectionId(expression) {:#collectionid_1138a27} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the collection ID from a path. + +```typescript +// Get the collection ID from a path. +collectionId(field("__name__")); + +``` + +Signature: + +```typescript +export declare function collectionId(expression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression evaluating to a path, which the collection ID will be extracted from. A new representing the collectionId operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### count(expression) {:#count_1138a27} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that counts the number of stage inputs with valid evaluations of the provided expression. + +```typescript +// Count the number of items where the price is greater than 10 +count(field("price").greaterThan(10)).as("expensiveItemCount"); + +``` + +Signature: + +```typescript +export declare function count(expression: Expression): AggregateFunction; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to count. A new representing the 'count' aggregation. | + +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +### divide(expression, value) {:#divide_01df3cf} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that divides an expression by a constant value. + +```typescript +// Divide the 'value' field by 10 +divide(field("value"), 10); + +``` + +Signature: + +```typescript +export declare function divide(expression: Expression, value: unknown): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to be divided. | +| value | unknown | The constant value to divide by. A new representing the division operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### equal(expression, value) {:#equal_01df3cf} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an expression is equal to a constant value. + +```typescript +// Check if the 'age' field is equal to 21 +equal(field("age"), 21); + +``` + +Signature: + +```typescript +export declare function equal(expression: Expression, value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare. | +| value | unknown | The constant value to compare to. A new Expr representing the equality comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### equalAny(expression, values) {:#equalany_7e759b5} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an expression, when evaluated, is equal to any of the provided values or expressions. + +```typescript +// Check if the 'category' field is either "Electronics" or value of field 'primaryType' +equalAny(field("category"), [constant("Electronics"), field("primaryType")]); + +``` + +Signature: + +```typescript +export declare function equalAny(expression: Expression, values: Array): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression whose results to compare. | +| values | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | The values to check against. A new representing the 'IN' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### equalAny(expression, arrayExpression) {:#equalany_214ce68} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an expression is equal to any of the provided values. + +```typescript +// Check if the 'category' field is set to a value in the disabledCategories field +equalAny(field("category"), field('disabledCategories')); + +``` + +Signature: + +```typescript +export declare function equalAny(expression: Expression, arrayExpression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression whose results to compare. | +| arrayExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression that evaluates to an array, whose elements to check for equality to the input. A new representing the 'IN' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### exp(expression) {:#exp_1138a27} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes e to the power of the expression's result. + +```typescript +// Compute e to the power of 2. +exp(constant(2)); + +``` + A new representing the exp of the numeric value. + +Signature: + +```typescript +export declare function exp(expression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### greaterThan(expression, value) {:#greaterthan_01df3cf} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an expression is greater than a constant value. + +```typescript +// Check if the 'age' field is greater than 18 +greaterThan(field("age"), 18); + +``` + +Signature: + +```typescript +export declare function greaterThan(expression: Expression, value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare. | +| value | unknown | The constant value to compare to. A new Expr representing the greater than comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### greaterThanOrEqual(expression, value) {:#greaterthanorequal_01df3cf} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an expression is greater than or equal to a constant value. + +```typescript +// Check if the 'quantity' field is greater than or equal to 10 +greaterThanOrEqual(field("quantity"), 10); + +``` + +Signature: + +```typescript +export declare function greaterThanOrEqual(expression: Expression, value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare. | +| value | unknown | The constant value to compare to. A new Expr representing the greater than or equal to comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### length\_2(expression) {:#length_2_1138a27} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the length of a string, array, map, vector, or bytes. + +```typescript +// Get the length of the 'name' field. +length(field("name")); + +// Get the number of items in the 'cart' array. +length(field("cart")); + +``` + +Signature: + +```typescript +declare function length_2(expression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression evaluating to a string, array, map, vector, or bytes, which the length will be calculated for. A new Expr representing the length of the string, array, map, vector, or bytes. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### lessThan(expression, value) {:#lessthan_01df3cf} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an expression is less than a constant value. + +```typescript +// Check if the 'age' field is less than 30 +lessThan(field("age"), 30); + +``` + +Signature: + +```typescript +export declare function lessThan(expression: Expression, value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare. | +| value | unknown | The constant value to compare to. A new Expr representing the less than comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### lessThanOrEqual(expression, value) {:#lessthanorequal_01df3cf} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an expression is less than or equal to a constant value. + +```typescript +// Check if the 'quantity' field is less than or equal to 20 +lessThan(field("quantity"), 20); + +``` + +Signature: + +```typescript +export declare function lessThanOrEqual(expression: Expression, value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare. | +| value | unknown | The constant value to compare to. A new Expr representing the less than or equal to comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### ln(expression) {:#ln_1138a27} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the natural logarithm of a numeric value. + +```typescript +// Compute the natural logarithm of the 'value' field. +ln(field("value")); + +``` + +Signature: + +```typescript +export declare function ln(expression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression evaluating to a numeric value, which the natural logarithm will be computed for. A new Expr representing the natural logarithm of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### log(expression, base) {:#log_ac183e2} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the logarithm of an expression to a given base. + +```typescript +// Compute the logarithm of the 'value' field with base 10. +log(field("value"), 10); + +``` + +Signature: + +```typescript +export declare function log(expression: Expression, base: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression evaluating to a numeric value, which the logarithm will be computed for. | +| base | number | The base of the logarithm. A new representing the logarithm of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### log(expression, base) {:#log_1894737} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the logarithm of an expression to a given base. + +```typescript +// Compute the logarithm of the 'value' field with the base in the 'base' field. +log(field("value"), field("base")); + +``` + +Signature: + +```typescript +export declare function log(expression: Expression, base: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression evaluating to a numeric value, which the logarithm will be computed for. | +| base | [Expression](./firestore_pipelines.expression.md#expression_class) | The base of the logarithm. A new representing the logarithm of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### log10(expression) {:#log10_1138a27} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the base-10 logarithm of a numeric value. + +```typescript +// Compute the base-10 logarithm of the 'value' field. +log10(field("value")); + +``` + +Signature: + +```typescript +export declare function log10(expression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression evaluating to a numeric value, which the base-10 logarithm will be computed for. A new Expr representing the base-10 logarithm of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### maximum(expression) {:#maximum_1138a27} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that finds the maximum value of an expression across multiple stage inputs. + +```typescript +// Find the highest score in a leaderboard +maximum(field("score")).as("highestScore"); + +``` + +Signature: + +```typescript +export declare function maximum(expression: Expression): AggregateFunction; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to find the maximum value of. A new representing the 'maximum' aggregation. | + +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +### minimum(expression) {:#minimum_1138a27} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that finds the minimum value of an expression across multiple stage inputs. + +```typescript +// Find the lowest price of all products +minimum(field("price")).as("lowestPrice"); + +``` + +Signature: + +```typescript +export declare function minimum(expression: Expression): AggregateFunction; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to find the minimum value of. A new representing the 'minimum' aggregation. | + +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +### mod(expression, value) {:#mod_01df3cf} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the modulo (remainder) of dividing an expression by a constant. + +```typescript +// Calculate the remainder of dividing 'field1' by 5. +mod(field("field1"), 5); + +``` + +Signature: + +```typescript +export declare function mod(expression: Expression, value: unknown): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The dividend expression. | +| value | unknown | The divisor constant. A new representing the modulo operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### notEqual(expression, value) {:#notequal_01df3cf} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if an expression is not equal to a constant value. + +```typescript +// Check if the 'status' field is not equal to "completed" +notEqual(field("status"), "completed"); + +``` + +Signature: + +```typescript +export declare function notEqual(expression: Expression, value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare. | +| value | unknown | The constant value to compare to. A new Expr representing the inequality comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### round(expression) {:#round_1138a27} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that rounds a numeric value to the nearest whole number. + +```typescript +// Round the value of the 'price' field. +round(field("price")); + +``` + +Signature: + +```typescript +export declare function round(expression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression evaluating to a numeric value, which will be rounded. A new Expr representing the rounded value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### round(expression, decimalPlaces) {:#round_a3a92d0} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that rounds a numeric value to the specified number of decimal places. + +```typescript +// Round the value of the 'price' field to two decimal places. +round(field("price"), constant(2)); + +``` + +Signature: + +```typescript +export declare function round(expression: Expression, decimalPlaces: number | Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression evaluating to a numeric value, which will be rounded. | +| decimalPlaces | number \| [Expression](./firestore_pipelines.expression.md#expression_class) | A constant or expression specifying the rounding precision in decimal places. A new Expr representing the rounded value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### split(expression, delimiter) {:#split_5b5612b} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that splits a string into an array of substrings based on the provided delimiter. + +Signature: + +```typescript +export declare function split(expression: Expression, delimiter: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | Split the result of this expression. | +| delimiter | string | Split on this delimiter. A new representing the split function. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### Example + + +```typescript +// Split the 'scoresCsv' field on delimiter ',' +split(field('scoresCsv'), ',') + +``` + +### split(expression, delimiter) {:#split_5a171ed} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that splits a string into an array of substrings based on the provided delimiter. + +Signature: + +```typescript +export declare function split(expression: Expression, delimiter: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | Split the result of this expression. | +| delimiter | [Expression](./firestore_pipelines.expression.md#expression_class) | Split on this delimiter returned by evaluating this expression. A new representing the split function. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### Example + + +```typescript +// Split the 'scores' field on delimiter ',' or ':' depending on the stored format +split(field('scores'), conditional(field('format').equal('csv'), constant(','), constant(':')) + +``` + +### sqrt(expression) {:#sqrt_1138a27} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the square root of a numeric value. + +```typescript +// Compute the square root of the 'value' field. +sqrt(field("value")); + +``` + +Signature: + +```typescript +export declare function sqrt(expression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression evaluating to a numeric value, which the square root will be computed for. A new representing the square root of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### subtract(expression, value) {:#subtract_01df3cf} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that subtracts a constant value from an expression. + +```typescript +// Subtract the constant value 2 from the 'value' field +subtract(field("value"), 2); + +``` + +Signature: + +```typescript +export declare function subtract(expression: Expression, value: unknown): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to subtract from. | +| value | unknown | The constant value to subtract. A new representing the subtraction operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### sum(expression) {:#sum_1138a27} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that calculates the sum of values from an expression across multiple stage inputs. + +```typescript +// Calculate the total revenue from a set of orders +sum(field("orderAmount")).as("totalRevenue"); + +``` + +Signature: + +```typescript +export declare function sum(expression: Expression): AggregateFunction; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to sum up. A new representing the 'sum' aggregation. | + +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +### type(expression) {:#type_1138a27} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the data type of an expression's result. + +Signature: + +```typescript +export declare function type(expression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### Example + + +```typescript +// Get the data type of a conditional expression +type(conditional(exists('foo'), constant(1), constant(true))) + +``` + A new {Expression} representing the data type. + +## function(field, ...) + +### isAbsent(field) {:#isabsent_0fb8cd4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns `true` if a field is absent. Otherwise, returns `false` even if the field value is `null`. + +```typescript +// Check if the field `value` is absent. +isAbsent("value"); + +``` + +Signature: + +```typescript +export declare function isAbsent(field: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| field | string | The field to check. A new representing the 'isAbsent' check. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### reverse(field) {:#reverse_0fb8cd4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that reverses a string value in the specified field. + +```typescript +// Reverse the value of the 'myString' field. +reverse("myString"); + +``` + +Signature: + +```typescript +export declare function reverse(field: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| field | string | The name of the field representing the string to reverse. A new representing the reversed string. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### stringReverse(field) {:#stringreverse_0fb8cd4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that reverses a string value in the specified field. + +```typescript +// Reverse the value of the 'myString' field. +strReverse("myString"); + +``` + +Signature: + +```typescript +export declare function stringReverse(field: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| field | string | The name of the field representing the string to reverse. A new representing the reversed string. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### substring(field, position, length) {:#substring_0d9573a} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns a substring of a string or byte array. + +Signature: + +```typescript +export declare function substring(field: string, position: number, length?: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| field | string | The name of a field containing a string or byte array to compute the substring from. | +| position | number | Index of the first character of the substring. | +| length | number | Length of the substring. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### substring(field, position, length) {:#substring_05cb14e} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns a substring of a string or byte array. + +Signature: + +```typescript +export declare function substring(field: string, position: Expression, length?: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| field | string | The name of a field containing a string or byte array to compute the substring from. | +| position | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression that returns the index of the first character of the substring. | +| length | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression that returns the length of the substring. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(fieldName, ...) + +### abs(fieldName) {:#abs_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the absolute value of a numeric value. + +Signature: + +```typescript +export declare function abs(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field to compute the absolute value of. A new representing the absolute value of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### add(fieldName, second) {:#add_b75bb8b} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that adds a field's value to an expression. + +```typescript +// Add the value of the 'quantity' field and the 'reserve' field. +add("quantity", field("reserve")); + +``` + +Signature: + +```typescript +export declare function add(fieldName: string, second: Expression | unknown): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the value to add. | +| second | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown | The second expression or literal to add. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### arrayContains(fieldName, element) {:#arraycontains_aaace4a} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's array value contains a specific element. + +```typescript +// Check if the 'colors' array contains the value of field 'selectedColor' +arrayContains("colors", field("selectedColor")); + +``` + +Signature: + +```typescript +export declare function arrayContains(fieldName: string, element: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to check. | +| element | [Expression](./firestore_pipelines.expression.md#expression_class) | The element to search for in the array. A new representing the 'array\_contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### arrayContains(fieldName, element) {:#arraycontains_999590f} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's array value contains a specific value. + +```typescript +// Check if the 'colors' array contains "red" +arrayContains("colors", "red"); + +``` + +Signature: + +```typescript +export declare function arrayContains(fieldName: string, element: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to check. | +| element | unknown | The element to search for in the array. A new representing the 'array\_contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### arrayContainsAll(fieldName, values) {:#arraycontainsall_8060b23} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's array value contains all the specified values or expressions. + +```typescript +// Check if the 'tags' array contains both of the values from field 'tag1', the value "SciFi", and "Adventure" +arrayContainsAll("tags", [field("tag1"), "SciFi", "Adventure"]); + +``` + +Signature: + +```typescript +export declare function arrayContainsAll(fieldName: string, values: Array): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to check. | +| values | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | The elements to check for in the array. A new representing the 'array\_contains\_all' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### arrayContainsAll(fieldName, arrayExpression) {:#arraycontainsall_48da8d9} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's array value contains all the specified values or expressions. + +```typescript +// Check if the 'tags' array contains both of the values from field 'tag1', the value "SciFi", and "Adventure" +arrayContainsAll("tags", [field("tag1"), "SciFi", "Adventure"]); + +``` + +Signature: + +```typescript +export declare function arrayContainsAll(fieldName: string, arrayExpression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to check. | +| arrayExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The elements to check for in the array. A new representing the 'array\_contains\_all' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### arrayContainsAny(fieldName, values) {:#arraycontainsany_8060b23} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's array value contains any of the specified elements. + +```typescript +// Check if the 'groups' array contains either the value from the 'userGroup' field +// or the value "guest" +arrayContainsAny("categories", [field("cate1"), "Science"]); + +``` + +Signature: + +```typescript +export declare function arrayContainsAny(fieldName: string, values: Array): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to check. | +| values | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | The elements to check for in the array. A new representing the 'array\_contains\_any' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### arrayContainsAny(fieldName, values) {:#arraycontainsany_1b4f7cd} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's array value contains any of the specified elements. + +```typescript +// Check if the 'groups' array contains either the value from the 'userGroup' field +// or the value "guest" +arrayContainsAny("categories", array([field("cate1"), "Science"])); + +``` + +Signature: + +```typescript +export declare function arrayContainsAny(fieldName: string, values: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to check. | +| values | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression that evaluates to an array, whose elements to check for in the array field. A new representing the 'array\_contains\_any' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### arrayLength(fieldName) {:#arraylength_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the length of an array in a specified field. + +```typescript +// Get the number of items in field 'cart' +arrayLength('cart'); + +``` + +Signature: + +```typescript +export declare function arrayLength(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing an array to calculate the length of. A new representing the length of the array. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### arraySum(fieldName) {:#arraysum_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the sum of the elements in an array. + +```typescript +// Compute the sum of the elements in the 'scores' field. +arraySum("scores"); + +``` + +Signature: + +```typescript +export declare function arraySum(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field to compute the sum of. A new Expr representing the sum of the elements in the array. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### ascending(fieldName) {:#ascending_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an that sorts documents in ascending order based on a field. + +```typescript +// Sort documents by the 'name' field in ascending order +firestore.pipeline().collection("users") + .sort(ascending("name")); + +``` + +Signature: + +```typescript +export declare function ascending(fieldName: string): Ordering; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field to create an ascending ordering for. A new Ordering for ascending sorting. | + +Returns: + +[Ordering](./firestore_pipelines.ordering.md#ordering_class) + +### average(fieldName) {:#average_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that calculates the average (mean) of a field's values across multiple stage inputs. + +```typescript +// Calculate the average age of users +average("age").as("averageAge"); + +``` + +Signature: + +```typescript +export declare function average(fieldName: string): AggregateFunction; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing numeric values to average. A new representing the 'average' aggregation. | + +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +### byteLength(fieldName) {:#bytelength_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the length of a string represented by a field in UTF-8 bytes, or just the length of a Blob. + +```typescript +// Calculate the length of the 'myString' field in bytes. +byteLength("myString"); + +``` + +Signature: + +```typescript +export declare function byteLength(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the string. A new representing the length of the string in bytes. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### ceil(fieldName) {:#ceil_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the ceiling of a numeric value. + +```typescript +// Compute the ceiling of the 'price' field. +ceil("price"); + +``` + +Signature: + +```typescript +export declare function ceil(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field to compute the ceiling of. A new representing the ceiling of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### charLength(fieldName) {:#charlength_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the character length of a string field in UTF8. + +```typescript +// Get the character length of the 'name' field in UTF-8. +strLength("name"); + +``` + +Signature: + +```typescript +export declare function charLength(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the string. A new representing the length of the string. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### collectionId(fieldName) {:#collectionid_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the collection ID from a path. + +```typescript +// Get the collection ID from a path. +collectionId("__name__"); + +``` + +Signature: + +```typescript +export declare function collectionId(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field to get the collection ID from. A new representing the collectionId operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### concat(fieldName, second, others) {:#concat_828272e} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that concatenates strings, arrays, or blobs. Types cannot be mixed. + +```typescript +// Concatenate a field with a literal string. +concat(field("firstName"), "Doe") + +``` + +Signature: + +```typescript +export declare function concat(fieldName: string, second: Expression | unknown, ...others: Array): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of a field to concatenate. | +| second | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown | The second literal or expression to concatenate. | +| others | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | Additional literal or expressions to concatenate. A new Expression representing the concatenation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### cosineDistance(fieldName, vector) {:#cosinedistance_463a23e} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the Cosine distance between a field's vector value and a literal vector value. + +```typescript +// Calculate the Cosine distance between the 'location' field and a target location +cosineDistance("location", [37.7749, -122.4194]); + +``` + +Signature: + +```typescript +export declare function cosineDistance(fieldName: string, vector: number[] | VectorValue): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the first vector. | +| vector | number\[\] \| [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) | The other vector (as an array of doubles) or [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) to compare against. A new representing the Cosine distance between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### cosineDistance(fieldName, vectorExpression) {:#cosinedistance_ed766a1} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the Cosine distance between a field's vector value and a vector expression. + +```typescript +// Calculate the cosine distance between the 'userVector' field and the 'itemVector' field +cosineDistance("userVector", field("itemVector")); + +``` + +Signature: + +```typescript +export declare function cosineDistance(fieldName: string, vectorExpression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the first vector. | +| vectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The other vector (represented as an Expr) to compare against. A new representing the cosine distance between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### count(fieldName) {:#count_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that counts the number of stage inputs where the input field exists. + +```typescript +// Count the total number of products +count("productId").as("totalProducts"); + +``` + +Signature: + +```typescript +export declare function count(fieldName: string): AggregateFunction; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field to count. A new representing the 'count' aggregation. | + +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +### descending(fieldName) {:#descending_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an that sorts documents in descending order based on a field. + +```typescript +// Sort documents by the 'name' field in descending order +firestore.pipeline().collection("users") + .sort(descending("name")); + +``` + +Signature: + +```typescript +export declare function descending(fieldName: string): Ordering; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field to create a descending ordering for. A new Ordering for descending sorting. | + +Returns: + +[Ordering](./firestore_pipelines.ordering.md#ordering_class) + +### divide(fieldName, expressions) {:#divide_cf36e43} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that divides a field's value by an expression. + +```typescript +// Divide the 'total' field by the 'count' field +divide("total", field("count")); + +``` + +Signature: + +```typescript +export declare function divide(fieldName: string, expressions: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to be divided. | +| expressions | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to divide by. A new representing the division operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### divide(fieldName, value) {:#divide_65e2f32} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that divides a field's value by a constant value. + +```typescript +// Divide the 'value' field by 10 +divide("value", 10); + +``` + +Signature: + +```typescript +export declare function divide(fieldName: string, value: unknown): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to be divided. | +| value | unknown | The constant value to divide by. A new representing the division operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### dotProduct(fieldName, vector) {:#dotproduct_463a23e} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the dot product between a field's vector value and a double array. + +```typescript +// Calculate the dot product distance between a feature vector and a target vector +dotProduct("features", [0.5, 0.8, 0.2]); + +``` + +Signature: + +```typescript +export declare function dotProduct(fieldName: string, vector: number[] | VectorValue): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the first vector. | +| vector | number\[\] \| [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) | The other vector (as an array of doubles or VectorValue) to calculate with. A new representing the dot product between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### dotProduct(fieldName, vectorExpression) {:#dotproduct_ed766a1} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the dot product between a field's vector value and a vector expression. + +```typescript +// Calculate the dot product distance between two document vectors: 'docVector1' and 'docVector2' +dotProduct("docVector1", field("docVector2")); + +``` + +Signature: + +```typescript +export declare function dotProduct(fieldName: string, vectorExpression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the first vector. | +| vectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The other vector (represented as an Expr) to calculate with. A new representing the dot product between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### endsWith(fieldName, suffix) {:#endswith_05ca3b0} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value ends with a given postfix. + +```typescript +// Check if the 'filename' field ends with ".txt" +endsWith("filename", ".txt"); + +``` + +Signature: + +```typescript +export declare function endsWith(fieldName: string, suffix: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to check. | +| suffix | string | The postfix to check for. A new representing the 'ends with' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### endsWith(fieldName, suffix) {:#endswith_8fc0ebc} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value ends with a given postfix. + +```typescript +// Check if the 'url' field ends with the value of the 'extension' field +endsWith("url", field("extension")); + +``` + +Signature: + +```typescript +export declare function endsWith(fieldName: string, suffix: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to check. | +| suffix | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the postfix. A new representing the 'ends with' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### equal(fieldName, expression) {:#equal_1e91657} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is equal to an expression. + +```typescript +// Check if the 'age' field is equal to the 'limit' field +equal("age", field("limit")); + +``` + +Signature: + +```typescript +export declare function equal(fieldName: string, expression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to compare. | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare to. A new Expr representing the equality comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### equal(fieldName, value) {:#equal_65e2f32} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is equal to a constant value. + +```typescript +// Check if the 'city' field is equal to string constant "London" +equal("city", "London"); + +``` + +Signature: + +```typescript +export declare function equal(fieldName: string, value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to compare. | +| value | unknown | The constant value to compare to. A new Expr representing the equality comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### equalAny(fieldName, values) {:#equalany_8060b23} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is equal to any of the provided values or expressions. + +```typescript +// Check if the 'category' field is either "Electronics" or value of field 'primaryType' +equalAny("category", [constant("Electronics"), field("primaryType")]); + +``` + +Signature: + +```typescript +export declare function equalAny(fieldName: string, values: Array): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field to compare. | +| values | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | The values to check against. A new representing the 'IN' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### equalAny(fieldName, arrayExpression) {:#equalany_48da8d9} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is equal to any of the provided values or expressions. + +```typescript +// Check if the 'category' field is either "Electronics" or value of field 'primaryType' +equalAny("category", ["Electronics", field("primaryType")]); + +``` + +Signature: + +```typescript +export declare function equalAny(fieldName: string, arrayExpression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field to compare. | +| arrayExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression that evaluates to an array, whose elements to check for equality to the input field. A new representing the 'IN' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### euclideanDistance(fieldName, vector) {:#euclideandistance_463a23e} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the Euclidean distance between a field's vector value and a double array. + +```typescript +// Calculate the Euclidean distance between the 'location' field and a target location +euclideanDistance("location", [37.7749, -122.4194]); + +``` + +Signature: + +```typescript +export declare function euclideanDistance(fieldName: string, vector: number[] | VectorValue): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the first vector. | +| vector | number\[\] \| [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) | The other vector (as an array of doubles or VectorValue) to compare against. A new representing the Euclidean distance between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### euclideanDistance(fieldName, vectorExpression) {:#euclideandistance_ed766a1} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the Euclidean distance between a field's vector value and a vector expression. + +```typescript +// Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB' +euclideanDistance("pointA", field("pointB")); + +``` + +Signature: + +```typescript +export declare function euclideanDistance(fieldName: string, vectorExpression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the first vector. | +| vectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The other vector (represented as an Expr) to compare against. A new representing the Euclidean distance between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### exists(fieldName) {:#exists_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field exists. + +```typescript +// Check if the document has a field named "phoneNumber" +exists("phoneNumber"); + +``` + +Signature: + +```typescript +export declare function exists(fieldName: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to check. A new representing the 'exists' check. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### exp(fieldName) {:#exp_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes e to the power of the expression's result. + +```typescript +// Compute e to the power of the 'value' field. +exp('value'); + +``` + A new representing the exp of the numeric value. + +Signature: + +```typescript +export declare function exp(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### floor(fieldName) {:#floor_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the floor of a numeric value. + +Signature: + +```typescript +export declare function floor(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field to compute the floor of. A new representing the floor of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### greaterThan(fieldName, expression) {:#greaterthan_1e91657} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is greater than an expression. + +```typescript +// Check if the value of field 'age' is greater than the value of field 'limit' +greaterThan("age", field("limit")); + +``` + +Signature: + +```typescript +export declare function greaterThan(fieldName: string, expression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to compare. | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare to. A new Expr representing the greater than comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### greaterThan(fieldName, value) {:#greaterthan_65e2f32} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is greater than a constant value. + +```typescript +// Check if the 'price' field is greater than 100 +greaterThan("price", 100); + +``` + +Signature: + +```typescript +export declare function greaterThan(fieldName: string, value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to compare. | +| value | unknown | The constant value to compare to. A new Expr representing the greater than comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### greaterThanOrEqual(fieldName, value) {:#greaterthanorequal_2e16acb} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is greater than or equal to an expression. + +```typescript +// Check if the value of field 'age' is greater than or equal to the value of field 'limit' +greaterThanOrEqual("age", field("limit")); + +``` + +Signature: + +```typescript +export declare function greaterThanOrEqual(fieldName: string, value: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to compare. | +| value | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare to. A new Expr representing the greater than or equal to comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### greaterThanOrEqual(fieldName, value) {:#greaterthanorequal_65e2f32} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is greater than or equal to a constant value. + +```typescript +// Check if the 'score' field is greater than or equal to 80 +greaterThanOrEqual("score", 80); + +``` + +Signature: + +```typescript +export declare function greaterThanOrEqual(fieldName: string, value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to compare. | +| value | unknown | The constant value to compare to. A new Expr representing the greater than or equal to comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### length\_2(fieldName) {:#length_2_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the length of a string, array, map, vector, or bytes. + +```typescript +// Get the length of the 'name' field. +length("name"); + +// Get the number of items in the 'cart' array. +length("cart"); + +``` + +Signature: + +```typescript +declare function length_2(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field to calculate the length of. A new Expr representing the length of the string, array, map, vector, or bytes. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### lessThan(fieldName, expression) {:#lessthan_1e91657} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is less than an expression. + +```typescript +// Check if the 'age' field is less than the 'limit' field +lessThan("age", field("limit")); + +``` + +Signature: + +```typescript +export declare function lessThan(fieldName: string, expression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to compare. | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare to. A new Expr representing the less than comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### lessThan(fieldName, value) {:#lessthan_65e2f32} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is less than a constant value. + +```typescript +// Check if the 'price' field is less than 50 +lessThan("price", 50); + +``` + +Signature: + +```typescript +export declare function lessThan(fieldName: string, value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to compare. | +| value | unknown | The constant value to compare to. A new Expr representing the less than comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### lessThanOrEqual(fieldName, expression) {:#lessthanorequal_1e91657} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is less than or equal to an expression. + +```typescript +// Check if the 'quantity' field is less than or equal to the 'limit' field +lessThan("quantity", field("limit")); + +``` + +Signature: + +```typescript +export declare function lessThanOrEqual(fieldName: string, expression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to compare. | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare to. A new Expr representing the less than or equal to comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### lessThanOrEqual(fieldName, value) {:#lessthanorequal_65e2f32} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is less than or equal to a constant value. + +```typescript +// Check if the 'score' field is less than or equal to 70 +lessThan("score", 70); + +``` + +Signature: + +```typescript +export declare function lessThanOrEqual(fieldName: string, value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to compare. | +| value | unknown | The constant value to compare to. A new Expr representing the less than or equal to comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### like(fieldName, pattern) {:#like_67f7432} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that performs a case-sensitive wildcard string comparison against a field. + +```typescript +// Check if the 'title' field contains the string "guide" +like("title", "%guide%"); + +``` + +Signature: + +```typescript +export declare function like(fieldName: string, pattern: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the string. | +| pattern | string | The pattern to search for. You can use "%" as a wildcard character. A new representing the 'like' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### like(fieldName, pattern) {:#like_cb1318d} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that performs a case-sensitive wildcard string comparison against a field. + +```typescript +// Check if the 'title' field contains the string "guide" +like("title", field("pattern")); + +``` + +Signature: + +```typescript +export declare function like(fieldName: string, pattern: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the string. | +| pattern | [Expression](./firestore_pipelines.expression.md#expression_class) | The pattern to search for. You can use "%" as a wildcard character. A new representing the 'like' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### ln(fieldName) {:#ln_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the natural logarithm of a numeric value. + +```typescript +// Compute the natural logarithm of the 'value' field. +ln("value"); + +``` + +Signature: + +```typescript +export declare function ln(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field to compute the natural logarithm of. A new Expr representing the natural logarithm of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### log(fieldName, base) {:#log_a89e21b} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the logarithm of a field to a given base. + +```typescript +// Compute the logarithm of the 'value' field with base 10. +log("value", 10); + +``` + +Signature: + +```typescript +export declare function log(fieldName: string, base: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field to compute the logarithm of. | +| base | number | The base of the logarithm. A new representing the logarithm of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### log(fieldName, base) {:#log_805b11f} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the logarithm of a field to a given base. + +```typescript +// Compute the logarithm of the 'value' field with the base in the 'base' field. +log("value", field("base")); + +``` + +Signature: + +```typescript +export declare function log(fieldName: string, base: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field to compute the logarithm of. | +| base | [Expression](./firestore_pipelines.expression.md#expression_class) | The base of the logarithm. A new representing the logarithm of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### log10(fieldName) {:#log10_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the base-10 logarithm of a numeric value. + +```typescript +// Compute the base-10 logarithm of the 'value' field. +log10("value"); + +``` + +Signature: + +```typescript +export declare function log10(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field to compute the base-10 logarithm of. A new Expr representing the base-10 logarithm of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### logicalMaximum(fieldName, second, others) {:#logicalmaximum_828272e} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the largest value between multiple input expressions or literal values. Based on Firestore's value type ordering. + +```typescript +// Returns the largest value between the 'field1' field, the 'field2' field, +// and 1000. +logicalMaximum("field1", field("field2"), 1000); + +``` + +Signature: + +```typescript +export declare function logicalMaximum(fieldName: string, second: Expression | unknown, ...others: Array): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The first operand field name. | +| second | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown | The second expression or literal. | +| others | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | Optional additional expressions or literals. A new representing the logical maximum operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### logicalMinimum(fieldName, second, others) {:#logicalminimum_828272e} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the smallest value between a field's value and other input expressions or literal values. Based on Firestore's value type ordering. + +```typescript +// Returns the smallest value between the 'field1' field, the 'field2' field, +// and 1000. +logicalMinimum("field1", field("field2"), 1000); + +``` + +Signature: + +```typescript +export declare function logicalMinimum(fieldName: string, second: Expression | unknown, ...others: Array): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The first operand field name. | +| second | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown | The second expression or literal. | +| others | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | Optional additional expressions or literals. A new representing the logical minimum operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### mapGet(fieldName, subField) {:#mapget_06663cf} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Accesses a value from a map (object) field using the provided key. + +```typescript +// Get the 'city' value from the 'address' map field +mapGet("address", "city"); + +``` + +Signature: + +```typescript +export declare function mapGet(fieldName: string, subField: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name of the map field. | +| subField | string | The key to access in the map. A new representing the value associated with the given key in the map. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### maximum(fieldName) {:#maximum_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that finds the maximum value of a field across multiple stage inputs. + +```typescript +// Find the highest score in a leaderboard +maximum("score").as("highestScore"); + +``` + +Signature: + +```typescript +export declare function maximum(fieldName: string): AggregateFunction; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field to find the maximum value of. A new representing the 'maximum' aggregation. | + +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +### minimum(fieldName) {:#minimum_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that finds the minimum value of a field across multiple stage inputs. + +```typescript +// Find the lowest price of all products +minimum("price").as("lowestPrice"); + +``` + +Signature: + +```typescript +export declare function minimum(fieldName: string): AggregateFunction; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field to find the minimum value of. A new representing the 'minimum' aggregation. | + +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +### mod(fieldName, expression) {:#mod_1e91657} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the modulo (remainder) of dividing a field's value by an expression. + +```typescript +// Calculate the remainder of dividing 'field1' by 'field2'. +mod("field1", field("field2")); + +``` + +Signature: + +```typescript +export declare function mod(fieldName: string, expression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The dividend field name. | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The divisor expression. A new representing the modulo operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### mod(fieldName, value) {:#mod_65e2f32} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the modulo (remainder) of dividing a field's value by a constant. + +```typescript +// Calculate the remainder of dividing 'field1' by 5. +mod("field1", 5); + +``` + +Signature: + +```typescript +export declare function mod(fieldName: string, value: unknown): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The dividend field name. | +| value | unknown | The divisor constant. A new representing the modulo operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### multiply(fieldName, second) {:#multiply_b75bb8b} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that multiplies a field's value by an expression. + +```typescript +// Multiply the 'quantity' field by the 'price' field +multiply("quantity", field("price")); + +``` + +Signature: + +```typescript +export declare function multiply(fieldName: string, second: Expression | unknown): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the value to add. | +| second | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown | The second expression or literal to add. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### notEqual(fieldName, expression) {:#notequal_1e91657} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is not equal to an expression. + +```typescript +// Check if the 'status' field is not equal to the value of 'expectedStatus' +notEqual("status", field("expectedStatus")); + +``` + +Signature: + +```typescript +export declare function notEqual(fieldName: string, expression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to compare. | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to compare to. A new Expr representing the inequality comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### notEqual(fieldName, value) {:#notequal_65e2f32} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is not equal to a constant value. + +```typescript +// Check if the 'country' field is not equal to "USA" +notEqual("country", "USA"); + +``` + +Signature: + +```typescript +export declare function notEqual(fieldName: string, value: unknown): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to compare. | +| value | unknown | The constant value to compare to. A new Expr representing the inequality comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### notEqualAny(fieldName, values) {:#notequalany_8060b23} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is not equal to any of the provided values or expressions. + +```typescript +// Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' +notEqualAny("status", [constant("pending"), field("rejectedStatus")]); + +``` + +Signature: + +```typescript +export declare function notEqualAny(fieldName: string, values: Array): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to compare. | +| values | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | The values to check against. A new representing the 'NOT IN' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### notEqualAny(fieldName, arrayExpression) {:#notequalany_48da8d9} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value is not equal to any of the values in the evaluated expression. + +```typescript +// Check if the 'status' field is not equal to any value in the field 'rejectedStatuses' +notEqualAny("status", field("rejectedStatuses")); + +``` + +Signature: + +```typescript +export declare function notEqualAny(fieldName: string, arrayExpression: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to compare. | +| arrayExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The values to check against. A new representing the 'NOT IN' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### regexContains(fieldName, pattern) {:#regexcontains_67f7432} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string field contains a specified regular expression as a substring. + +```typescript +// Check if the 'description' field contains "example" (case-insensitive) +regexContains("description", "(?i)example"); + +``` + +Signature: + +```typescript +export declare function regexContains(fieldName: string, pattern: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the string. | +| pattern | string | The regular expression to use for the search. A new representing the 'contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### regexContains(fieldName, pattern) {:#regexcontains_cb1318d} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string field contains a specified regular expression as a substring. + +```typescript +// Check if the 'description' field contains "example" (case-insensitive) +regexContains("description", field("pattern")); + +``` + +Signature: + +```typescript +export declare function regexContains(fieldName: string, pattern: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the string. | +| pattern | [Expression](./firestore_pipelines.expression.md#expression_class) | The regular expression to use for the search. A new representing the 'contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### regexMatch(fieldName, pattern) {:#regexmatch_67f7432} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string field matches a specified regular expression. + +```typescript +// Check if the 'email' field matches a valid email pattern +regexMatch("email", "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"); + +``` + +Signature: + +```typescript +export declare function regexMatch(fieldName: string, pattern: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the string. | +| pattern | string | The regular expression to use for the match. A new representing the regular expression match. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### regexMatch(fieldName, pattern) {:#regexmatch_cb1318d} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string field matches a specified regular expression. + +```typescript +// Check if the 'email' field matches a valid email pattern +regexMatch("email", field("pattern")); + +``` + +Signature: + +```typescript +export declare function regexMatch(fieldName: string, pattern: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the string. | +| pattern | [Expression](./firestore_pipelines.expression.md#expression_class) | The regular expression to use for the match. A new representing the regular expression match. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### round(fieldName) {:#round_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that rounds a numeric value to the nearest whole number. + +```typescript +// Round the value of the 'price' field. +round("price"); + +``` + +Signature: + +```typescript +export declare function round(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field to round. A new Expr representing the rounded value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### round(fieldName, decimalPlaces) {:#round_07d0cf0} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that rounds a numeric value to the specified number of decimal places. + +```typescript +// Round the value of the 'price' field to two decimal places. +round("price", 2); + +``` + +Signature: + +```typescript +export declare function round(fieldName: string, decimalPlaces: number | Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field to round. | +| decimalPlaces | number \| [Expression](./firestore_pipelines.expression.md#expression_class) | A constant or expression specifying the rounding precision in decimal places. A new Expr representing the rounded value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### split(fieldName, delimiter) {:#split_2cfdd37} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that splits the value of a field on the provided delimiter. + +Signature: + +```typescript +export declare function split(fieldName: string, delimiter: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | Split the value in this field. | +| delimiter | string | Split on this delimiter. A new representing the split function. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### Example + + +```typescript +// Split the 'scoresCsv' field on delimiter ',' +split('scoresCsv', ',') + +``` + +### split(fieldName, delimiter) {:#split_f4fe06a} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that splits the value of a field on the provided delimiter. + +Signature: + +```typescript +export declare function split(fieldName: string, delimiter: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | Split the value in this field. | +| delimiter | [Expression](./firestore_pipelines.expression.md#expression_class) | Split on this delimiter returned by evaluating this expression. A new representing the split function. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### Example + + +```typescript +// Split the 'scores' field on delimiter ',' or ':' depending on the stored format +split('scores', conditional(field('format').equal('csv'), constant(','), constant(':')) + +``` + +### sqrt(fieldName) {:#sqrt_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that computes the square root of a numeric value. + +```typescript +// Compute the square root of the 'value' field. +sqrt("value"); + +``` + +Signature: + +```typescript +export declare function sqrt(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field to compute the square root of. A new representing the square root of the numeric value. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### startsWith(fieldName, prefix) {:#startswith_89325cc} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value starts with a given prefix. + +```typescript +// Check if the 'name' field starts with "Mr." +startsWith("name", "Mr."); + +``` + +Signature: + +```typescript +export declare function startsWith(fieldName: string, prefix: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to check. | +| prefix | string | The prefix to check for. A new representing the 'starts with' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### startsWith(fieldName, prefix) {:#startswith_266c338} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field's value starts with a given prefix. + +```typescript +// Check if the 'fullName' field starts with the value of the 'firstName' field +startsWith("fullName", field("firstName")); + +``` + +Signature: + +```typescript +export declare function startsWith(fieldName: string, prefix: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to check. | +| prefix | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the prefix. A new representing the 'starts with' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### stringConcat(fieldName, secondString, otherStrings) {:#stringconcat_d80077e} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that concatenates string functions, fields or constants together. + +```typescript +// Combine the 'firstName', " ", and 'lastName' fields into a single string +stringConcat("firstName", " ", field("lastName")); + +``` + +Signature: + +```typescript +export declare function stringConcat(fieldName: string, secondString: Expression | string, ...otherStrings: Array): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name containing the initial string value. | +| secondString | [Expression](./firestore_pipelines.expression.md#expression_class) \| string | An expression or string literal to concatenate. | +| otherStrings | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| string> | Optional additional expressions or literals (typically strings) to concatenate. A new representing the concatenated string. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### stringContains(fieldName, substring) {:#stringcontains_5b94cfe} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string field contains a specified substring. + +```typescript +// Check if the 'description' field contains "example". +stringContains("description", "example"); + +``` + +Signature: + +```typescript +export declare function stringContains(fieldName: string, substring: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the string. | +| substring | string | The substring to search for. A new representing the 'contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### stringContains(fieldName, substring) {:#stringcontains_ac3ba47} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string field contains a substring specified by an expression. + +```typescript +// Check if the 'description' field contains the value of the 'keyword' field. +stringContains("description", field("keyword")); + +``` + +Signature: + +```typescript +export declare function stringContains(fieldName: string, substring: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the string. | +| substring | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the substring to search for. A new representing the 'contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### subtract(fieldName, expression) {:#subtract_1e91657} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that subtracts an expression from a field's value. + +```typescript +// Subtract the 'discount' field from the 'price' field +subtract("price", field("discount")); + +``` + +Signature: + +```typescript +export declare function subtract(fieldName: string, expression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to subtract from. | +| expression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to subtract. A new representing the subtraction operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### subtract(fieldName, value) {:#subtract_65e2f32} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that subtracts a constant value from a field's value. + +```typescript +// Subtract 20 from the value of the 'total' field +subtract("total", 20); + +``` + +Signature: + +```typescript +export declare function subtract(fieldName: string, value: unknown): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The field name to subtract from. | +| value | unknown | The constant value to subtract. A new representing the subtraction operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### sum(fieldName) {:#sum_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an aggregation that calculates the sum of a field's values across multiple stage inputs. + +```typescript +// Calculate the total revenue from a set of orders +sum("orderAmount").as("totalRevenue"); + +``` + +Signature: + +```typescript +export declare function sum(fieldName: string): AggregateFunction; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing numeric values to sum up. A new representing the 'sum' aggregation. | + +Returns: + +[AggregateFunction](./firestore_pipelines.aggregatefunction.md#aggregatefunction_class) + +### timestampAdd(fieldName, unit, amount) {:#timestampadd_341fe7d} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that adds a specified amount of time to a timestamp represented by a field. + +```typescript +// Add 1 day to the 'timestamp' field. +timestampAdd("timestamp", "day", 1); + +``` + +Signature: + +```typescript +export declare function timestampAdd(fieldName: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field representing the timestamp. | +| unit | 'microsecond' \| 'millisecond' \| 'second' \| 'minute' \| 'hour' \| 'day' | The unit of time to add (e.g., "day", "hour"). | +| amount | number | The amount of time to add. A new representing the resulting timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### timestampSubtract(fieldName, unit, amount) {:#timestampsubtract_341fe7d} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that subtracts a specified amount of time from a timestamp represented by a field. + +```typescript +// Subtract 1 day from the 'timestamp' field. +timestampSubtract("timestamp", "day", 1); + +``` + +Signature: + +```typescript +export declare function timestampSubtract(fieldName: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field representing the timestamp. | +| unit | 'microsecond' \| 'millisecond' \| 'second' \| 'minute' \| 'hour' \| 'day' | The unit of time to subtract (e.g., "day", "hour"). | +| amount | number | The amount of time to subtract. A new representing the resulting timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### timestampToUnixMicros(fieldName) {:#timestamptounixmicros_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that converts a timestamp field to the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + +```typescript +// Convert the 'timestamp' field to microseconds since epoch. +timestampToUnixMicros("timestamp"); + +``` + +Signature: + +```typescript +export declare function timestampToUnixMicros(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field representing the timestamp. A new representing the number of microseconds since epoch. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### timestampToUnixMillis(fieldName) {:#timestamptounixmillis_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that converts a timestamp field to the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + +```typescript +// Convert the 'timestamp' field to milliseconds since epoch. +timestampToUnixMillis("timestamp"); + +``` + +Signature: + +```typescript +export declare function timestampToUnixMillis(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field representing the timestamp. A new representing the number of milliseconds since epoch. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### timestampToUnixSeconds(fieldName) {:#timestamptounixseconds_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that converts a timestamp field to the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC). + +```typescript +// Convert the 'timestamp' field to seconds since epoch. +timestampToUnixSeconds("timestamp"); + +``` + +Signature: + +```typescript +export declare function timestampToUnixSeconds(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field representing the timestamp. A new representing the number of seconds since epoch. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### timestampTruncate(fieldName, granularity, timezone) {:#timestamptruncate_b6c7512} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that truncates a timestamp to a specified granularity. + +Signature: + +```typescript +export declare function timestampTruncate(fieldName: string, granularity: TimeGranularity, timezone?: string | Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | Truncate the timestamp value contained in this field. | +| granularity | [TimeGranularity](./firestore_pipelines.md#timegranularity) | The granularity to truncate to. | +| timezone | string \| [Expression](./firestore_pipelines.expression.md#expression_class) | The timezone to use for truncation. Valid values are from the TZ database (e.g., "America/Los\_Angeles") or in the format "Etc/GMT-1". A new {Expression} representing the truncated timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### Example + + +```typescript +// Truncate the 'createdAt' timestamp to the beginning of the day. +field('createdAt').timestampTruncate('day') + +``` + +### timestampTruncate(fieldName, granularity, timezone) {:#timestamptruncate_ed83d46} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that truncates a timestamp to a specified granularity. + +Signature: + +```typescript +export declare function timestampTruncate(fieldName: string, granularity: Expression, timezone?: string | Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | Truncate the timestamp value contained in this field. | +| granularity | [Expression](./firestore_pipelines.expression.md#expression_class) | The granularity to truncate to. | +| timezone | string \| [Expression](./firestore_pipelines.expression.md#expression_class) | The timezone to use for truncation. Valid values are from the TZ database (e.g., "America/Los\_Angeles") or in the format "Etc/GMT-1". A new {Expression} representing the truncated timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### Example + + +```typescript +// Truncate the 'createdAt' timestamp to the granularity specified in the field 'granularity'. +field('createdAt').timestampTruncate(field('granularity')) + +``` + +### toLower(fieldName) {:#tolower_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that converts a string field to lowercase. + +```typescript +// Convert the 'name' field to lowercase +toLower("name"); + +``` + +Signature: + +```typescript +export declare function toLower(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the string. A new representing the lowercase string. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### toUpper(fieldName) {:#toupper_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that converts a string field to uppercase. + +```typescript +// Convert the 'title' field to uppercase +toUpper("title"); + +``` + +Signature: + +```typescript +export declare function toUpper(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the string. A new representing the uppercase string. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### trim(fieldName, valueToTrim) {:#trim_c9f90ee} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that removes leading and trailing whitespace from a string or byte array. + +```typescript +// Trim whitespace from the 'userInput' field +trim("userInput"); + +// Trim quotes from the 'userInput' field +trim("userInput", '"'); + +``` + +Signature: + +```typescript +export declare function trim(fieldName: string, valueToTrim?: string | Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field containing the string or byte array. | +| valueToTrim | string \| [Expression](./firestore_pipelines.expression.md#expression_class) | Optional This parameter is treated as a set of characters or bytes that will be trimmed from the input. If not specified, then whitespace will be trimmed. A new representing the trimmed string. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### type(fieldName) {:#type_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the data type of the data in the specified field. + +Signature: + +```typescript +export declare function type(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### Example + + +```typescript +// Get the data type of the value in field 'title' +type('title') + +``` + A new {Expression} representing the data type. + +### unixMicrosToTimestamp(fieldName) {:#unixmicrostotimestamp_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that interprets a field's value as the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. + +```typescript +// Interpret the 'microseconds' field as microseconds since epoch. +unixMicrosToTimestamp("microseconds"); + +``` + +Signature: + +```typescript +export declare function unixMicrosToTimestamp(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field representing the number of microseconds since epoch. A new representing the timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### unixMillisToTimestamp(fieldName) {:#unixmillistotimestamp_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that interprets a field's value as the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. + +```typescript +// Interpret the 'milliseconds' field as milliseconds since epoch. +unixMillisToTimestamp("milliseconds"); + +``` + +Signature: + +```typescript +export declare function unixMillisToTimestamp(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field representing the number of milliseconds since epoch. A new representing the timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### unixSecondsToTimestamp(fieldName) {:#unixsecondstotimestamp_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that interprets a field's value as the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC) and returns a timestamp. + +```typescript +// Interpret the 'seconds' field as seconds since epoch. +unixSecondsToTimestamp("seconds"); + +``` + +Signature: + +```typescript +export declare function unixSecondsToTimestamp(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field representing the number of seconds since epoch. A new representing the timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### vectorLength(fieldName) {:#vectorlength_e5b0480} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the length of a Firestore Vector represented by a field. + +```typescript +// Get the vector length (dimension) of the field 'embedding'. +vectorLength("embedding"); + +``` + +Signature: + +```typescript +export declare function vectorLength(fieldName: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | The name of the field representing the Firestore Vector. A new representing the length of the array. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(first, ...) + +### add(first, second) {:#add_846ca1b} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that adds two expressions together. + +```typescript +// Add the value of the 'quantity' field and the 'reserve' field. +add(field("quantity"), field("reserve")); + +``` + +Signature: + +```typescript +export declare function add(first: Expression, second: Expression | unknown): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| first | [Expression](./firestore_pipelines.expression.md#expression_class) | The first expression to add. | +| second | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown | The second expression or literal to add. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### and(first, second, more) {:#and_e0c48bd} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that performs a logical 'AND' operation on multiple filter conditions. + +```typescript +// Check if the 'age' field is greater than 18 AND the 'city' field is "London" AND +// the 'status' field is "active" +const condition = and(greaterThan("age", 18), equal("city", "London"), equal("status", "active")); + +``` + +Signature: + +```typescript +export declare function and(first: BooleanExpression, second: BooleanExpression, ...more: BooleanExpression[]): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| first | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) | The first filter condition. | +| second | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) | The second filter condition. | +| more | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class)\[\] | Additional filter conditions to 'AND' together. A new representing the logical 'AND' operation. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### concat(first, second, others) {:#concat_83be015} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that concatenates strings, arrays, or blobs. Types cannot be mixed. + +```typescript +// Concatenate the 'firstName' and 'lastName' fields with a space in between. +concat(field("firstName"), " ", field("lastName")) + +``` + +Signature: + +```typescript +export declare function concat(first: Expression, second: Expression | unknown, ...others: Array): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| first | [Expression](./firestore_pipelines.expression.md#expression_class) | The first expressions to concatenate. | +| second | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown | The second literal or expression to concatenate. | +| others | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | Additional literals or expressions to concatenate. A new Expression representing the concatenation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### logicalMaximum(first, second, others) {:#logicalmaximum_83be015} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the largest value between multiple input expressions or literal values. Based on Firestore's value type ordering. + +```typescript +// Returns the largest value between the 'field1' field, the 'field2' field, +// and 1000 +logicalMaximum(field("field1"), field("field2"), 1000); + +``` + +Signature: + +```typescript +export declare function logicalMaximum(first: Expression, second: Expression | unknown, ...others: Array): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| first | [Expression](./firestore_pipelines.expression.md#expression_class) | The first operand expression. | +| second | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown | The second expression or literal. | +| others | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | Optional additional expressions or literals. A new representing the logical maximum operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### logicalMinimum(first, second, others) {:#logicalminimum_83be015} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the smallest value between multiple input expressions and literal values. Based on Firestore's value type ordering. + +```typescript +// Returns the smallest value between the 'field1' field, the 'field2' field, +// and 1000. +logicalMinimum(field("field1"), field("field2"), 1000); + +``` + +Signature: + +```typescript +export declare function logicalMinimum(first: Expression, second: Expression | unknown, ...others: Array): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| first | [Expression](./firestore_pipelines.expression.md#expression_class) | The first operand expression. | +| second | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown | The second expression or literal. | +| others | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown> | Optional additional expressions or literals. A new representing the logical minimum operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### multiply(first, second) {:#multiply_846ca1b} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that multiplies two expressions together. + +```typescript +// Multiply the 'quantity' field by the 'price' field +multiply(field("quantity"), field("price")); + +``` + +Signature: + +```typescript +export declare function multiply(first: Expression, second: Expression | unknown): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| first | [Expression](./firestore_pipelines.expression.md#expression_class) | The first expression to multiply. | +| second | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown | The second expression or literal to multiply. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### or(first, second, more) {:#or_e0c48bd} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that performs a logical 'OR' operation on multiple filter conditions. + +```typescript +// Check if the 'age' field is greater than 18 OR the 'city' field is "London" OR +// the 'status' field is "active" +const condition = or(greaterThan("age", 18), equal("city", "London"), equal("status", "active")); + +``` + +Signature: + +```typescript +export declare function or(first: BooleanExpression, second: BooleanExpression, ...more: BooleanExpression[]): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| first | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) | The first filter condition. | +| second | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) | The second filter condition. | +| more | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class)\[\] | Additional filter conditions to 'OR' together. A new representing the logical 'OR' operation. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### xor(first, second, additionalConditions) {:#xor_8197113} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that performs a logical 'XOR' (exclusive OR) operation on multiple BooleanExpressions. + +```typescript +// Check if only one of the conditions is true: 'age' greater than 18, 'city' is "London", +// or 'status' is "active". +const condition = xor( + greaterThan("age", 18), + equal("city", "London"), + equal("status", "active")); + +``` + +Signature: + +```typescript +export declare function xor(first: BooleanExpression, second: BooleanExpression, ...additionalConditions: BooleanExpression[]): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| first | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) | The first condition. | +| second | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) | The second condition. | +| additionalConditions | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class)\[\] | Additional conditions to 'XOR' together. A new representing the logical 'XOR' operation. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## function(firstArray, ...) + +### arrayConcat(firstArray, secondArray, otherArrays) {:#arrayconcat_c00d5d7} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that concatenates an array expression with other arrays. + +```typescript +// Combine the 'items' array with two new item arrays +arrayConcat(field("items"), [field("newItems"), field("otherItems")]); + +``` + +Signature: + +```typescript +export declare function arrayConcat(firstArray: Expression, secondArray: Expression | unknown[], ...otherArrays: Array): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firstArray | [Expression](./firestore_pipelines.expression.md#expression_class) | The first array expression to concatenate to. | +| secondArray | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown\[\] | The second array expression or array literal to concatenate to. | +| otherArrays | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown\[\]> | Optional additional array expressions or array literals to concatenate. A new representing the concatenated array. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(firstArrayField, ...) + +### arrayConcat(firstArrayField, secondArray, otherArrays) {:#arrayconcat_f92063d} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that concatenates a field's array value with other arrays. + +```typescript +// Combine the 'items' array with two new item arrays +arrayConcat("items", [field("newItems"), field("otherItems")]); + +``` + +Signature: + +```typescript +export declare function arrayConcat(firstArrayField: string, secondArray: Expression | unknown[], ...otherArrays: Array): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firstArrayField | string | The first array to concatenate to. | +| secondArray | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown\[\] | The second array expression or array literal to concatenate to. | +| otherArrays | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| unknown\[\]> | Optional additional array expressions or array literals to concatenate. A new representing the concatenated array. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(firstMap, ...) + +### mapMerge(firstMap, secondMap, otherMaps) {:#mapmerge_cfe77ce} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that merges multiple map values. + +``` +// Merges the map in the settings field with, a map literal, and a map in +// that is conditionally returned by another expression +mapMerge(field('settings'), { enabled: true }, conditional(field('isAdmin'), { admin: true}, {}) + +``` + +Signature: + +```typescript +export declare function mapMerge(firstMap: Record | Expression, secondMap: Record | Expression, ...otherMaps: Array | Expression>): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firstMap | Record<string, unknown> \| [Expression](./firestore_pipelines.expression.md#expression_class) | An expression or literal map value that will be merged. | +| secondMap | Record<string, unknown> \| [Expression](./firestore_pipelines.expression.md#expression_class) | A required second map to merge. Represented as a literal or an expression that returns a map. | +| otherMaps | Array<Record<string, unknown> \| [Expression](./firestore_pipelines.expression.md#expression_class)> | Optional additional maps to merge. Each map is represented as a literal or an expression that returns a map. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(firstString, ...) + +### stringConcat(firstString, secondString, otherStrings) {:#stringconcat_8a8d1b6} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that concatenates string expressions together. + +```typescript +// Combine the 'firstName', " ", and 'lastName' fields into a single string +stringConcat(field("firstName"), " ", field("lastName")); + +``` + +Signature: + +```typescript +export declare function stringConcat(firstString: Expression, secondString: Expression | string, ...otherStrings: Array): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firstString | [Expression](./firestore_pipelines.expression.md#expression_class) | The initial string expression to concatenate to. | +| secondString | [Expression](./firestore_pipelines.expression.md#expression_class) \| string | An expression or string literal to concatenate. | +| otherStrings | Array<[Expression](./firestore_pipelines.expression.md#expression_class) \| string> | Optional additional expressions or literals (typically strings) to concatenate. A new representing the concatenated string. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(ifExpr, ...) + +### ifAbsent(ifExpr, elseExpr) {:#ifabsent_0e6d161} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the `elseExpr` argument if `ifExpr` is absent, else return the result of the `ifExpr` argument evaluation. + +```typescript +// Returns the value of the optional field 'optional_field', or returns 'default_value' +// if the field is absent. +ifAbsent(field("optional_field"), constant("default_value")) + +``` + +Signature: + +```typescript +export declare function ifAbsent(ifExpr: Expression, elseExpr: Expression): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| ifExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to check for absence. | +| elseExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression that will be evaluated and returned if \[ifExpr\] is absent. A new Expression representing the ifAbsent operation. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +### ifAbsent(ifExpr, elseValue) {:#ifabsent_c34e5ed} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the `elseValue` argument if `ifExpr` is absent, else return the result of the `ifExpr` argument evaluation. + +```typescript +// Returns the value of the optional field 'optional_field', or returns 'default_value' +// if the field is absent. +ifAbsent(field("optional_field"), "default_value") + +``` + +Signature: + +```typescript +export declare function ifAbsent(ifExpr: Expression, elseValue: unknown): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| ifExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to check for absence. | +| elseValue | unknown | The value that will be returned if ifExpr evaluates to an absent value. A new \[Expression\] representing the ifAbsent operation. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +## function(ifFieldName, ...) + +### ifAbsent(ifFieldName, elseExpr) {:#ifabsent_e6dabea} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the `elseExpr` argument if `ifFieldName` is absent, else return the value of the field. + +```typescript +// Returns the value of the optional field 'optional_field', or returns the value of +// 'default_field' if 'optional_field' is absent. +ifAbsent("optional_field", field("default_field")) + +``` + +Signature: + +```typescript +export declare function ifAbsent(ifFieldName: string, elseExpr: Expression): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| ifFieldName | string | The field to check for absence. | +| elseExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression that will be evaluated and returned if ifFieldName is absent. A new Expression representing the ifAbsent operation. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +### ifAbsent(ifFieldName, elseValue) {:#ifabsent_d8f2823} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the `elseValue` argument if `ifFieldName` is absent, else return the value of the field. + +```typescript +// Returns the value of the optional field 'optional_field', or returns 'default_value' +// if the field is absent. +ifAbsent("optional_field", "default_value") + +``` + +Signature: + +```typescript +export declare function ifAbsent(ifFieldName: string | Expression, elseValue: Expression | unknown): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| ifFieldName | string \| [Expression](./firestore_pipelines.expression.md#expression_class) | The field to check for absence. | +| elseValue | [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown | The value that will be returned if \[ifFieldName\] is absent. A new Expression representing the ifAbsent operation. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +## function(input, ...) + +### substring(input, position, length) {:#substring_e6e0aa3} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns a substring of a string or byte array. + +Signature: + +```typescript +export declare function substring(input: Expression, position: number, length?: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| input | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression returning a string or byte array to compute the substring from. | +| position | number | Index of the first character of the substring. | +| length | number | Length of the substring. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### substring(input, position, length) {:#substring_ab56dc6} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns a substring of a string or byte array. + +Signature: + +```typescript +export declare function substring(input: Expression, position: Expression, length?: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| input | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression returning a string or byte array to compute the substring from. | +| position | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression that returns the index of the first character of the substring. | +| length | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression that returns the length of the substring. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(left, ...) + +### divide(left, right) {:#divide_b3c3382} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that divides two expressions. + +```typescript +// Divide the 'total' field by the 'count' field +divide(field("total"), field("count")); + +``` + +Signature: + +```typescript +export declare function divide(left: Expression, right: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| left | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to be divided. | +| right | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to divide by. A new representing the division operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### equal(left, right) {:#equal_b3c3382} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if two expressions are equal. + +```typescript +// Check if the 'age' field is equal to an expression +equal(field("age"), field("minAge").add(10)); + +``` + +Signature: + +```typescript +export declare function equal(left: Expression, right: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| left | [Expression](./firestore_pipelines.expression.md#expression_class) | The first expression to compare. | +| right | [Expression](./firestore_pipelines.expression.md#expression_class) | The second expression to compare. A new Expr representing the equality comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### greaterThan(left, right) {:#greaterthan_b3c3382} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if the first expression is greater than the second expression. + +```typescript +// Check if the 'age' field is greater than 18 +greaterThan(field("age"), Constant(9).add(9)); + +``` + +Signature: + +```typescript +export declare function greaterThan(left: Expression, right: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| left | [Expression](./firestore_pipelines.expression.md#expression_class) | The first expression to compare. | +| right | [Expression](./firestore_pipelines.expression.md#expression_class) | The second expression to compare. A new Expr representing the greater than comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### greaterThanOrEqual(left, right) {:#greaterthanorequal_b3c3382} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if the first expression is greater than or equal to the second expression. + +```typescript +// Check if the 'quantity' field is greater than or equal to the field "threshold" +greaterThanOrEqual(field("quantity"), field("threshold")); + +``` + +Signature: + +```typescript +export declare function greaterThanOrEqual(left: Expression, right: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| left | [Expression](./firestore_pipelines.expression.md#expression_class) | The first expression to compare. | +| right | [Expression](./firestore_pipelines.expression.md#expression_class) | The second expression to compare. A new Expr representing the greater than or equal to comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### lessThan(left, right) {:#lessthan_b3c3382} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if the first expression is less than the second expression. + +```typescript +// Check if the 'age' field is less than 30 +lessThan(field("age"), field("limit")); + +``` + +Signature: + +```typescript +export declare function lessThan(left: Expression, right: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| left | [Expression](./firestore_pipelines.expression.md#expression_class) | The first expression to compare. | +| right | [Expression](./firestore_pipelines.expression.md#expression_class) | The second expression to compare. A new Expr representing the less than comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### lessThanOrEqual(left, right) {:#lessthanorequal_b3c3382} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if the first expression is less than or equal to the second expression. + +```typescript +// Check if the 'quantity' field is less than or equal to 20 +lessThan(field("quantity"), field("limit")); + +``` + +Signature: + +```typescript +export declare function lessThanOrEqual(left: Expression, right: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| left | [Expression](./firestore_pipelines.expression.md#expression_class) | The first expression to compare. | +| right | [Expression](./firestore_pipelines.expression.md#expression_class) | The second expression to compare. A new Expr representing the less than or equal to comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### mod(left, right) {:#mod_b3c3382} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the modulo (remainder) of dividing two expressions. + +```typescript +// Calculate the remainder of dividing 'field1' by 'field2'. +mod(field("field1"), field("field2")); + +``` + +Signature: + +```typescript +export declare function mod(left: Expression, right: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| left | [Expression](./firestore_pipelines.expression.md#expression_class) | The dividend expression. | +| right | [Expression](./firestore_pipelines.expression.md#expression_class) | The divisor expression. A new representing the modulo operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### notEqual(left, right) {:#notequal_b3c3382} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if two expressions are not equal. + +```typescript +// Check if the 'status' field is not equal to field 'finalState' +notEqual(field("status"), field("finalState")); + +``` + +Signature: + +```typescript +export declare function notEqual(left: Expression, right: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| left | [Expression](./firestore_pipelines.expression.md#expression_class) | The first expression to compare. | +| right | [Expression](./firestore_pipelines.expression.md#expression_class) | The second expression to compare. A new Expr representing the inequality comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### pipelineResultEqual(left, right) {:#pipelineresultequal_707a755} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Test equality of two PipelineResults. + +Signature: + +```typescript +export declare function pipelineResultEqual(left: PipelineResult, right: PipelineResult): boolean; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| left | [PipelineResult](./firestore_pipelines.pipelineresult.md#pipelineresult_class) | | +| right | [PipelineResult](./firestore_pipelines.pipelineresult.md#pipelineresult_class) | | + +Returns: + +boolean + +### subtract(left, right) {:#subtract_b3c3382} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that subtracts two expressions. + +```typescript +// Subtract the 'discount' field from the 'price' field +subtract(field("price"), field("discount")); + +``` + +Signature: + +```typescript +export declare function subtract(left: Expression, right: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| left | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to subtract from. | +| right | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to subtract. A new representing the subtraction operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(mapExpr, ...) + +### mapRemove(mapExpr, key) {:#mapremove_23c7d51} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that removes a key from the map produced by evaluating an expression. + +``` +// Removes the key 'baz' from the input map. +mapRemove(map({foo: 'bar', baz: true}), 'baz'); + +``` + +Signature: + +```typescript +export declare function mapRemove(mapExpr: Expression, key: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| mapExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression return a map value. | +| key | string | The name of the key to remove from the input map. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### mapRemove(mapExpr, keyExpr) {:#mapremove_9fbcaa3} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that removes a key from the map produced by evaluating an expression. + +``` +// Removes the key 'baz' from the input map. +mapRemove(map({foo: 'bar', baz: true}), constant('baz')); + +``` + +Signature: + +```typescript +export declare function mapRemove(mapExpr: Expression, keyExpr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| mapExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression return a map value. | +| keyExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression that produces the name of the key to remove from the input map. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(mapExpression, ...) + +### mapGet(mapExpression, subField) {:#mapget_688c050} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Accesses a value from a map (object) expression using the provided key. + +```typescript +// Get the 'city' value from the 'address' map field +mapGet(field("address"), "city"); + +``` + +Signature: + +```typescript +export declare function mapGet(mapExpression: Expression, subField: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| mapExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the map. | +| subField | string | The key to access in the map. A new representing the value associated with the given key in the map. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(mapField, ...) + +### mapMerge(mapField, secondMap, otherMaps) {:#mapmerge_70a564b} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that merges multiple map values. + +``` +// Merges the map in the settings field with, a map literal, and a map in +// that is conditionally returned by another expression +mapMerge('settings', { enabled: true }, conditional(field('isAdmin'), { admin: true}, {}) + +``` + +Signature: + +```typescript +export declare function mapMerge(mapField: string, secondMap: Record | Expression, ...otherMaps: Array | Expression>): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| mapField | string | Name of a field containing a map value that will be merged. | +| secondMap | Record<string, unknown> \| [Expression](./firestore_pipelines.expression.md#expression_class) | A required second map to merge. Represented as a literal or an expression that returns a map. | +| otherMaps | Array<Record<string, unknown> \| [Expression](./firestore_pipelines.expression.md#expression_class)> | Optional additional maps to merge. Each map is represented as a literal or an expression that returns a map. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### mapRemove(mapField, key) {:#mapremove_bd5726e} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that removes a key from the map at the specified field name. + +``` +// Removes the key 'city' field from the map in the address field of the input document. +mapRemove('address', 'city'); + +``` + +Signature: + +```typescript +export declare function mapRemove(mapField: string, key: string): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| mapField | string | The name of a field containing a map value. | +| key | string | The name of the key to remove from the input map. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### mapRemove(mapField, keyExpr) {:#mapremove_8406d13} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that removes a key from the map at the specified field name. + +``` +// Removes the key 'city' field from the map in the address field of the input document. +mapRemove('address', constant('city')); + +``` + +Signature: + +```typescript +export declare function mapRemove(mapField: string, keyExpr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| mapField | string | The name of a field containing a map value. | +| keyExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression that produces the name of the key to remove from the input map. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(name, ...) + +### field(name) {:#field_1eaaff4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates a instance representing the field at the given path. + +The path can be a simple field name (e.g., "name") or a dot-separated path to a nested field (e.g., "address.city"). + +```typescript +// Create a Field instance for the 'title' field +const titleField = field("title"); + +// Create a Field instance for a nested field 'author.firstName' +const authorFirstNameField = field("author.firstName"); + +``` + +Signature: + +```typescript +export declare function field(name: string): Field; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | The path to the field. A new instance representing the specified field. | + +Returns: + +[Field](./firestore_pipelines.field.md#field_class) + +## function(options, ...) + +### execute(options) {:#execute_9e87e31} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Executes a pipeline and returns a Promise to represent the asynchronous operation. + +The returned Promise can be used to track the progress of the pipeline execution and retrieve the results (or handle any errors) asynchronously. + +The pipeline results are returned as a that contains a list of objects. Each typically represents a single key/value map that has passed through all the stages of the pipeline, however this might differ depending on the stages involved in the pipeline. For example: + +

  • If there are no stages or only transformation stages, each represents a single document.
  • If there is an aggregation, only a single is returned, representing the aggregated results over the entire dataset .
  • If there is an aggregation stage with grouping, each represents a distinct group and its associated aggregated values.
+ +

Example: + +```typescript +const snapshot: PipelineSnapshot = await execute(firestore.pipeline().collection("books") + .where(gt(field("rating"), 4.5)) + .select("title", "author", "rating")); + +const results: PipelineResults = snapshot.results; + +``` + +Signature: + +```typescript +export declare function execute(options: PipelineExecuteOptions): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [PipelineExecuteOptions](./firestore_pipelines.pipelineexecuteoptions.md#pipelineexecuteoptions_interface) | Specifies the pipeline to execute and other options for execute. A Promise representing the asynchronous pipeline execution. | + +Returns: + +Promise<[PipelineSnapshot](./firestore_pipelines.pipelinesnapshot.md#pipelinesnapshot_class)> + +## function(path, ...) + +### field(path) {:#field_34ee07d} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates a instance representing the field at the given path. + +Signature: + +```typescript +export declare function field(path: FieldPath): Field; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| path | [FieldPath](./firestore_.fieldpath.md#fieldpath_class) | A FieldPath specifying the field. A new instance representing the specified field. | + +Returns: + +[Field](./firestore_pipelines.field.md#field_class) + +## function(pipeline, ...) + +### execute(pipeline) {:#execute_01df620} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Executes a pipeline and returns a Promise to represent the asynchronous operation. + +The returned Promise can be used to track the progress of the pipeline execution and retrieve the results (or handle any errors) asynchronously. + +The pipeline results are returned as a that contains a list of objects. Each typically represents a single key/value map that has passed through all the stages of the pipeline, however this might differ depending on the stages involved in the pipeline. For example: + +

  • If there are no stages or only transformation stages, each represents a single document.
  • If there is an aggregation, only a single is returned, representing the aggregated results over the entire dataset .
  • If there is an aggregation stage with grouping, each represents a distinct group and its associated aggregated values.
+ +

Example: + +```typescript +const snapshot: PipelineSnapshot = await execute(firestore.pipeline().collection("books") + .where(gt(field("rating"), 4.5)) + .select("title", "author", "rating")); + +const results: PipelineResults = snapshot.results; + +``` + +Signature: + +```typescript +export declare function execute(pipeline: Pipeline): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| pipeline | [Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) | The pipeline to execute. A Promise representing the asynchronous pipeline execution. | + +Returns: + +Promise<[PipelineSnapshot](./firestore_pipelines.pipelinesnapshot.md#pipelinesnapshot_class)> + +## function(stringExpression, ...) + +### charLength(stringExpression) {:#charlength_c25a54a} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the character length of a string expression in UTF-8. + +```typescript +// Get the character length of the 'name' field in UTF-8. +strLength(field("name")); + +``` + +Signature: + +```typescript +export declare function charLength(stringExpression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the string to calculate the length of. A new representing the length of the string. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### endsWith(stringExpression, suffix) {:#endswith_0a0b889} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string expression ends with a given postfix. + +```typescript +// Check if the result of concatenating 'firstName' and 'lastName' fields ends with "Jr." +endsWith(field("fullName"), "Jr."); + +``` + +Signature: + +```typescript +export declare function endsWith(stringExpression: Expression, suffix: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to check. | +| suffix | string | The postfix to check for. A new representing the 'ends with' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### endsWith(stringExpression, suffix) {:#endswith_13aee0d} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string expression ends with a given postfix. + +```typescript +// Check if the result of concatenating 'firstName' and 'lastName' fields ends with "Jr." +endsWith(field("fullName"), constant("Jr.")); + +``` + +Signature: + +```typescript +export declare function endsWith(stringExpression: Expression, suffix: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to check. | +| suffix | [Expression](./firestore_pipelines.expression.md#expression_class) | The postfix to check for. A new representing the 'ends with' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### like(stringExpression, pattern) {:#like_a84c581} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that performs a case-sensitive wildcard string comparison. + +```typescript +// Check if the 'title' field contains the string "guide" +like(field("title"), "%guide%"); + +``` + +Signature: + +```typescript +export declare function like(stringExpression: Expression, pattern: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the string to perform the comparison on. | +| pattern | string | The pattern to search for. You can use "%" as a wildcard character. A new representing the 'like' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### like(stringExpression, pattern) {:#like_b534848} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that performs a case-sensitive wildcard string comparison. + +```typescript +// Check if the 'title' field contains the string "guide" +like(field("title"), field("pattern")); + +``` + +Signature: + +```typescript +export declare function like(stringExpression: Expression, pattern: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the string to perform the comparison on. | +| pattern | [Expression](./firestore_pipelines.expression.md#expression_class) | The pattern to search for. You can use "%" as a wildcard character. A new representing the 'like' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### regexContains(stringExpression, pattern) {:#regexcontains_a84c581} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string expression contains a specified regular expression as a substring. + +```typescript +// Check if the 'description' field contains "example" (case-insensitive) +regexContains(field("description"), "(?i)example"); + +``` + +Signature: + +```typescript +export declare function regexContains(stringExpression: Expression, pattern: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the string to perform the comparison on. | +| pattern | string | The regular expression to use for the search. A new representing the 'contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### regexContains(stringExpression, pattern) {:#regexcontains_b534848} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string expression contains a specified regular expression as a substring. + +```typescript +// Check if the 'description' field contains "example" (case-insensitive) +regexContains(field("description"), field("pattern")); + +``` + +Signature: + +```typescript +export declare function regexContains(stringExpression: Expression, pattern: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the string to perform the comparison on. | +| pattern | [Expression](./firestore_pipelines.expression.md#expression_class) | The regular expression to use for the search. A new representing the 'contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### regexMatch(stringExpression, pattern) {:#regexmatch_a84c581} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string expression matches a specified regular expression. + +```typescript +// Check if the 'email' field matches a valid email pattern +regexMatch(field("email"), "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"); + +``` + +Signature: + +```typescript +export declare function regexMatch(stringExpression: Expression, pattern: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the string to match against. | +| pattern | string | The regular expression to use for the match. A new representing the regular expression match. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### regexMatch(stringExpression, pattern) {:#regexmatch_b534848} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string expression matches a specified regular expression. + +```typescript +// Check if the 'email' field matches a valid email pattern +regexMatch(field("email"), field("pattern")); + +``` + +Signature: + +```typescript +export declare function regexMatch(stringExpression: Expression, pattern: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the string to match against. | +| pattern | [Expression](./firestore_pipelines.expression.md#expression_class) | The regular expression to use for the match. A new representing the regular expression match. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### reverse(stringExpression) {:#reverse_c25a54a} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that reverses a string. + +```typescript +// Reverse the value of the 'myString' field. +reverse(field("myString")); + +``` + +Signature: + +```typescript +export declare function reverse(stringExpression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression evaluating to a string value, which will be reversed. A new representing the reversed string. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### startsWith(stringExpression, prefix) {:#startswith_75c3dbb} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string expression starts with a given prefix. + +```typescript +// Check if the result of concatenating 'firstName' and 'lastName' fields starts with "Mr." +startsWith(field("fullName"), "Mr."); + +``` + +Signature: + +```typescript +export declare function startsWith(stringExpression: Expression, prefix: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to check. | +| prefix | string | The prefix to check for. A new representing the 'starts with' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### startsWith(stringExpression, prefix) {:#startswith_52f218a} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string expression starts with a given prefix. + +```typescript +// Check if the result of concatenating 'firstName' and 'lastName' fields starts with "Mr." +startsWith(field("fullName"), field("prefix")); + +``` + +Signature: + +```typescript +export declare function startsWith(stringExpression: Expression, prefix: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to check. | +| prefix | [Expression](./firestore_pipelines.expression.md#expression_class) | The prefix to check for. A new representing the 'starts with' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### stringContains(stringExpression, substring) {:#stringcontains_3e9ff32} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string expression contains a specified substring. + +```typescript +// Check if the 'description' field contains "example". +stringContains(field("description"), "example"); + +``` + +Signature: + +```typescript +export declare function stringContains(stringExpression: Expression, substring: string): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the string to perform the comparison on. | +| substring | string | The substring to search for. A new representing the 'contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### stringContains(stringExpression, substring) {:#stringcontains_cc6ee02} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a string expression contains a substring specified by another expression. + +```typescript +// Check if the 'description' field contains the value of the 'keyword' field. +stringContains(field("description"), field("keyword")); + +``` + +Signature: + +```typescript +export declare function stringContains(stringExpression: Expression, substring: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the string to perform the comparison on. | +| substring | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the substring to search for. A new representing the 'contains' comparison. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### stringReverse(stringExpression) {:#stringreverse_c25a54a} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that reverses a string. + +```typescript +// Reverse the value of the 'myString' field. +strReverse(field("myString")); + +``` + +Signature: + +```typescript +export declare function stringReverse(stringExpression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression evaluating to a string value, which will be reversed. A new representing the reversed string. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### toLower(stringExpression) {:#tolower_c25a54a} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that converts a string expression to lowercase. + +```typescript +// Convert the 'name' field to lowercase +toLower(field("name")); + +``` + +Signature: + +```typescript +export declare function toLower(stringExpression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the string to convert to lowercase. A new representing the lowercase string. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### toUpper(stringExpression) {:#toupper_c25a54a} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that converts a string expression to uppercase. + +```typescript +// Convert the 'title' field to uppercase +toUppercase(field("title")); + +``` + +Signature: + +```typescript +export declare function toUpper(stringExpression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the string to convert to uppercase. A new representing the uppercase string. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### trim(stringExpression, valueToTrim) {:#trim_dd54322} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that removes leading and trailing characters from a string or byte array expression. + +```typescript +// Trim whitespace from the 'userInput' field +trim(field("userInput")); + +// Trim quotes from the 'userInput' field +trim(field("userInput"), '"'); + +``` + +Signature: + +```typescript +export declare function trim(stringExpression: Expression, valueToTrim?: string | Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| stringExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the string or byte array to trim. | +| valueToTrim | string \| [Expression](./firestore_pipelines.expression.md#expression_class) | Optional This parameter is treated as a set of characters or bytes that will be trimmed from the input. If not specified, then whitespace will be trimmed. A new representing the trimmed string or byte array. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(timestamp, ...) + +### timestampAdd(timestamp, unit, amount) {:#timestampadd_98418f9} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that adds a specified amount of time to a timestamp. + +```typescript +// Add some duration determined by field 'unit' and 'amount' to the 'timestamp' field. +timestampAdd(field("timestamp"), field("unit"), field("amount")); + +``` + +Signature: + +```typescript +export declare function timestampAdd(timestamp: Expression, unit: Expression, amount: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| timestamp | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the timestamp. | +| unit | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression evaluates to unit of time, must be one of 'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'. | +| amount | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression evaluates to amount of the unit. A new representing the resulting timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### timestampAdd(timestamp, unit, amount) {:#timestampadd_ffe8e57} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that adds a specified amount of time to a timestamp. + +```typescript +// Add 1 day to the 'timestamp' field. +timestampAdd(field("timestamp"), "day", 1); + +``` + +Signature: + +```typescript +export declare function timestampAdd(timestamp: Expression, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| timestamp | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the timestamp. | +| unit | 'microsecond' \| 'millisecond' \| 'second' \| 'minute' \| 'hour' \| 'day' | The unit of time to add (e.g., "day", "hour"). | +| amount | number | The amount of time to add. A new representing the resulting timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### timestampSubtract(timestamp, unit, amount) {:#timestampsubtract_98418f9} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that subtracts a specified amount of time from a timestamp. + +```typescript +// Subtract some duration determined by field 'unit' and 'amount' from the 'timestamp' field. +timestampSubtract(field("timestamp"), field("unit"), field("amount")); + +``` + +Signature: + +```typescript +export declare function timestampSubtract(timestamp: Expression, unit: Expression, amount: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| timestamp | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the timestamp. | +| unit | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression evaluates to unit of time, must be one of 'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'. | +| amount | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression evaluates to amount of the unit. A new representing the resulting timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### timestampSubtract(timestamp, unit, amount) {:#timestampsubtract_ffe8e57} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that subtracts a specified amount of time from a timestamp. + +```typescript +// Subtract 1 day from the 'timestamp' field. +timestampSubtract(field("timestamp"), "day", 1); + +``` + +Signature: + +```typescript +export declare function timestampSubtract(timestamp: Expression, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| timestamp | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the timestamp. | +| unit | 'microsecond' \| 'millisecond' \| 'second' \| 'minute' \| 'hour' \| 'day' | The unit of time to subtract (e.g., "day", "hour"). | +| amount | number | The amount of time to subtract. A new representing the resulting timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(timestampExpression, ...) + +### timestampTruncate(timestampExpression, granularity, timezone) {:#timestamptruncate_ad5d843} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that truncates a timestamp to a specified granularity. + +Signature: + +```typescript +export declare function timestampTruncate(timestampExpression: Expression, granularity: TimeGranularity, timezone?: string | Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| timestampExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | Truncate the timestamp value that is returned by this expression. | +| granularity | [TimeGranularity](./firestore_pipelines.md#timegranularity) | The granularity to truncate to. | +| timezone | string \| [Expression](./firestore_pipelines.expression.md#expression_class) | The timezone to use for truncation. Valid values are from the TZ database (e.g., "America/Los\_Angeles") or in the format "Etc/GMT-1". A new {Expression} representing the truncated timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### Example + + +```typescript +// Truncate the 'createdAt' timestamp to the beginning of the day. +field('createdAt').timestampTruncate('day') + +``` + +### timestampTruncate(timestampExpression, granularity, timezone) {:#timestamptruncate_d6ab2a4} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that truncates a timestamp to a specified granularity. + +Signature: + +```typescript +export declare function timestampTruncate(timestampExpression: Expression, granularity: Expression, timezone?: string | Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| timestampExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | Truncate the timestamp value that is returned by this expression. | +| granularity | [Expression](./firestore_pipelines.expression.md#expression_class) | The granularity to truncate to. | +| timezone | string \| [Expression](./firestore_pipelines.expression.md#expression_class) | The timezone to use for truncation. Valid values are from the TZ database (e.g., "America/Los\_Angeles") or in the format "Etc/GMT-1". A new {Expression} representing the truncated timestamp. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### Example + + +```typescript +// Truncate the 'createdAt' timestamp to the granularity specified in the field 'granularity'. +field('createdAt').timestampTruncate(field('granularity')) + +``` + +## function(tryExpr, ...) + +### ifError(tryExpr, catchExpr) {:#iferror_a99a327} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the `catch` argument if there is an error, else return the result of the `try` argument evaluation. + +This overload is useful when a BooleanExpression is required. + +```typescript +// Create an expression that protects against a divide by zero error +// but always returns a boolean expression. +ifError(constant(50).divide('length').gt(1), constant(false)); + +``` + +Signature: + +```typescript +export declare function ifError(tryExpr: BooleanExpression, catchExpr: BooleanExpression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| tryExpr | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) | The try expression. | +| catchExpr | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) | The catch expression that will be evaluated and returned if the tryExpr produces an error. A new representing the 'ifError' operation. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### ifError(tryExpr, catchExpr) {:#iferror_756c12e} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the `catch` argument if there is an error, else return the result of the `try` argument evaluation. + +```typescript +// Returns the first item in the title field arrays, or returns +// the entire title field if the array is empty or the field is another type. +ifError(field("title").arrayGet(0), field("title")); + +``` + +Signature: + +```typescript +export declare function ifError(tryExpr: Expression, catchExpr: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| tryExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | The try expression. | +| catchExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | The catch expression that will be evaluated and returned if the tryExpr produces an error. A new representing the 'ifError' operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### ifError(tryExpr, catchValue) {:#iferror_dc532f9} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns the `catch` argument if there is an error, else return the result of the `try` argument evaluation. + +```typescript +// Returns the first item in the title field arrays, or returns +// "Default Title" +ifError(field("title").arrayGet(0), "Default Title"); + +``` + +Signature: + +```typescript +export declare function ifError(tryExpr: Expression, catchValue: unknown): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| tryExpr | [Expression](./firestore_pipelines.expression.md#expression_class) | The try expression. | +| catchValue | unknown | The value that will be returned if the tryExpr produces an error. A new representing the 'ifError' operation. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## function(value, ...) + +### constant(value) {:#constant_0c00f91} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates a `Constant` instance for a number value. + +Signature: + +```typescript +export declare function constant(value: number): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | number | The number value. A new Constant instance. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +### constant(value) {:#constant_6dac335} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates a `Constant` instance for a VectorValue value. + +Signature: + +```typescript +export declare function constant(value: VectorValue): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) | The VectorValue value. A new Constant instance. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +### constant(value) {:#constant_7c807cd} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates a `Constant` instance for a string value. + +Signature: + +```typescript +export declare function constant(value: string): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | string | The string value. A new Constant instance. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +### constant(value) {:#constant_b2e4f8d} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates a `BooleanExpression` instance for a boolean value. + +Signature: + +```typescript +export declare function constant(value: boolean): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | boolean | The boolean value. A new Constant instance. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### constant(value) {:#constant_73ebd84} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates a `Constant` instance for a null value. + +Signature: + +```typescript +export declare function constant(value: null): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | null | The null value. A new Constant instance. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +### constant(value) {:#constant_72a76cb} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates a `Constant` instance for a GeoPoint value. + +Signature: + +```typescript +export declare function constant(value: GeoPoint): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | [GeoPoint](./firestore_.geopoint.md#geopoint_class) | The GeoPoint value. A new Constant instance. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +### constant(value) {:#constant_000477d} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates a `Constant` instance for a Timestamp value. + +Signature: + +```typescript +export declare function constant(value: Timestamp): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | [Timestamp](./firestore_.timestamp.md#timestamp_class) | The Timestamp value. A new Constant instance. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +### constant(value) {:#constant_5131bf7} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates a `Constant` instance for a Date value. + +Signature: + +```typescript +export declare function constant(value: Date): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | Date | The Date value. A new Constant instance. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +### constant(value) {:#constant_fdf565d} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates a `Constant` instance for a Bytes value. + +Signature: + +```typescript +export declare function constant(value: Bytes): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | [Bytes](./firestore_.bytes.md#bytes_class) | The Bytes value. A new Constant instance. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +### constant(value) {:#constant_bcd2b0b} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates a `Constant` instance for a DocumentReference value. + +Signature: + +```typescript +export declare function constant(value: DocumentReference): Expression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | [DocumentReference](./firestore_.documentreference.md#documentreference_class) | The DocumentReference value. A new Constant instance. | + +Returns: + +[Expression](./firestore_pipelines.expression.md#expression_class) + +### exists(value) {:#exists_f3daf14} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a field exists. + +```typescript +// Check if the document has a field named "phoneNumber" +exists(field("phoneNumber")); + +``` + +Signature: + +```typescript +export declare function exists(value: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | [Expression](./firestore_pipelines.expression.md#expression_class) | An expression evaluates to the name of the field to check. A new representing the 'exists' check. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### isAbsent(value) {:#isabsent_f3daf14} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that returns `true` if a value is absent. Otherwise, returns `false` even if the value is `null`. + +```typescript +// Check if the field `value` is absent. +isAbsent(field("value")); + +``` + +Signature: + +```typescript +export declare function isAbsent(value: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to check. A new representing the 'isAbsent' check. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +### isError(value) {:#iserror_f3daf14} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that checks if a given expression produces an error. + +```typescript +// Check if the result of a calculation is an error +isError(field("title").arrayContains(1)); + +``` + +Signature: + +```typescript +export declare function isError(value: Expression): BooleanExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| value | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression to check. A new representing the 'isError' check. | + +Returns: + +[BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) + +## function(vectorExpression, ...) + +### cosineDistance(vectorExpression, vector) {:#cosinedistance_3a80317} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the Cosine distance between a vector expression and a vector literal. + +```typescript +// Calculate the cosine distance between the 'location' field and a target location +cosineDistance(field("location"), [37.7749, -122.4194]); + +``` + +Signature: + +```typescript +export declare function cosineDistance(vectorExpression: Expression, vector: number[] | VectorValue): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The first vector (represented as an Expr) to compare against. | +| vector | number\[\] \| [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) | The other vector (as an array of doubles or VectorValue) to compare against. A new representing the cosine distance between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### cosineDistance(vectorExpression, otherVectorExpression) {:#cosinedistance_17b5bcc} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the Cosine distance between two vector expressions. + +```typescript +// Calculate the cosine distance between the 'userVector' field and the 'itemVector' field +cosineDistance(field("userVector"), field("itemVector")); + +``` + +Signature: + +```typescript +export declare function cosineDistance(vectorExpression: Expression, otherVectorExpression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The first vector (represented as an Expr) to compare against. | +| otherVectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The other vector (represented as an Expr) to compare against. A new representing the cosine distance between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### dotProduct(vectorExpression, vector) {:#dotproduct_3a80317} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the dot product between a vector expression and a double array. + +```typescript +// Calculate the dot product between a feature vector and a target vector +dotProduct(field("features"), [0.5, 0.8, 0.2]); + +``` + +Signature: + +```typescript +export declare function dotProduct(vectorExpression: Expression, vector: number[] | VectorValue): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The first vector (represented as an Expr) to calculate with. | +| vector | number\[\] \| [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) | The other vector (as an array of doubles or VectorValue) to calculate with. A new representing the dot product between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### dotProduct(vectorExpression, otherVectorExpression) {:#dotproduct_17b5bcc} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the dot product between two vector expressions. + +```typescript +// Calculate the dot product between two document vectors: 'docVector1' and 'docVector2' +dotProduct(field("docVector1"), field("docVector2")); + +``` + +Signature: + +```typescript +export declare function dotProduct(vectorExpression: Expression, otherVectorExpression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The first vector (represented as an Expr) to calculate with. | +| otherVectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The other vector (represented as an Expr) to calculate with. A new representing the dot product between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### euclideanDistance(vectorExpression, vector) {:#euclideandistance_3a80317} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the Euclidean distance between a vector expression and a double array. + +```typescript +// Calculate the Euclidean distance between the 'location' field and a target location + +euclideanDistance(field("location"), [37.7749, -122.4194]); + +``` + +Signature: + +```typescript +export declare function euclideanDistance(vectorExpression: Expression, vector: number[] | VectorValue): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The first vector (represented as an Expr) to compare against. | +| vector | number\[\] \| [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) | The other vector (as an array of doubles or VectorValue) to compare against. A new representing the Euclidean distance between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### euclideanDistance(vectorExpression, otherVectorExpression) {:#euclideandistance_17b5bcc} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Calculates the Euclidean distance between two vector expressions. + +```typescript +// Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB' +euclideanDistance(field("pointA"), field("pointB")); + +``` + +Signature: + +```typescript +export declare function euclideanDistance(vectorExpression: Expression, otherVectorExpression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The first vector (represented as an Expr) to compare against. | +| otherVectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The other vector (represented as an Expr) to compare against. A new representing the Euclidean distance between the two vectors. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +### vectorLength(vectorExpression) {:#vectorlength_58a039b} + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Creates an expression that calculates the length of a Firestore Vector. + +```typescript +// Get the vector length (dimension) of the field 'embedding'. +vectorLength(field("embedding")); + +``` + +Signature: + +```typescript +export declare function vectorLength(vectorExpression: Expression): FunctionExpression; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| vectorExpression | [Expression](./firestore_pipelines.expression.md#expression_class) | The expression representing the Firestore Vector. A new representing the length of the array. | + +Returns: + +[FunctionExpression](./firestore_pipelines.functionexpression.md#functionexpression_class) + +## AddFieldsStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how an AddFieldsStage is evaluated. See . + +Signature: + +```typescript +export declare type AddFieldsStageOptions = StageOptions & { + fields: Selectable[]; +}; +``` + +## AggregateStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how an AggregateStage is evaluated. See . + +Signature: + +```typescript +export declare type AggregateStageOptions = StageOptions & { + accumulators: AliasedAggregate[]; + groups?: Array; +}; +``` + +## CollectionGroupStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Defines the configuration options for a within a pipeline. This type extends and provides specific settings for how a collection group is identified and processed during pipeline execution. + +Signature: + +```typescript +export declare type CollectionGroupStageOptions = StageOptions & { + collectionId: string; + forceIndex?: string; +}; +``` + +## CollectionStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how a CollectionStage is evaluated. See . + +Signature: + +```typescript +export declare type CollectionStageOptions = StageOptions & { + collection: string | Query; + forceIndex?: string; +}; +``` + +## DatabaseStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how a DatabaseStage is evaluated. See . + +Signature: + +```typescript +export declare type DatabaseStageOptions = StageOptions & {}; +``` + +## DistinctStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how a DistinctStage is evaluated. See . + +Signature: + +```typescript +export declare type DistinctStageOptions = StageOptions & { + groups: Array; +}; +``` + +## DocumentsStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how a DocumentsStage is evaluated. See . + +Signature: + +```typescript +export declare type DocumentsStageOptions = StageOptions & { + docs: Array; +}; +``` + +## ExpressionType + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +An enumeration of the different types of expressions. + +Signature: + +```typescript +export declare type ExpressionType = 'Field' | 'Constant' | 'Function' | 'AggregateFunction' | 'ListOfExpressions' | 'AliasedExpression'; +``` + +## FindNearestStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how a FindNearestStage is evaluated. See . + +Signature: + +```typescript +export declare type FindNearestStageOptions = StageOptions & { + field: Field | string; + vectorValue: VectorValue | number[]; + distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; + limit?: number; + distanceField?: string; +}; +``` + +## LimitStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how a LimitStage is evaluated. See . + +Signature: + +```typescript +export declare type LimitStageOptions = StageOptions & { + limit: number; +}; +``` + +## OffsetStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how an OffsetStage is evaluated. See . + +Signature: + +```typescript +export declare type OffsetStageOptions = StageOptions & { + offset: number; +}; +``` + +## OneOf + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Utility type to create an type that only allows one property of the Type param T to be set. + +type XorY = OneOf<{ x: unknown, y: unknown}> let a = { x: "foo" } // OK let b = { y: "foo" } // OK let c = { a: "foo", y: "foo" } // Not OK + +Signature: + +```typescript +export declare type OneOf = { + [K in keyof T]: Pick & { + [P in Exclude]?: undefined; + }; +}[keyof T]; +``` + +## PartialWithFieldValue + +Similar to TypeScript's `Partial`, but allows nested fields to be omitted and FieldValues to be passed in as property values. + +Signature: + +```typescript +export declare type PartialWithFieldValue = Partial | (T extends Primitive ? T : T extends {} ? { + [K in keyof T]?: PartialWithFieldValue | FieldValue; +} : never); +``` + +## Primitive + +Primitive types. + +Signature: + +```typescript +export declare type Primitive = string | number | boolean | undefined | null; +``` + +## RemoveFieldsStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how a RemoveFieldsStage is evaluated. See . + +Signature: + +```typescript +export declare type RemoveFieldsStageOptions = StageOptions & { + fields: Array; +}; +``` + +## ReplaceWithStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how a ReplaceWithStage is evaluated. See . + +Signature: + +```typescript +export declare type ReplaceWithStageOptions = StageOptions & { + map: Expression | string; +}; +``` + +## SampleStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Defines the options for evaluating a sample stage within a pipeline. This type combines common with a specific configuration where only one of the defined sampling methods can be applied. + +See to create a sample stage.. + +Signature: + +```typescript +export declare type SampleStageOptions = StageOptions & OneOf<{ + percentage: number; + documents: number; +}>; +``` + +## SelectStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how a SelectStage is evaluated. See . + +Signature: + +```typescript +export declare type SelectStageOptions = StageOptions & { + selections: Array; +}; +``` + +## SetOptions + +An options object that configures the behavior of [setDoc()](./firestore_lite.md#setdoc_ee215ad), and calls. These calls can be configured to perform granular merges instead of overwriting the target documents in their entirety by providing a `SetOptions` with `merge: true`. + +Signature: + +```typescript +export declare type SetOptions = { + readonly merge?: boolean; +} | { + readonly mergeFields?: Array; +}; +``` + +## SortStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how a SortStage is evaluated. See . + +Signature: + +```typescript +export declare type SortStageOptions = StageOptions & { + orderings: Ordering[]; +}; +``` + +## TimeGranularity + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Specify time granularity for expressions. + +Signature: + +```typescript +export declare type TimeGranularity = 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'week(monday)' | 'week(tuesday)' | 'week(wednesday)' | 'week(thursday)' | 'week(friday)' | 'week(saturday)' | 'week(sunday)' | 'isoWeek' | 'month' | 'quarter' | 'year' | 'isoYear'; +``` + +## UnionStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how a UnionStage is evaluated. See . + +Signature: + +```typescript +export declare type UnionStageOptions = StageOptions & { + other: Pipeline; +}; +``` + +## UnnestStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Represents the specific options available for configuring an `UnnestStage` within a pipeline. + +Signature: + +```typescript +export declare type UnnestStageOptions = StageOptions & { + selectable: Selectable; + indexField?: string; +}; +``` + +## WhereStageOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how a WhereStage is evaluated. See . + +Signature: + +```typescript +export declare type WhereStageOptions = StageOptions & { + condition: BooleanExpression; +}; +``` + +## WithFieldValue + +Allows FieldValues to be passed in as a property value while maintaining type safety. + +Signature: + +```typescript +export declare type WithFieldValue = T | (T extends Primitive ? T : T extends {} ? { + [K in keyof T]: WithFieldValue | FieldValue; +} : never); +``` diff --git a/docs-devsite/firestore_pipelines.ordering.md b/docs-devsite/firestore_pipelines.ordering.md new file mode 100644 index 0000000000..373f962aee --- /dev/null +++ b/docs-devsite/firestore_pipelines.ordering.md @@ -0,0 +1,80 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Ordering class +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Represents an ordering criterion for sorting documents in a Firestore pipeline. + +You create `Ordering` instances using the `ascending` and `descending` helper functions. + +Signature: + +```typescript +export declare class Ordering +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(expr, direction, \_methodName)](./firestore_pipelines.ordering.md#orderingconstructor) | | (Public Preview) Constructs a new instance of the Ordering class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [direction](./firestore_pipelines.ordering.md#orderingdirection) | | 'ascending' \| 'descending' | (Public Preview) | +| [expr](./firestore_pipelines.ordering.md#orderingexpr) | | [Expression](./firestore_pipelines.expression.md#expression_class) | (Public Preview) | + +## Ordering.(constructor) + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Constructs a new instance of the `Ordering` class + +Signature: + +```typescript +constructor(expr: Expression, direction: 'ascending' | 'descending', _methodName: string | undefined); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) | | +| direction | 'ascending' \| 'descending' | | +| \_methodName | string \| undefined | | + +## Ordering.direction + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +readonly direction: 'ascending' | 'descending'; +``` + +## Ordering.expr + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +readonly expr: Expression; +``` diff --git a/docs-devsite/firestore_pipelines.pipeline.md b/docs-devsite/firestore_pipelines.pipeline.md new file mode 100644 index 0000000000..4ba1ba716c --- /dev/null +++ b/docs-devsite/firestore_pipelines.pipeline.md @@ -0,0 +1,674 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Pipeline class +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + + +Signature: + +```typescript +export declare class Pipeline +``` + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [addFields(field, additionalFields)](./firestore_pipelines.pipeline.md#pipelineaddfields) | | (Public Preview) | +| [addFields(options)](./firestore_pipelines.pipeline.md#pipelineaddfields) | | (Public Preview) | +| [aggregate(accumulator, additionalAccumulators)](./firestore_pipelines.pipeline.md#pipelineaggregate) | | (Public Preview) | +| [aggregate(options)](./firestore_pipelines.pipeline.md#pipelineaggregate) | | (Public Preview) | +| [distinct(group, additionalGroups)](./firestore_pipelines.pipeline.md#pipelinedistinct) | | (Public Preview) | +| [distinct(options)](./firestore_pipelines.pipeline.md#pipelinedistinct) | | (Public Preview) | +| [findNearest(options)](./firestore_pipelines.pipeline.md#pipelinefindnearest) | | (Public Preview) | +| [limit(limit)](./firestore_pipelines.pipeline.md#pipelinelimit) | | (Public Preview) | +| [limit(options)](./firestore_pipelines.pipeline.md#pipelinelimit) | | (Public Preview) | +| [offset(offset)](./firestore_pipelines.pipeline.md#pipelineoffset) | | (Public Preview) | +| [offset(options)](./firestore_pipelines.pipeline.md#pipelineoffset) | | (Public Preview) | +| [rawStage(name, params, options)](./firestore_pipelines.pipeline.md#pipelinerawstage) | | (Public Preview) | +| [removeFields(fieldValue, additionalFields)](./firestore_pipelines.pipeline.md#pipelineremovefields) | | (Public Preview) | +| [removeFields(options)](./firestore_pipelines.pipeline.md#pipelineremovefields) | | (Public Preview) | +| [replaceWith(fieldName)](./firestore_pipelines.pipeline.md#pipelinereplacewith) | | (Public Preview) | +| [replaceWith(expr)](./firestore_pipelines.pipeline.md#pipelinereplacewith) | | (Public Preview) | +| [replaceWith(options)](./firestore_pipelines.pipeline.md#pipelinereplacewith) | | (Public Preview) | +| [sample(documents)](./firestore_pipelines.pipeline.md#pipelinesample) | | (Public Preview) | +| [sample(options)](./firestore_pipelines.pipeline.md#pipelinesample) | | (Public Preview) | +| [select(selection, additionalSelections)](./firestore_pipelines.pipeline.md#pipelineselect) | | (Public Preview) | +| [select(options)](./firestore_pipelines.pipeline.md#pipelineselect) | | (Public Preview) | +| [sort(ordering, additionalOrderings)](./firestore_pipelines.pipeline.md#pipelinesort) | | (Public Preview) | +| [sort(options)](./firestore_pipelines.pipeline.md#pipelinesort) | | (Public Preview) | +| [union(other)](./firestore_pipelines.pipeline.md#pipelineunion) | | (Public Preview) | +| [union(options)](./firestore_pipelines.pipeline.md#pipelineunion) | | (Public Preview) | +| [unnest(selectable, indexField)](./firestore_pipelines.pipeline.md#pipelineunnest) | | (Public Preview) | +| [unnest(options)](./firestore_pipelines.pipeline.md#pipelineunnest) | | (Public Preview) | +| [where(condition)](./firestore_pipelines.pipeline.md#pipelinewhere) | | (Public Preview) | +| [where(options)](./firestore_pipelines.pipeline.md#pipelinewhere) | | (Public Preview) | + +## Pipeline.addFields() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +addFields(field: Selectable, ...additionalFields: Selectable[]): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| field | [Selectable](./firestore_pipelines.selectable.md#selectable_interface) | | +| additionalFields | [Selectable](./firestore_pipelines.selectable.md#selectable_interface)\[\] | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.addFields() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +addFields(options: AddFieldsStageOptions): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [AddFieldsStageOptions](./firestore_pipelines.md#addfieldsstageoptions) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.aggregate() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +aggregate(accumulator: AliasedAggregate, ...additionalAccumulators: AliasedAggregate[]): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| accumulator | [AliasedAggregate](./firestore_pipelines.aliasedaggregate.md#aliasedaggregate_class) | | +| additionalAccumulators | [AliasedAggregate](./firestore_pipelines.aliasedaggregate.md#aliasedaggregate_class)\[\] | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.aggregate() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +aggregate(options: AggregateStageOptions): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [AggregateStageOptions](./firestore_pipelines.md#aggregatestageoptions) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.distinct() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +distinct(group: string | Selectable, ...additionalGroups: Array): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| group | string \| [Selectable](./firestore_pipelines.selectable.md#selectable_interface) | | +| additionalGroups | Array<string \| [Selectable](./firestore_pipelines.selectable.md#selectable_interface)> | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.distinct() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +distinct(options: DistinctStageOptions): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [DistinctStageOptions](./firestore_pipelines.md#distinctstageoptions) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.findNearest() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +findNearest(options: FindNearestStageOptions): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [FindNearestStageOptions](./firestore_pipelines.md#findneareststageoptions) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.limit() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +limit(limit: number): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| limit | number | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.limit() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +limit(options: LimitStageOptions): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [LimitStageOptions](./firestore_pipelines.md#limitstageoptions) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.offset() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +offset(offset: number): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| offset | number | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.offset() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +offset(options: OffsetStageOptions): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [OffsetStageOptions](./firestore_pipelines.md#offsetstageoptions) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.rawStage() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +rawStage(name: string, params: unknown[], options?: { [key: string]: Expression | unknown; }): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | +| params | unknown\[\] | | +| options | { \[key: string\]: [Expression](./firestore_pipelines.expression.md#expression_class) \| unknown; } | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.removeFields() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +removeFields(fieldValue: Field | string, ...additionalFields: Array): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldValue | [Field](./firestore_pipelines.field.md#field_class) \| string | | +| additionalFields | Array<[Field](./firestore_pipelines.field.md#field_class) \| string> | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.removeFields() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +removeFields(options: RemoveFieldsStageOptions): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [RemoveFieldsStageOptions](./firestore_pipelines.md#removefieldsstageoptions) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.replaceWith() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +replaceWith(fieldName: string): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.replaceWith() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +replaceWith(expr: Expression): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| expr | [Expression](./firestore_pipelines.expression.md#expression_class) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.replaceWith() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +replaceWith(options: ReplaceWithStageOptions): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [ReplaceWithStageOptions](./firestore_pipelines.md#replacewithstageoptions) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.sample() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +sample(documents: number): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| documents | number | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.sample() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +sample(options: SampleStageOptions): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [SampleStageOptions](./firestore_pipelines.md#samplestageoptions) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.select() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +select(selection: Selectable | string, ...additionalSelections: Array): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| selection | [Selectable](./firestore_pipelines.selectable.md#selectable_interface) \| string | | +| additionalSelections | Array<[Selectable](./firestore_pipelines.selectable.md#selectable_interface) \| string> | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.select() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +select(options: SelectStageOptions): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [SelectStageOptions](./firestore_pipelines.md#selectstageoptions) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.sort() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +sort(ordering: Ordering, ...additionalOrderings: Ordering[]): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| ordering | [Ordering](./firestore_pipelines.ordering.md#ordering_class) | | +| additionalOrderings | [Ordering](./firestore_pipelines.ordering.md#ordering_class)\[\] | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.sort() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +sort(options: SortStageOptions): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [SortStageOptions](./firestore_pipelines.md#sortstageoptions) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.union() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +union(other: Pipeline): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| other | [Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.union() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +union(options: UnionStageOptions): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [UnionStageOptions](./firestore_pipelines.md#unionstageoptions) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.unnest() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +unnest(selectable: Selectable, indexField?: string): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| selectable | [Selectable](./firestore_pipelines.selectable.md#selectable_interface) | | +| indexField | string | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.unnest() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +unnest(options: UnnestStageOptions): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [UnnestStageOptions](./firestore_pipelines.md#unneststageoptions) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.where() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +where(condition: BooleanExpression): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| condition | [BooleanExpression](./firestore_pipelines.booleanexpression.md#booleanexpression_class) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +## Pipeline.where() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +where(options: WhereStageOptions): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [WhereStageOptions](./firestore_pipelines.md#wherestageoptions) | | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + diff --git a/docs-devsite/firestore_pipelines.pipelineexecuteoptions.md b/docs-devsite/firestore_pipelines.pipelineexecuteoptions.md new file mode 100644 index 0000000000..fc0ed90769 --- /dev/null +++ b/docs-devsite/firestore_pipelines.pipelineexecuteoptions.md @@ -0,0 +1,132 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# PipelineExecuteOptions interface +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining Pipeline execution. + +Signature: + +```typescript +export declare interface PipelineExecuteOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [indexMode](./firestore_pipelines.pipelineexecuteoptions.md#pipelineexecuteoptionsindexmode) | 'recommended' | (Public Preview) Specify the index mode. | +| [pipeline](./firestore_pipelines.pipelineexecuteoptions.md#pipelineexecuteoptionspipeline) | [Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) | (Public Preview) Pipeline to be evaluated. | +| [rawOptions](./firestore_pipelines.pipelineexecuteoptions.md#pipelineexecuteoptionsrawoptions) | { \[name: string\]: unknown; } | (Public Preview) An escape hatch to set options not known at SDK build time. These values will be passed directly to the Firestore backend and not used by the SDK.The option name will be used as provided. And must match the name format used by the backend (hint: use a snake\_case\_name).Custom option values can be any type supported by Firestore (for example: string, boolean, number, map, …). Value types not known to the SDK will be rejected.Values specified in rawOptions will take precedence over any options with the same name set by the SDK.Override the example_option: +``` + execute({ + pipeline: myPipeline, + rawOptions: { + // Override `example_option`. This will not + // merge with the existing `example_option` object. + "example_option": { + foo: "bar" + } + } + } + +``` +rawOptions supports dot notation, if you want to override a nested option. +``` + execute({ + pipeline: myPipeline, + rawOptions: { + // Override `example_option.foo` and do not override + // any other properties of `example_option`. + "example_option.foo": "bar" + } + } + +``` + | + +## PipelineExecuteOptions.indexMode + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Specify the index mode. + +Signature: + +```typescript +indexMode?: 'recommended'; +``` + +## PipelineExecuteOptions.pipeline + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Pipeline to be evaluated. + +Signature: + +```typescript +pipeline: Pipeline; +``` + +## PipelineExecuteOptions.rawOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +An escape hatch to set options not known at SDK build time. These values will be passed directly to the Firestore backend and not used by the SDK. + +The option name will be used as provided. And must match the name format used by the backend (hint: use a snake\_case\_name). + +Custom option values can be any type supported by Firestore (for example: string, boolean, number, map, …). Value types not known to the SDK will be rejected. + +Values specified in rawOptions will take precedence over any options with the same name set by the SDK. + +Override the `example_option`: + +``` + execute({ + pipeline: myPipeline, + rawOptions: { + // Override `example_option`. This will not + // merge with the existing `example_option` object. + "example_option": { + foo: "bar" + } + } + } + +``` +`rawOptions` supports dot notation, if you want to override a nested option. + +``` + execute({ + pipeline: myPipeline, + rawOptions: { + // Override `example_option.foo` and do not override + // any other properties of `example_option`. + "example_option.foo": "bar" + } + } + +``` + +Signature: + +```typescript +rawOptions?: { + [name: string]: unknown; + }; +``` diff --git a/docs-devsite/firestore_pipelines.pipelineresult.md b/docs-devsite/firestore_pipelines.pipelineresult.md new file mode 100644 index 0000000000..b5dfb1a41d --- /dev/null +++ b/docs-devsite/firestore_pipelines.pipelineresult.md @@ -0,0 +1,168 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# PipelineResult class +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +A PipelineResult contains data read from a Firestore Pipeline. The data can be extracted with the or methods. + +

If the PipelineResult represents a non-document result, `ref` will return a undefined value. + +Signature: + +```typescript +export declare class PipelineResult +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [createTime](./firestore_pipelines.pipelineresult.md#pipelineresultcreatetime) | | [Timestamp](./firestore_.timestamp.md#timestamp_class) \| undefined | (Public Preview) The time the document was created. Undefined if this result is not a document. {Timestamp\|undefined} | +| [id](./firestore_pipelines.pipelineresult.md#pipelineresultid) | | string \| undefined | (Public Preview) The ID of the document for which this PipelineResult contains data, if it is a document; otherwise undefined. {string} | +| [ref](./firestore_pipelines.pipelineresult.md#pipelineresultref) | | [DocumentReference](./firestore_.documentreference.md#documentreference_class) \| undefined | (Public Preview) The reference of the document, if it is a document; otherwise undefined. | +| [updateTime](./firestore_pipelines.pipelineresult.md#pipelineresultupdatetime) | | [Timestamp](./firestore_.timestamp.md#timestamp_class) \| undefined | (Public Preview) The time the document was last updated (at the time the snapshot was generated). Undefined if this result is not a document. {Timestamp\|undefined} | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [data()](./firestore_pipelines.pipelineresult.md#pipelineresultdata) | | (Public Preview) Retrieves all fields in the result as an object. | +| [get(fieldPath)](./firestore_pipelines.pipelineresult.md#pipelineresultget) | | (Public Preview) Retrieves the field specified by field. | + +## PipelineResult.createTime + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +The time the document was created. Undefined if this result is not a document. + + {Timestamp\|undefined} + +Signature: + +```typescript +get createTime(): Timestamp | undefined; +``` + +## PipelineResult.id + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +The ID of the document for which this PipelineResult contains data, if it is a document; otherwise `undefined`. + + {string} + +Signature: + +```typescript +get id(): string | undefined; +``` + +## PipelineResult.ref + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +The reference of the document, if it is a document; otherwise `undefined`. + +Signature: + +```typescript +get ref(): DocumentReference | undefined; +``` + +## PipelineResult.updateTime + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +The time the document was last updated (at the time the snapshot was generated). Undefined if this result is not a document. + + {Timestamp\|undefined} + +Signature: + +```typescript +get updateTime(): Timestamp | undefined; +``` + +## PipelineResult.data() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Retrieves all fields in the result as an object. + +Signature: + +```typescript +data(): AppModelType; +``` +Returns: + +AppModelType + +{T} An object containing all fields in the document or 'undefined' if the document doesn't exist. + +### Example + + +``` +let p = firestore.pipeline().collection('col'); + +p.execute().then(results => { + let data = results[0].data(); + console.log(`Retrieved data: ${JSON.stringify(data)}`); +}); + +``` + +## PipelineResult.get() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Retrieves the field specified by `field`. + +Signature: + +```typescript +get(fieldPath: string | FieldPath | Field): any; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldPath | string \| [FieldPath](./firestore_.fieldpath.md#fieldpath_class) \| [Field](./firestore_pipelines.field.md#field_class) | | + +Returns: + +any + +{\*} The data at the specified field location or undefined if no such field exists. + +### Example + + +``` +let p = firestore.pipeline().collection('col'); + +p.execute().then(results => { + let field = results[0].get('a.b'); + console.log(`Retrieved field value: ${field}`); +}); + +``` + diff --git a/docs-devsite/firestore_pipelines.pipelinesnapshot.md b/docs-devsite/firestore_pipelines.pipelinesnapshot.md new file mode 100644 index 0000000000..7f6d4744f4 --- /dev/null +++ b/docs-devsite/firestore_pipelines.pipelinesnapshot.md @@ -0,0 +1,103 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# PipelineSnapshot class +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Represents the results of a Firestore pipeline execution. + +A `PipelineSnapshot` contains zero or more objects representing the documents returned by a pipeline query. It provides methods to iterate over the documents and access metadata about the query results. + +Signature: + +```typescript +export declare class PipelineSnapshot +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(pipeline, results, executionTime)](./firestore_pipelines.pipelinesnapshot.md#pipelinesnapshotconstructor) | | (Public Preview) Constructs a new instance of the PipelineSnapshot class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [executionTime](./firestore_pipelines.pipelinesnapshot.md#pipelinesnapshotexecutiontime) | | [Timestamp](./firestore_.timestamp.md#timestamp_class) | (Public Preview) The time at which the pipeline producing this result is executed. {Timestamp} | +| [results](./firestore_pipelines.pipelinesnapshot.md#pipelinesnapshotresults) | | [PipelineResult](./firestore_pipelines.pipelineresult.md#pipelineresult_class)\[\] | (Public Preview) An array of all the results in the PipelineSnapshot. | + +## PipelineSnapshot.(constructor) + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Constructs a new instance of the `PipelineSnapshot` class + +Signature: + +```typescript +constructor(pipeline: Pipeline, results: PipelineResult[], executionTime?: Timestamp); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| pipeline | [Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) | | +| results | [PipelineResult](./firestore_pipelines.pipelineresult.md#pipelineresult_class)\[\] | | +| executionTime | [Timestamp](./firestore_.timestamp.md#timestamp_class) | | + +## PipelineSnapshot.executionTime + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +The time at which the pipeline producing this result is executed. + + {Timestamp} + +Signature: + +```typescript +get executionTime(): Timestamp; +``` + +## PipelineSnapshot.results + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +An array of all the results in the `PipelineSnapshot`. + +Signature: + +```typescript +get results(): PipelineResult[]; +``` + +### Example + + +```typescript +const snapshot: PipelineSnapshot = await firestore + .pipeline() + .collection('myCollection') + .where(field('value').greaterThan(10)) + .execute(); + +snapshot.results.forEach(doc => { + console.log(doc.id, '=>', doc.data()); +}); + +``` + diff --git a/docs-devsite/firestore_pipelines.pipelinesource.md b/docs-devsite/firestore_pipelines.pipelinesource.md new file mode 100644 index 0000000000..383f31f7fd --- /dev/null +++ b/docs-devsite/firestore_pipelines.pipelinesource.md @@ -0,0 +1,251 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# PipelineSource class +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Provides the entry point for defining the data source of a Firestore . + +Use the methods of this class (e.g., , , , or ) to specify the initial data for your pipeline, such as a collection, a collection group, the entire database, or a set of specific documents. + +Signature: + +```typescript +export declare class PipelineSource +``` + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [collection(collection)](./firestore_pipelines.pipelinesource.md#pipelinesourcecollection) | | (Public Preview) Returns all documents from the entire collection. The collection can be nested. | +| [collection(options)](./firestore_pipelines.pipelinesource.md#pipelinesourcecollection) | | (Public Preview) Returns all documents from the entire collection. The collection can be nested. | +| [collectionGroup(collectionId)](./firestore_pipelines.pipelinesource.md#pipelinesourcecollectiongroup) | | (Public Preview) Returns all documents from a collection ID regardless of the parent. | +| [collectionGroup(options)](./firestore_pipelines.pipelinesource.md#pipelinesourcecollectiongroup) | | (Public Preview) Returns all documents from a collection ID regardless of the parent. | +| [createFrom(query)](./firestore_pipelines.pipelinesource.md#pipelinesourcecreatefrom) | | (Public Preview) Convert the given Query into an equivalent Pipeline. | +| [database()](./firestore_pipelines.pipelinesource.md#pipelinesourcedatabase) | | (Public Preview) Returns all documents from the entire database. | +| [database(options)](./firestore_pipelines.pipelinesource.md#pipelinesourcedatabase) | | (Public Preview) Returns all documents from the entire database. | +| [documents(docs)](./firestore_pipelines.pipelinesource.md#pipelinesourcedocuments) | | (Public Preview) Set the pipeline's source to the documents specified by the given paths and DocumentReferences. | +| [documents(options)](./firestore_pipelines.pipelinesource.md#pipelinesourcedocuments) | | (Public Preview) Set the pipeline's source to the documents specified by the given paths and DocumentReferences. | + +## PipelineSource.collection() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Returns all documents from the entire collection. The collection can be nested. + +Signature: + +```typescript +collection(collection: string | Query): PipelineType; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| collection | string \| [Query](./firestore_.query.md#query_class) | Name or reference to the collection that will be used as the Pipeline source. | + +Returns: + +PipelineType + +## PipelineSource.collection() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Returns all documents from the entire collection. The collection can be nested. + +Signature: + +```typescript +collection(options: CollectionStageOptions): PipelineType; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [CollectionStageOptions](./firestore_pipelines.md#collectionstageoptions) | Options defining how this CollectionStage is evaluated. | + +Returns: + +PipelineType + +## PipelineSource.collectionGroup() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Returns all documents from a collection ID regardless of the parent. + +Signature: + +```typescript +collectionGroup(collectionId: string): PipelineType; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| collectionId | string | ID of the collection group to use as the Pipeline source. | + +Returns: + +PipelineType + +## PipelineSource.collectionGroup() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Returns all documents from a collection ID regardless of the parent. + +Signature: + +```typescript +collectionGroup(options: CollectionGroupStageOptions): PipelineType; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [CollectionGroupStageOptions](./firestore_pipelines.md#collectiongroupstageoptions) | Options defining how this CollectionGroupStage is evaluated. | + +Returns: + +PipelineType + +## PipelineSource.createFrom() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Convert the given Query into an equivalent Pipeline. + +Signature: + +```typescript +createFrom(query: Query): Pipeline; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| query | [Query](./firestore_.query.md#query_class) | A Query to be converted into a Pipeline. | + +Returns: + +[Pipeline](./firestore_pipelines.pipeline.md#pipeline_class) + +#### Exceptions + + Thrown if any of the provided DocumentReferences target a different project or database than the pipeline. + +## PipelineSource.database() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Returns all documents from the entire database. + +Signature: + +```typescript +database(): PipelineType; +``` +Returns: + +PipelineType + +## PipelineSource.database() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Returns all documents from the entire database. + +Signature: + +```typescript +database(options: DatabaseStageOptions): PipelineType; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [DatabaseStageOptions](./firestore_pipelines.md#databasestageoptions) | Options defining how a DatabaseStage is evaluated. | + +Returns: + +PipelineType + +## PipelineSource.documents() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Set the pipeline's source to the documents specified by the given paths and DocumentReferences. + +Signature: + +```typescript +documents(docs: Array): PipelineType; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| docs | Array<string \| [DocumentReference](./firestore_.documentreference.md#documentreference_class)> | An array of paths and DocumentReferences specifying the individual documents that will be the source of this pipeline. The converters for these DocumentReferences will be ignored and not have an effect on this pipeline. | + +Returns: + +PipelineType + +#### Exceptions + + Thrown if any of the provided DocumentReferences target a different project or database than the pipeline. + +## PipelineSource.documents() + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Set the pipeline's source to the documents specified by the given paths and DocumentReferences. + +Signature: + +```typescript +documents(options: DocumentsStageOptions): PipelineType; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [DocumentsStageOptions](./firestore_pipelines.md#documentsstageoptions) | Options defining how this DocumentsStage is evaluated. | + +Returns: + +PipelineType + +#### Exceptions + + Thrown if any of the provided DocumentReferences target a different project or database than the pipeline. + diff --git a/docs-devsite/firestore_pipelines.query.md b/docs-devsite/firestore_pipelines.query.md new file mode 100644 index 0000000000..6b47d2485a --- /dev/null +++ b/docs-devsite/firestore_pipelines.query.md @@ -0,0 +1,125 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Query class +A `Query` refers to a query which you can read or listen to. You can also construct refined `Query` objects by adding filters and ordering. + +Signature: + +```typescript +export declare class Query +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)()](./firestore_pipelines.query.md#queryconstructor) | | Constructs a new instance of the Query class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [converter](./firestore_pipelines.query.md#queryconverter) | | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<AppModelType, DbModelType> \| null | If provided, the FirestoreDataConverter associated with this instance. | +| [firestore](./firestore_pipelines.query.md#queryfirestore) | | [Firestore](./firestore_.firestore.md#firestore_class) | The Firestore instance for the Firestore database (useful for performing transactions, etc.). | +| [type](./firestore_pipelines.query.md#querytype) | | 'query' \| 'collection' | The type of this Firestore reference. | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [withConverter(converter)](./firestore_pipelines.query.md#querywithconverter) | | Removes the current converter. | +| [withConverter(converter)](./firestore_pipelines.query.md#querywithconverter) | | Applies a custom data converter to this query, allowing you to use your own custom model objects with Firestore. When you call [getDocs()](./firestore_.md#getdocs_4e56953) with the returned query, the provided converter will convert between Firestore data of type NewDbModelType and your custom type NewAppModelType. | + +## Query.(constructor) + +Constructs a new instance of the `Query` class + +Signature: + +```typescript +protected constructor(); +``` + +## Query.converter + +If provided, the `FirestoreDataConverter` associated with this instance. + +Signature: + +```typescript +readonly converter: FirestoreDataConverter | null; +``` + +## Query.firestore + +The `Firestore` instance for the Firestore database (useful for performing transactions, etc.). + +Signature: + +```typescript +readonly firestore: Firestore; +``` + +## Query.type + +The type of this Firestore reference. + +Signature: + +```typescript +readonly type: 'query' | 'collection'; +``` + +## Query.withConverter() + +Removes the current converter. + +Signature: + +```typescript +withConverter(converter: null): Query; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| converter | null | null removes the current converter. | + +Returns: + +[Query](./firestore_.query.md#query_class)<[DocumentData](./firestore_.documentdata.md#documentdata_interface), [DocumentData](./firestore_.documentdata.md#documentdata_interface)> + +A `Query` that does not use a converter. + +## Query.withConverter() + +Applies a custom data converter to this query, allowing you to use your own custom model objects with Firestore. When you call [getDocs()](./firestore_.md#getdocs_4e56953) with the returned query, the provided converter will convert between Firestore data of type `NewDbModelType` and your custom type `NewAppModelType`. + +Signature: + +```typescript +withConverter(converter: FirestoreDataConverter): Query; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<NewAppModelType, NewDbModelType> | Converts objects to and from Firestore. | + +Returns: + +[Query](./firestore_.query.md#query_class)<NewAppModelType, NewDbModelType> + +A `Query` that uses the provided converter. + diff --git a/docs-devsite/firestore_pipelines.querydocumentsnapshot.md b/docs-devsite/firestore_pipelines.querydocumentsnapshot.md new file mode 100644 index 0000000000..ae64c7c1b9 --- /dev/null +++ b/docs-devsite/firestore_pipelines.querydocumentsnapshot.md @@ -0,0 +1,54 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# QueryDocumentSnapshot class +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()` or `.get()` to get a specific field. + +A `QueryDocumentSnapshot` offers the same API surface as a `DocumentSnapshot`. Since query results contain only existing documents, the `exists` property will always be true and `data()` will never return 'undefined'. + +Signature: + +```typescript +export declare class QueryDocumentSnapshot extends DocumentSnapshot +``` +Extends: [DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class)<AppModelType, DbModelType> + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [data(options)](./firestore_pipelines.querydocumentsnapshot.md#querydocumentsnapshotdata) | | Retrieves all fields in the document as an Object.By default, serverTimestamp() values that have not yet been set to their final value will be returned as null. You can override this by passing an options object. | + +## QueryDocumentSnapshot.data() + +Retrieves all fields in the document as an `Object`. + +By default, `serverTimestamp()` values that have not yet been set to their final value will be returned as `null`. You can override this by passing an options object. + +Signature: + +```typescript +/** @override */ +data(options?: SnapshotOptions): AppModelType; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [SnapshotOptions](./firestore_.snapshotoptions.md#snapshotoptions_interface) | An options object to configure how data is retrieved from the snapshot (for example the desired behavior for server timestamps that have not yet been set to their final value). | + +Returns: + +AppModelType + +An `Object` containing all fields in the document. + diff --git a/docs-devsite/firestore_pipelines.selectable.md b/docs-devsite/firestore_pipelines.selectable.md new file mode 100644 index 0000000000..53bde7320b --- /dev/null +++ b/docs-devsite/firestore_pipelines.selectable.md @@ -0,0 +1,39 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Selectable interface +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +An interface that represents a selectable expression. + +Signature: + +```typescript +export declare interface Selectable +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [selectable](./firestore_pipelines.selectable.md#selectableselectable) | true | (Public Preview) | + +## Selectable.selectable + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +selectable: true; +``` diff --git a/docs-devsite/firestore_pipelines.snapshotmetadata.md b/docs-devsite/firestore_pipelines.snapshotmetadata.md new file mode 100644 index 0000000000..be104c3034 --- /dev/null +++ b/docs-devsite/firestore_pipelines.snapshotmetadata.md @@ -0,0 +1,75 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# SnapshotMetadata class +Metadata about a snapshot, describing the state of the snapshot. + +Signature: + +```typescript +export declare class SnapshotMetadata +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [fromCache](./firestore_pipelines.snapshotmetadata.md#snapshotmetadatafromcache) | | boolean | True if the snapshot was created from cached data rather than guaranteed up-to-date server data. If your listener has opted into metadata updates (via SnapshotListenOptions) you will receive another snapshot with fromCache set to false once the client has received up-to-date data from the backend. | +| [hasPendingWrites](./firestore_pipelines.snapshotmetadata.md#snapshotmetadatahaspendingwrites) | | boolean | True if the snapshot contains the result of local writes (for example set() or update() calls) that have not yet been committed to the backend. If your listener has opted into metadata updates (via SnapshotListenOptions) you will receive another snapshot with hasPendingWrites equal to false once the writes have been committed to the backend. | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [isEqual(other)](./firestore_pipelines.snapshotmetadata.md#snapshotmetadataisequal) | | Returns true if this SnapshotMetadata is equal to the provided one. | + +## SnapshotMetadata.fromCache + +True if the snapshot was created from cached data rather than guaranteed up-to-date server data. If your listener has opted into metadata updates (via `SnapshotListenOptions`) you will receive another snapshot with `fromCache` set to false once the client has received up-to-date data from the backend. + +Signature: + +```typescript +readonly fromCache: boolean; +``` + +## SnapshotMetadata.hasPendingWrites + +True if the snapshot contains the result of local writes (for example `set()` or `update()` calls) that have not yet been committed to the backend. If your listener has opted into metadata updates (via `SnapshotListenOptions`) you will receive another snapshot with `hasPendingWrites` equal to false once the writes have been committed to the backend. + +Signature: + +```typescript +readonly hasPendingWrites: boolean; +``` + +## SnapshotMetadata.isEqual() + +Returns true if this `SnapshotMetadata` is equal to the provided one. + +Signature: + +```typescript +isEqual(other: SnapshotMetadata): boolean; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| other | [SnapshotMetadata](./firestore_.snapshotmetadata.md#snapshotmetadata_class) | The SnapshotMetadata to compare against. | + +Returns: + +boolean + +true if this `SnapshotMetadata` is equal to the provided one. + diff --git a/docs-devsite/firestore_pipelines.snapshotoptions.md b/docs-devsite/firestore_pipelines.snapshotoptions.md new file mode 100644 index 0000000000..cee6bb93eb --- /dev/null +++ b/docs-devsite/firestore_pipelines.snapshotoptions.md @@ -0,0 +1,41 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# SnapshotOptions interface +Options that configure how data is retrieved from a `DocumentSnapshot` (for example the desired behavior for server timestamps that have not yet been set to their final value). + +Signature: + +```typescript +export declare interface SnapshotOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [serverTimestamps](./firestore_pipelines.snapshotoptions.md#snapshotoptionsservertimestamps) | 'estimate' \| 'previous' \| 'none' | If set, controls the return value for server timestamps that have not yet been set to their final value.By specifying 'estimate', pending server timestamps return an estimate based on the local clock. This estimate will differ from the final value and cause these values to change once the server result becomes available.By specifying 'previous', pending timestamps will be ignored and return their previous value instead.If omitted or set to 'none', null will be returned by default until the server value becomes available. | + +## SnapshotOptions.serverTimestamps + +If set, controls the return value for server timestamps that have not yet been set to their final value. + +By specifying 'estimate', pending server timestamps return an estimate based on the local clock. This estimate will differ from the final value and cause these values to change once the server result becomes available. + +By specifying 'previous', pending timestamps will be ignored and return their previous value instead. + +If omitted or set to 'none', `null` will be returned by default until the server value becomes available. + +Signature: + +```typescript +readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; +``` diff --git a/docs-devsite/firestore_pipelines.stageoptions.md b/docs-devsite/firestore_pipelines.stageoptions.md new file mode 100644 index 0000000000..e85f6809dd --- /dev/null +++ b/docs-devsite/firestore_pipelines.stageoptions.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# StageOptions interface +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Options defining how a Stage is evaluated. + +Signature: + +```typescript +export declare interface StageOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [rawOptions](./firestore_pipelines.stageoptions.md#stageoptionsrawoptions) | { \[name: string\]: unknown; } | (Public Preview) An escape hatch to set options not known at SDK build time. These values will be passed directly to the Firestore backend and not used by the SDK.The option name will be used as provided. And must match the name format used by the backend (hint: use a snake\_case\_name).Raw option values can be any type supported by Firestore (for example: string, boolean, number, map, …). Value types not known to the SDK will be rejected.Values specified in rawOptions will take precedence over any options with the same name set by the SDK.rawOptions supports dot notation, if you want to override a nested option. | + +## StageOptions.rawOptions + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +An escape hatch to set options not known at SDK build time. These values will be passed directly to the Firestore backend and not used by the SDK. + +The option name will be used as provided. And must match the name format used by the backend (hint: use a snake\_case\_name). + +Raw option values can be any type supported by Firestore (for example: string, boolean, number, map, …). Value types not known to the SDK will be rejected. + +Values specified in rawOptions will take precedence over any options with the same name set by the SDK. + +`rawOptions` supports dot notation, if you want to override a nested option. + +Signature: + +```typescript +rawOptions?: { + [name: string]: unknown; + }; +``` diff --git a/docs-devsite/firestore_pipelines.timestamp.md b/docs-devsite/firestore_pipelines.timestamp.md new file mode 100644 index 0000000000..c81df6f992 --- /dev/null +++ b/docs-devsite/firestore_pipelines.timestamp.md @@ -0,0 +1,265 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Timestamp class +A `Timestamp` represents a point in time independent of any time zone or calendar, represented as seconds and fractions of seconds at nanosecond resolution in UTC Epoch time. + +It is encoded using the Proleptic Gregorian Calendar which extends the Gregorian calendar backwards to year one. It is encoded assuming all minutes are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. + +For examples and further specifications, refer to the [Timestamp definition](https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto). + +Signature: + +```typescript +export declare class Timestamp +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(seconds, nanoseconds)](./firestore_pipelines.timestamp.md#timestampconstructor) | | Creates a new timestamp. | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [nanoseconds](./firestore_pipelines.timestamp.md#timestampnanoseconds) | | number | The fractions of a second at nanosecond resolution.\* | +| [seconds](./firestore_pipelines.timestamp.md#timestampseconds) | | number | The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [fromDate(date)](./firestore_pipelines.timestamp.md#timestampfromdate) | static | Creates a new timestamp from the given date. | +| [fromJSON(json)](./firestore_pipelines.timestamp.md#timestampfromjson) | static | Builds a Timestamp instance from a JSON object created by [Timestamp.toJSON()](./firestore_.timestamp.md#timestamptojson). | +| [fromMillis(milliseconds)](./firestore_pipelines.timestamp.md#timestampfrommillis) | static | Creates a new timestamp from the given number of milliseconds. | +| [isEqual(other)](./firestore_pipelines.timestamp.md#timestampisequal) | | Returns true if this Timestamp is equal to the provided one. | +| [now()](./firestore_pipelines.timestamp.md#timestampnow) | static | Creates a new timestamp with the current date, with millisecond precision. | +| [toDate()](./firestore_pipelines.timestamp.md#timestamptodate) | | Converts a Timestamp to a JavaScript Date object. This conversion causes a loss of precision since Date objects only support millisecond precision. | +| [toJSON()](./firestore_pipelines.timestamp.md#timestamptojson) | | Returns a JSON-serializable representation of this Timestamp. | +| [toMillis()](./firestore_pipelines.timestamp.md#timestamptomillis) | | Converts a Timestamp to a numeric timestamp (in milliseconds since epoch). This operation causes a loss of precision. | +| [toString()](./firestore_pipelines.timestamp.md#timestamptostring) | | Returns a textual representation of this Timestamp. | +| [valueOf()](./firestore_pipelines.timestamp.md#timestampvalueof) | | Converts this object to a primitive string, which allows Timestamp objects to be compared using the >, <=, >= and > operators. | + +## Timestamp.(constructor) + +Creates a new timestamp. + +Signature: + +```typescript +constructor( + seconds: number, + nanoseconds: number); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| seconds | number | 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. | +| nanoseconds | number | 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. | + +## Timestamp.nanoseconds + +The fractions of a second at nanosecond resolution.\* + +Signature: + +```typescript +readonly nanoseconds: number; +``` + +## Timestamp.seconds + +The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. + +Signature: + +```typescript +readonly seconds: number; +``` + +## Timestamp.fromDate() + +Creates a new timestamp from the given date. + +Signature: + +```typescript +static fromDate(date: Date): Timestamp; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| date | Date | The date to initialize the Timestamp from. | + +Returns: + +[Timestamp](./firestore_.timestamp.md#timestamp_class) + +A new `Timestamp` representing the same point in time as the given date. + +## Timestamp.fromJSON() + +Builds a `Timestamp` instance from a JSON object created by [Timestamp.toJSON()](./firestore_.timestamp.md#timestamptojson). + +Signature: + +```typescript +static fromJSON(json: object): Timestamp; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| json | object | | + +Returns: + +[Timestamp](./firestore_.timestamp.md#timestamp_class) + +## Timestamp.fromMillis() + +Creates a new timestamp from the given number of milliseconds. + +Signature: + +```typescript +static fromMillis(milliseconds: number): Timestamp; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| milliseconds | number | Number of milliseconds since Unix epoch 1970-01-01T00:00:00Z. | + +Returns: + +[Timestamp](./firestore_.timestamp.md#timestamp_class) + +A new `Timestamp` representing the same point in time as the given number of milliseconds. + +## Timestamp.isEqual() + +Returns true if this `Timestamp` is equal to the provided one. + +Signature: + +```typescript +isEqual(other: Timestamp): boolean; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| other | [Timestamp](./firestore_.timestamp.md#timestamp_class) | The Timestamp to compare against. | + +Returns: + +boolean + +true if this `Timestamp` is equal to the provided one. + +## Timestamp.now() + +Creates a new timestamp with the current date, with millisecond precision. + +Signature: + +```typescript +static now(): Timestamp; +``` +Returns: + +[Timestamp](./firestore_.timestamp.md#timestamp_class) + +a new timestamp representing the current date. + +## Timestamp.toDate() + +Converts a `Timestamp` to a JavaScript `Date` object. This conversion causes a loss of precision since `Date` objects only support millisecond precision. + +Signature: + +```typescript +toDate(): Date; +``` +Returns: + +Date + +JavaScript `Date` object representing the same point in time as this `Timestamp`, with millisecond precision. + +## Timestamp.toJSON() + +Returns a JSON-serializable representation of this `Timestamp`. + +Signature: + +```typescript +toJSON(): { + seconds: number; + nanoseconds: number; + type: string; + }; +``` +Returns: + +{ seconds: number; nanoseconds: number; type: string; } + +## Timestamp.toMillis() + +Converts a `Timestamp` to a numeric timestamp (in milliseconds since epoch). This operation causes a loss of precision. + +Signature: + +```typescript +toMillis(): number; +``` +Returns: + +number + +The point in time corresponding to this timestamp, represented as the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z. + +## Timestamp.toString() + +Returns a textual representation of this `Timestamp`. + +Signature: + +```typescript +toString(): string; +``` +Returns: + +string + +## Timestamp.valueOf() + +Converts this object to a primitive string, which allows `Timestamp` objects to be compared using the `>`, `<=`, `>=` and `>` operators. + +Signature: + +```typescript +valueOf(): string; +``` +Returns: + +string + diff --git a/docs-devsite/firestore_pipelines.vectorvalue.md b/docs-devsite/firestore_pipelines.vectorvalue.md new file mode 100644 index 0000000000..8185645466 --- /dev/null +++ b/docs-devsite/firestore_pipelines.vectorvalue.md @@ -0,0 +1,101 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# VectorValue class +Represents a vector type in Firestore documents. Create an instance with [vector()](./firestore_.md#vector_0dbdaf2). + + VectorValue + +Signature: + +```typescript +export declare class VectorValue +``` + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [fromJSON(json)](./firestore_pipelines.vectorvalue.md#vectorvaluefromjson) | static | Builds a VectorValue instance from a JSON object created by [VectorValue.toJSON()](./firestore_.vectorvalue.md#vectorvaluetojson). | +| [isEqual(other)](./firestore_pipelines.vectorvalue.md#vectorvalueisequal) | | Returns true if the two VectorValue values have the same raw number arrays, returns false otherwise. | +| [toArray()](./firestore_pipelines.vectorvalue.md#vectorvaluetoarray) | | Returns a copy of the raw number array form of the vector. | +| [toJSON()](./firestore_pipelines.vectorvalue.md#vectorvaluetojson) | | Returns a JSON-serializable representation of this VectorValue instance. | + +## VectorValue.fromJSON() + +Builds a `VectorValue` instance from a JSON object created by [VectorValue.toJSON()](./firestore_.vectorvalue.md#vectorvaluetojson). + +Signature: + +```typescript +static fromJSON(json: object): VectorValue; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| json | object | a JSON object represention of a VectorValue instance. | + +Returns: + +[VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) + +an instance of [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) if the JSON object could be parsed. Throws a [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class) if an error occurs. + +## VectorValue.isEqual() + +Returns `true` if the two `VectorValue` values have the same raw number arrays, returns `false` otherwise. + +Signature: + +```typescript +isEqual(other: VectorValue): boolean; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| other | [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) | | + +Returns: + +boolean + +## VectorValue.toArray() + +Returns a copy of the raw number array form of the vector. + +Signature: + +```typescript +toArray(): number[]; +``` +Returns: + +number\[\] + +## VectorValue.toJSON() + +Returns a JSON-serializable representation of this `VectorValue` instance. + +Signature: + +```typescript +toJSON(): object; +``` +Returns: + +object + +a JSON representation of this object. + diff --git a/integration/firestore/gulpfile.js b/integration/firestore/gulpfile.js index 57dc674606..601c12b8dd 100644 --- a/integration/firestore/gulpfile.js +++ b/integration/firestore/gulpfile.js @@ -48,7 +48,8 @@ function copyTests() { testBase + '/integration/util/settings.ts', testBase + '/integration/util/testing_hooks_util.ts', testBase + '/util/equality_matcher.ts', - testBase + '/util/promise.ts' + testBase + '/util/promise.ts', + testBase + '/util/mocha_extensions.ts' ], { base: '../../packages/firestore' } ) @@ -69,6 +70,16 @@ if (typeof process === 'undefined') { ` ) ) + .pipe( + replace( + /** + * This regex is designed to match the Firebase import in our + * integration tests. + */ + /\s+from '\.(\.\/util)?\/pipeline_export';/, + ` from '${resolve(__dirname, './pipeline_export')}';` + ) + ) .pipe( /** * Fixing the project.json require to properly reference the file diff --git a/integration/firestore/package.json b/integration/firestore/package.json index b65148c5f5..5f99e9e60d 100644 --- a/integration/firestore/package.json +++ b/integration/firestore/package.json @@ -3,9 +3,10 @@ "version": "1.0.1", "private": true, "scripts": { - "build:deps": "lerna run --scope @firebase/'{app,firestore}' --include-dependencies build", - "build:persistence": "INCLUDE_FIRESTORE_PERSISTENCE=true gulp compile-tests", - "build:memory": "INCLUDE_FIRESTORE_PERSISTENCE=false gulp compile-tests", + "build:deps": "lerna run --scope @firebase/'{app,firestore}' --include-dependencies build && npm run typings:public", + "typings:public": "lerna run --scope @firebase/'{app,firestore}' --include-dependencies typings:public", + "build:persistence": "yarn typings:public && INCLUDE_FIRESTORE_PERSISTENCE=true gulp compile-tests", + "build:memory": "yarn typings:public && INCLUDE_FIRESTORE_PERSISTENCE=false gulp compile-tests", "karma:singlerun": "karma start", "prettier": "prettier --write '*.js' '*.ts'", "test:persistence": " yarn build:persistence; karma start", diff --git a/integration/firestore/pipeline_export.ts b/integration/firestore/pipeline_export.ts new file mode 100644 index 0000000000..15383ae558 --- /dev/null +++ b/integration/firestore/pipeline_export.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2025 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. + */ + +// This file replaces "packages/firestore/test/integration/util/pipeline_export" +// and depends on the minified sources. + +export * from '@firebase/firestore/pipelines'; diff --git a/package.json b/package.json index 39455ef116..49fe50d236 100644 --- a/package.json +++ b/package.json @@ -142,6 +142,7 @@ "postinstall-postinstall": "2.1.0", "prettier": "2.8.8", "protractor": "5.4.2", + "protobufjs-cli": "^1.1.3", "request": "2.88.2", "semver": "7.7.1", "simple-git": "3.27.0", diff --git a/packages/firebase/firestore/lite/pipelines/index.ts b/packages/firebase/firestore/lite/pipelines/index.ts new file mode 100644 index 0000000000..8ef0f25b90 --- /dev/null +++ b/packages/firebase/firestore/lite/pipelines/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2024 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. + */ + +export * from '@firebase/firestore/lite/pipelines'; diff --git a/packages/firebase/firestore/lite/pipelines/package.json b/packages/firebase/firestore/lite/pipelines/package.json new file mode 100644 index 0000000000..d0d28ab6af --- /dev/null +++ b/packages/firebase/firestore/lite/pipelines/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/firestore/lite/pipelines", + "main": "dist/pipelines.cjs.js", + "browser": "dist/esm/pipelines.esm.js", + "module": "dist/esm/pipelines.esm.js", + "typings": "dist/firestore/lite/pipelines/index.d.ts" +} diff --git a/packages/firebase/firestore/package.json b/packages/firebase/firestore/package.json index cf9bb8b2d7..4fe0292c9d 100644 --- a/packages/firebase/firestore/package.json +++ b/packages/firebase/firestore/package.json @@ -4,4 +4,4 @@ "browser": "dist/esm/index.esm.js", "module": "dist/esm/index.esm.js", "typings": "dist/firestore/index.d.ts" -} \ No newline at end of file +} diff --git a/packages/firebase/firestore/pipelines/index.cdn.ts b/packages/firebase/firestore/pipelines/index.cdn.ts new file mode 100644 index 0000000000..81e81b39d8 --- /dev/null +++ b/packages/firebase/firestore/pipelines/index.cdn.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2024 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. + */ + +export * from '@firebase/firestore'; + +import * as pipelines from '@firebase/firestore/pipelines'; +export { pipelines }; diff --git a/packages/firebase/firestore/pipelines/index.ts b/packages/firebase/firestore/pipelines/index.ts new file mode 100644 index 0000000000..be062f16e9 --- /dev/null +++ b/packages/firebase/firestore/pipelines/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2024 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. + */ + +export * from '@firebase/firestore/pipelines'; diff --git a/packages/firebase/firestore/pipelines/package.json b/packages/firebase/firestore/pipelines/package.json new file mode 100644 index 0000000000..b52d1749ce --- /dev/null +++ b/packages/firebase/firestore/pipelines/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/firestore/pipelines", + "main": "dist/pipelines.cjs.js", + "browser": "dist/esm/pipelines.esm.js", + "module": "dist/esm/pipelines.esm.js", + "typings": "dist/firestore/pipelines/index.d.ts" +} diff --git a/packages/firebase/gulpfile.js b/packages/firebase/gulpfile.js index 4ea24182af..e05345fbdf 100644 --- a/packages/firebase/gulpfile.js +++ b/packages/firebase/gulpfile.js @@ -21,7 +21,7 @@ const replace = require('gulp-replace'); const pkgJson = require('./package.json'); const files = pkgJson.components.map(component => { - const componentName = component.replace('/', '-'); + const componentName = component.replaceAll('/', '-'); return `firebase-${componentName}.js`; }); const FIREBASE_APP_URL = `https://www.gstatic.com/firebasejs/${pkgJson.version}/firebase-app.js`; diff --git a/packages/firebase/package.json b/packages/firebase/package.json index 69ca37b633..a6b0983474 100644 --- a/packages/firebase/package.json +++ b/packages/firebase/package.json @@ -131,6 +131,18 @@ }, "default": "./firestore/dist/esm/index.esm.js" }, + "./firestore/pipelines": { + "types": "./firestore/pipelines/dist/firestore/pipelines/index.d.ts", + "node": { + "require": "./firestore/pipelines/dist/pipelines.cjs.js", + "import": "./firestore/pipelines/dist/pipelines.mjs" + }, + "browser": { + "require": "./firestore/pipelines/dist/pipelines.cjs.js", + "import": "./firestore/pipelines/dist/esm/pipelines.esm.js" + }, + "default": "./firestore/pipelines/dist/esm/pipelines.esm.js" + }, "./firestore/lite": { "types": "./firestore/lite/dist/firestore/lite/index.d.ts", "node": { @@ -143,6 +155,18 @@ }, "default": "./firestore/lite/dist/esm/index.esm.js" }, + "./firestore/lite/pipelines": { + "types": "./firestore/lite/pipelines/dist/firestore/lite/pipelines/index.d.ts", + "node": { + "require": "./firestore/lite/pipelines/dist/pipelines.cjs.js", + "import": "./firestore/lite/pipelines/dist/pipelines.mjs" + }, + "browser": { + "require": "./firestore/lite/pipelines/dist/pipelines.cjs.js", + "import": "./firestore/lite/pipelines/dist/esm/pipelines.esm.js" + }, + "default": "./firestore/lite/pipelines/dist/esm/pipelines.esm.js" + }, "./functions": { "types": "./functions/dist/functions/index.d.ts", "node": { @@ -453,6 +477,8 @@ "functions", "firestore", "firestore/lite", + "firestore/pipelines", + "firestore/lite/pipelines", "installations", "storage", "performance", diff --git a/packages/firebase/rollup.config.js b/packages/firebase/rollup.config.js index f96ff01666..87b9f7c834 100644 --- a/packages/firebase/rollup.config.js +++ b/packages/firebase/rollup.config.js @@ -20,6 +20,7 @@ import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; import pkg from './package.json'; import { resolve } from 'path'; +import { existsSync } from 'fs'; import resolveModule from '@rollup/plugin-node-resolve'; import rollupTypescriptPlugin from 'rollup-plugin-typescript2'; import sourcemaps from 'rollup-plugin-sourcemaps'; @@ -149,10 +150,12 @@ const cdnBuilds = [ .map(component => { // It is needed for handling sub modules, for example firestore/lite which should produce firebase-firestore-lite.js // Otherwise, we will create a directory with '/' in the name. - const componentName = component.replace('/', '-'); + const componentName = component.replaceAll('/', '-'); return { - input: `${component}/index.ts`, + input: existsSync(`${component}/index.cdn.ts`) + ? `${component}/index.cdn.ts` + : `${component}/index.ts`, output: { file: `firebase-${componentName}.js`, sourcemap: true, diff --git a/packages/firestore/.eslintrc.js b/packages/firestore/.eslintrc.js index 5dd443333d..9ffb1d0279 100644 --- a/packages/firestore/.eslintrc.js +++ b/packages/firestore/.eslintrc.js @@ -24,7 +24,7 @@ module.exports = { tsconfigRootDir: __dirname }, plugins: ['import'], - ignorePatterns: ['compat/*'], + ignorePatterns: ['compat/*', 'pipelines.d.ts'], rules: { 'no-console': ['error', { allow: ['warn', 'error'] }], '@typescript-eslint/no-unused-vars': [ diff --git a/packages/firestore/api-extractor.json b/packages/firestore/api-extractor.json index ed10a0d62a..be1cec0170 100644 --- a/packages/firestore/api-extractor.json +++ b/packages/firestore/api-extractor.json @@ -4,13 +4,17 @@ * apiReport and d.ts rollup are handled by scripts/api-report.ts. */ "extends": "../../config/api-extractor.json", - // Point it to your entry point d.ts file. + // Point it to your entry point d.ts file. "mainEntryPointFilePath": "/dist/index.d.ts", "additionalEntryPoints": [ - { - "modulePath": "lite", - "filePath": "/dist/lite/index.d.ts" - } + { + "modulePath": "lite", + "filePath": "/dist/lite/index.d.ts" + }, + { + "modulePath": "pipelines", + "filePath": "/dist/pipelines.d.ts" + } ], "apiReport": { /** @@ -18,4 +22,4 @@ */ "enabled": false } -} \ No newline at end of file +} diff --git a/packages/firestore/externs.json b/packages/firestore/externs.json index c56b078ddd..ae68fe87be 100644 --- a/packages/firestore/externs.json +++ b/packages/firestore/externs.json @@ -17,7 +17,9 @@ "packages/app-check-interop-types/index.d.ts", "packages/auth-interop-types/index.d.ts", "packages/firestore/dist/lite/internal.d.ts", + "packages/firestore/dist/lite/internal.pipelines.d.ts", "packages/firestore/dist/internal.d.ts", + "packages/firestore/dist/internal.pipelines.d.ts", "packages/firestore-types/index.d.ts", "packages/firebase/compat/index.d.ts", "packages/component/dist/src/component.d.ts", diff --git a/packages/firestore/lite/pipelines/package.json b/packages/firestore/lite/pipelines/package.json new file mode 100644 index 0000000000..aff9ad6671 --- /dev/null +++ b/packages/firestore/lite/pipelines/package.json @@ -0,0 +1,14 @@ +{ + "name": "@firebase/firestore-lite-pipelines", + "description": "Pipelines for the lite Firestore SDK", + "main": "../../dist/lite/pipelines.node.cjs.js", + "main-esm": "../../dist/lite/pipelines.node.mjs", + "module": "../../dist/lite/pipelines.browser.esm.js", + "browser": "../../dist/lite/pipelines.browser.esm.js", + "react-native": "../../dist/lite/pipelines.rn.esm.js", + "typings": "./pipelines.d.ts", + "private": true, + "engines": { + "node": ">=20.0.0" + } +} diff --git a/packages/firestore/lite/pipelines/pipelines.d.ts b/packages/firestore/lite/pipelines/pipelines.d.ts new file mode 100644 index 0000000000..8133645665 --- /dev/null +++ b/packages/firestore/lite/pipelines/pipelines.d.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2024 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 { PipelineSource, Pipeline } from '../../dist/lite/pipelines'; + +// Augument the Firestore class with the pipeline() method. +// This is stripped from dist/lite/pipelines.d.ts during the build +// so it needs to be re-added here. +declare module '@firebase/firestore/lite' { + interface Firestore { + pipeline(): PipelineSource; + } +} + +export * from '../../dist/lite/pipelines'; diff --git a/packages/firestore/lite/pipelines/pipelines.ts b/packages/firestore/lite/pipelines/pipelines.ts new file mode 100644 index 0000000000..9e4f804701 --- /dev/null +++ b/packages/firestore/lite/pipelines/pipelines.ts @@ -0,0 +1,194 @@ +/** + * Firestore Lite Pipelines + * + * @remarks Firestore Lite is a small online-only SDK that allows read + * and write access to your Firestore database. All operations connect + * directly to the backend, and `onSnapshot()` APIs are not supported. + * @packageDocumentation + */ +/** + * @license + * Copyright 2024 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. + */ + +// External exports: ./index +// These external exports will be stripped from the dist/pipelines.d.ts file +// by the prune-dts script, in order to reduce type duplication. However, these +// types need to be exported here to ensure that api-extractor behaves +// correctly. If a type from api.ts is missing from this export, then +// api-extractor may rename it with a suffix `_#`, e.g. `YourType_2`. +export type { + Timestamp, + DocumentReference, + VectorValue, + GeoPoint, + FieldPath, + DocumentData, + Query, + Firestore, + FirestoreDataConverter, + WithFieldValue, + PartialWithFieldValue, + SetOptions, + QueryDocumentSnapshot, + Primitive, + FieldValue, + Bytes +} from '../index'; + +export { PipelineSource } from '../../src/lite-api/pipeline-source'; + +export { OneOf } from '../../src/util/types'; + +export { + PipelineResult, + PipelineSnapshot +} from '../../src/lite-api/pipeline-result'; + +export { Pipeline } from '../../src/lite-api/pipeline'; + +export { execute } from '../../src/lite-api/pipeline_impl'; + +export { + StageOptions, + CollectionStageOptions, + CollectionGroupStageOptions, + DatabaseStageOptions, + DocumentsStageOptions, + AddFieldsStageOptions, + RemoveFieldsStageOptions, + SelectStageOptions, + WhereStageOptions, + OffsetStageOptions, + LimitStageOptions, + DistinctStageOptions, + AggregateStageOptions, + FindNearestStageOptions, + ReplaceWithStageOptions, + SampleStageOptions, + UnionStageOptions, + UnnestStageOptions, + SortStageOptions +} from '../../src/lite-api/stage_options'; + +export { + Expression, + field, + and, + array, + constant, + add, + subtract, + multiply, + average, + substring, + count, + mapMerge, + mapRemove, + ifError, + isAbsent, + isError, + or, + divide, + map, + mod, + documentId, + equal, + notEqual, + lessThan, + countIf, + lessThanOrEqual, + greaterThan, + greaterThanOrEqual, + arrayConcat, + arrayContains, + arrayContainsAny, + arrayContainsAll, + arrayLength, + equalAny, + notEqualAny, + xor, + conditional, + not, + logicalMaximum, + logicalMinimum, + exists, + reverse, + byteLength, + charLength, + like, + regexContains, + regexMatch, + stringContains, + startsWith, + endsWith, + toLower, + toUpper, + trim, + stringConcat, + mapGet, + countAll, + minimum, + maximum, + cosineDistance, + dotProduct, + euclideanDistance, + vectorLength, + unixMicrosToTimestamp, + timestampToUnixMicros, + unixMillisToTimestamp, + timestampToUnixMillis, + unixSecondsToTimestamp, + timestampToUnixSeconds, + timestampAdd, + timestampSubtract, + ascending, + descending, + arrayGet, + abs, + sum, + countDistinct, + ceil, + floor, + exp, + pow, + round, + collectionId, + ln, + log, + sqrt, + stringReverse, + log10, + concat, + currentTimestamp, + ifAbsent, + join, + length, + arraySum, + split, + timestampTruncate, + type, + AliasedExpression, + Field, + Constant, + FunctionExpression, + Ordering, + ExpressionType, + AliasedAggregate, + Selectable, + BooleanExpression, + AggregateFunction, + TimeGranularity +} from '../../src/lite-api/expressions'; diff --git a/packages/firestore/package.json b/packages/firestore/package.json index 6c3a2eefaa..b816b1bcc9 100644 --- a/packages/firestore/package.json +++ b/packages/firestore/package.json @@ -8,6 +8,7 @@ "author": "Firebase (https://firebase.google.com/)", "scripts": { "bundle": "rollup -c", + "compile": "tsc --emitDeclarationOnly --declaration -p tsconfig.json", "prebuild": "tsc --emitDeclarationOnly --declaration -p tsconfig.json; yarn api-report", "build": "run-p --npm-path npm build:lite build:main", "build:release": "yarn build && yarn typings:public", @@ -50,9 +51,11 @@ "test:minified": "(cd ../../integration/firestore ; yarn test)", "trusted-type-check": "tsec -p tsconfig.json --noEmit", "api-report:main": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package firestore --packageRoot . --typescriptDts ./dist/firestore/src/index.d.ts --rollupDts ./dist/private.d.ts --untrimmedRollupDts ./dist/internal.d.ts --publicDts ./dist/index.d.ts", + "api-report:pipelines": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package firestore-pipelines --packageRoot . --typescriptDts ./dist/firestore/pipelines/pipelines.d.ts --rollupDts ./dist/private.pipelines.d.ts --untrimmedRollupDts ./dist/internal.pipelines.d.ts --publicDts ./dist/pipelines.d.ts --otherExportsPublicDtsFiles ./dist/index.d.ts", "api-report:lite": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package firestore-lite --packageRoot . --typescriptDts ./dist/firestore/lite/index.d.ts --rollupDts ./dist/lite/private.d.ts --untrimmedRollupDts ./dist/lite/internal.d.ts --publicDts ./dist/lite/index.d.ts", + "api-report:lite:pipelines": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package firestore-lite-pipelines --packageRoot . --typescriptDts ./dist/firestore/lite/pipelines/pipelines.d.ts --rollupDts ./dist/lite/private.pipelines.d.ts --untrimmedRollupDts ./dist/lite/internal.pipelines.d.ts --publicDts ./dist/lite/pipelines.d.ts --otherExportsPublicDtsFiles ./dist/lite/index.d.ts", "api-report:api-json": "rm -rf temp && api-extractor run --local --verbose", - "api-report": "run-s --npm-path npm api-report:main api-report:lite && yarn api-report:api-json", + "api-report": "run-s --npm-path npm api-report:main api-report:pipelines api-report:lite api-report:lite:pipelines && yarn api-report:api-json", "doc": "api-documenter markdown --input temp --output docs", "typings:public": "node ../../scripts/build/use_typings.js ./dist/index.d.ts", "assertion-id:check": "ts-node scripts/assertion-id-tool.ts --dir=src --check", @@ -82,11 +85,37 @@ }, "react-native": "./dist/lite/index.rn.esm.js", "browser": { - "require": "./dist/lite/index.cjs.js", + "require": "./dist/lite/index.browser.cjs.js", "import": "./dist/lite/index.browser.esm.js" }, "default": "./dist/lite/index.browser.esm.js" }, + "./lite/pipelines": { + "types": "./dist/lite/pipelines.d.ts", + "node": { + "require": "./dist/lite/pipelines.node.cjs.js", + "import": "./dist/lite/pipelines.node.mjs" + }, + "react-native": "./dist/lite/pipelines.rn.esm.js", + "browser": { + "require": "./dist/lite/pipelines.browser.cjs.js", + "import": "./dist/lite/pipelines.browser.esm.js" + }, + "default": "./dist/lite/pipelines.browser.esm.js" + }, + "./pipelines": { + "types": "./pipelines/pipelines.d.ts", + "node": { + "require": "./dist/pipelines.node.cjs.js", + "import": "./dist/pipelines.node.mjs" + }, + "react-native": "./dist/index.rn.js", + "browser": { + "require": "./dist/pipelines.cjs.js", + "import": "./dist/pipelines.esm.js" + }, + "default": "./dist/pipelines.esm.js" + }, "./package.json": "./package.json" }, "main": "dist/index.node.cjs.js", @@ -97,7 +126,11 @@ "license": "Apache-2.0", "files": [ "dist", - "lite/package.json" + "lite/package.json", + "pipelines/package.json", + "pipelines/pipelines.d.ts", + "lite/pipelines/package.json", + "lite/pipelines/pipelines.d.ts" ], "dependencies": { "@firebase/component": "0.7.0", diff --git a/packages/firestore/pipelines/package.json b/packages/firestore/pipelines/package.json new file mode 100644 index 0000000000..aaf410ce36 --- /dev/null +++ b/packages/firestore/pipelines/package.json @@ -0,0 +1,14 @@ +{ + "name": "@firebase/firestore-pipelines", + "description": "pipelines", + "main": "../dist/pipelines.node.cjs.js", + "main-esm": "../dist/pipelines.node.mjs", + "module": "../dist/pipelines.esm.js", + "browser": "../dist/pipelines.esm.js", + "react-native": "../dist/pipelines.rn.js", + "typings": "./pipelines.d.ts", + "private": true, + "engines": { + "node": ">=20.0.0" + } +} diff --git a/packages/firestore/pipelines/pipelines.d.ts b/packages/firestore/pipelines/pipelines.d.ts new file mode 100644 index 0000000000..3458798533 --- /dev/null +++ b/packages/firestore/pipelines/pipelines.d.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2024 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 { PipelineSource, Pipeline } from '../dist/pipelines'; + +// Augment the Firestore and Query classes with the pipeline() method. +// This is stripped from dist/lite/pipelines.d.ts during the build +// so it needs to be re-added here. +declare module '@firebase/firestore' { + interface Firestore { + pipeline(): PipelineSource; + } +} + +export * from '../dist/pipelines'; diff --git a/packages/firestore/pipelines/pipelines.node.ts b/packages/firestore/pipelines/pipelines.node.ts new file mode 100644 index 0000000000..fc0e91de0f --- /dev/null +++ b/packages/firestore/pipelines/pipelines.node.ts @@ -0,0 +1,18 @@ +/** + * @license + * 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. + */ + +export * from '../src/api_pipelines'; diff --git a/packages/firestore/pipelines/pipelines.rn.ts b/packages/firestore/pipelines/pipelines.rn.ts new file mode 100644 index 0000000000..d5d4597190 --- /dev/null +++ b/packages/firestore/pipelines/pipelines.rn.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2024 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. + */ + +export * from '../src/api_pipelines'; diff --git a/packages/firestore/pipelines/pipelines.ts b/packages/firestore/pipelines/pipelines.ts new file mode 100644 index 0000000000..b056059adf --- /dev/null +++ b/packages/firestore/pipelines/pipelines.ts @@ -0,0 +1,51 @@ +/** + * Cloud Firestore + * + * @packageDocumentation + */ + +/** + * @license + * Copyright 2024 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. + */ + +// External exports: ./api +// These external exports will be stripped from the dist/pipelines.d.ts file +// by the prune-dts script, in order to reduce type duplication. However, these +// types need to be exported here to ensure that api-extractor behaves +// correctly. If a type from api.ts is missing from this export, then +// api-extractor may rename it with a suffix `_#`, e.g. `YourType_2`. +export type { + Timestamp, + DocumentReference, + VectorValue, + GeoPoint, + FieldPath, + DocumentData, + Query, + Firestore, + FirestoreDataConverter, + WithFieldValue, + PartialWithFieldValue, + SetOptions, + QueryDocumentSnapshot, + SnapshotOptions, + Primitive, + FieldValue, + SnapshotMetadata, + Bytes +} from '../src/api'; + +export * from '../src/api_pipelines'; diff --git a/packages/firestore/rollup.config.js b/packages/firestore/rollup.config.js index 5fa616bca8..646ed038f0 100644 --- a/packages/firestore/rollup.config.js +++ b/packages/firestore/rollup.config.js @@ -26,6 +26,7 @@ import tmp from 'tmp'; import typescript from 'typescript'; import { generateBuildTargetReplaceConfig } from '../../scripts/build/rollup_replace_build_target'; +import { replaceDeclareModule } from '../../scripts/build/rollup_replace_declare_module'; import pkg from './package.json'; import tsconfig from './tsconfig.json'; @@ -64,9 +65,11 @@ const allBuilds = [ // this is an intermediate build used to generate the actual esm and cjs builds // which add build target reporting { - input: './src/index.node.ts', + input: ['./src/index.node.ts', './pipelines/pipelines.node.ts'], output: { - file: pkg['main-esm'], + dir: 'dist/intermediate', + entryFileNames: '[name].mjs', + chunkFileNames: 'common-[hash].node.mjs', format: 'es', sourcemap: true }, @@ -79,9 +82,14 @@ const allBuilds = [ }, // Node CJS build { - input: pkg['main-esm'], + input: [ + 'dist/intermediate/index.node.mjs', + 'dist/intermediate/pipelines.node.mjs' + ], output: { - file: pkg.main, + dir: 'dist/', + entryFileNames: '[name].cjs.js', + chunkFileNames: 'common-[hash].node.cjs.js', format: 'cjs', sourcemap: true }, @@ -106,9 +114,14 @@ const allBuilds = [ }, // Node ESM build with build target reporting { - input: pkg['main-esm'], + input: [ + 'dist/intermediate/index.node.mjs', + 'dist/intermediate/pipelines.node.mjs' + ], output: { - file: pkg['main-esm'], + dir: 'dist/', + entryFileNames: '[name].mjs', + chunkFileNames: 'common-[hash].node.mjs', format: 'es', sourcemap: true }, @@ -125,9 +138,11 @@ const allBuilds = [ // this is an intermediate build used to generate the actual esm and cjs builds // which add build target reporting { - input: './src/index.ts', + input: ['./src/index.ts', './pipelines/pipelines.ts'], output: { - file: pkg.browser, + dir: 'dist/intermediate', + entryFileNames: '[name].js', + chunkFileNames: 'common-[hash].js', format: 'es', sourcemap: true }, @@ -139,10 +154,12 @@ const allBuilds = [ }, // Convert es2020 build to cjs { - input: pkg['browser'], + input: ['dist/intermediate/index.js', 'dist/intermediate/pipelines.js'], output: [ { - file: './dist/index.cjs.js', + dir: 'dist/', + entryFileNames: '[name].cjs.js', + chunkFileNames: 'common-[hash].cjs.js', format: 'cjs', sourcemap: true } @@ -158,10 +175,12 @@ const allBuilds = [ }, // es2020 build with build target reporting { - input: pkg['browser'], + input: ['dist/intermediate/index.js', 'dist/intermediate/pipelines.js'], output: [ { - file: pkg['browser'], + dir: 'dist/', + entryFileNames: '[name].esm.js', + chunkFileNames: 'common-[hash].esm.js', format: 'es', sourcemap: true } @@ -177,9 +196,11 @@ const allBuilds = [ }, // RN build { - input: './src/index.rn.ts', + input: ['./src/index.rn.ts', './pipelines/pipelines.rn.ts'], output: { - file: pkg['react-native'], + dir: 'dist/', + entryFileNames: '[name].js', + chunkFileNames: 'common-[hash].rn.js', format: 'es', sourcemap: true }, @@ -194,7 +215,7 @@ const allBuilds = [ } }, { - input: 'dist/firestore/src/index.d.ts', + input: 'dist/firestore/src/global.d.ts', output: { file: 'dist/firestore/src/global_index.d.ts', format: 'es' @@ -202,7 +223,17 @@ const allBuilds = [ plugins: [ dts({ respectExternal: true - }) + }), + + // The global.d.ts input file will include + // a `declare module './database' { ... }` block. This block + // was not removed in the build, and the module + // './database' is not known in context of the global.d.ts file. + // Use the declareModuleReplacePlugin to replace: + // `declare module './database' { Y }` + // with the contents of the block: + // `Y` + replaceDeclareModule('global_index.d.ts', './database') ] } ]; diff --git a/packages/firestore/rollup.config.lite.js b/packages/firestore/rollup.config.lite.js index 5ea2225f36..6bf9297e4d 100644 --- a/packages/firestore/rollup.config.lite.js +++ b/packages/firestore/rollup.config.lite.js @@ -56,9 +56,11 @@ const allBuilds = [ // this is an intermediate build used to generate the actual esm and cjs builds // which add build target reporting { - input: './lite/index.ts', + input: ['./lite/index.ts', './lite/pipelines/pipelines.ts'], output: { - file: path.resolve('./lite', pkg['main-esm']), + dir: 'dist/intermediate/lite/', + entryFileNames: '[name].node.mjs', + chunkFileNames: 'common-[hash].node.mjs', format: 'es', sourcemap: true }, @@ -77,9 +79,14 @@ const allBuilds = [ }, // Node CJS build { - input: path.resolve('./lite', pkg['main-esm']), + input: [ + 'dist/intermediate/lite/index.node.mjs', + 'dist/intermediate/lite/pipelines.node.mjs' + ], output: { - file: path.resolve('./lite', pkg.main), + dir: 'dist/lite/', + entryFileNames: '[name].cjs.js', + chunkFileNames: 'common-[hash].node.cjs.js', format: 'cjs', sourcemap: true }, @@ -102,9 +109,14 @@ const allBuilds = [ }, // Node ESM build { - input: path.resolve('./lite', pkg['main-esm']), + input: [ + 'dist/intermediate/lite/index.node.mjs', + 'dist/intermediate/lite/pipelines.node.mjs' + ], output: { - file: path.resolve('./lite', pkg['main-esm']), + dir: 'dist/lite/', + entryFileNames: '[name].mjs', + chunkFileNames: 'common-[hash].node.mjs', format: 'es', sourcemap: true }, @@ -121,9 +133,11 @@ const allBuilds = [ // this is an intermediate build used to generate the actual esm and cjs builds // which add build target reporting { - input: './lite/index.ts', + input: ['./lite/index.ts', './lite/pipelines/pipelines.ts'], output: { - file: path.resolve('./lite', pkg.browser), + dir: 'dist/intermediate/lite/', + entryFileNames: '[name].browser.js', + chunkFileNames: 'common-[hash].browser.js', format: 'es', sourcemap: true }, @@ -142,10 +156,15 @@ const allBuilds = [ }, // Convert es2020 build to CJS { - input: path.resolve('./lite', pkg.browser), + input: [ + 'dist/intermediate/lite/index.browser.js', + 'dist/intermediate/lite/pipelines.browser.js' + ], output: [ { - file: './dist/lite/index.cjs.js', + dir: 'dist/lite/', + entryFileNames: '[name].cjs.js', + chunkFileNames: 'common-[hash].cjs.js', format: 'es', sourcemap: true } @@ -161,10 +180,15 @@ const allBuilds = [ }, // Browser es2020 build { - input: path.resolve('./lite', pkg.browser), + input: [ + 'dist/intermediate/lite/index.browser.js', + 'dist/intermediate/lite/pipelines.browser.js' + ], output: [ { - file: path.resolve('./lite', pkg.browser), + dir: 'dist/lite/', + entryFileNames: '[name].esm.js', + chunkFileNames: 'common-[hash].esm.js', format: 'es', sourcemap: true } @@ -180,9 +204,11 @@ const allBuilds = [ }, // RN build { - input: './lite/index.ts', + input: ['./lite/index.ts', './lite/pipelines/pipelines.ts'], output: { - file: path.resolve('./lite', pkg['react-native']), + dir: 'dist/lite/', + entryFileNames: '[name].rn.esm.js', + chunkFileNames: 'common-[hash].rn.esm.js', format: 'es', sourcemap: true }, diff --git a/packages/firestore/src/api/aggregate.ts b/packages/firestore/src/api/aggregate.ts index f0e2c1e1dc..453f9e0a84 100644 --- a/packages/firestore/src/api/aggregate.ts +++ b/packages/firestore/src/api/aggregate.ts @@ -15,17 +15,21 @@ * limitations under the License. */ -import { AggregateField, AggregateSpec, DocumentData, Query } from '../api'; import { AggregateImpl } from '../core/aggregate'; import { firestoreClientRunAggregateQuery } from '../core/firestore_client'; import { count } from '../lite-api/aggregate'; -import { AggregateQuerySnapshot } from '../lite-api/aggregate_types'; +import { + AggregateField, + AggregateQuerySnapshot, + AggregateSpec +} from '../lite-api/aggregate_types'; +import { DocumentData, Query } from '../lite-api/reference'; import { ApiClientObjectMap, Value } from '../protos/firestore_proto_api'; import { cast } from '../util/input_validation'; import { mapToArray } from '../util/obj'; import { ensureFirestoreConfigured, Firestore } from './database'; -import { ExpUserDataWriter } from './reference_impl'; +import { ExpUserDataWriter } from './user_data_writer'; export { aggregateQuerySnapshotEqual, diff --git a/packages/firestore/src/api/database.ts b/packages/firestore/src/api/database.ts index a2feb19507..f894ddce03 100644 --- a/packages/firestore/src/api/database.ts +++ b/packages/firestore/src/api/database.ts @@ -302,6 +302,7 @@ export function configureFirestore(firestore: Firestore): void { firestore._databaseId, firestore._app?.options.appId || '', firestore._persistenceKey, + firestore._app?.options.apiKey, settings ); if (!firestore._componentsProvider) { diff --git a/packages/firestore/src/api/parse_context.ts b/packages/firestore/src/api/parse_context.ts index ce3c221f66..2381bcff4c 100644 --- a/packages/firestore/src/api/parse_context.ts +++ b/packages/firestore/src/api/parse_context.ts @@ -16,8 +16,53 @@ */ import { DatabaseId } from '../core/database_info'; +import { UserDataSource } from '../lite-api/user_data_reader'; +import { DocumentKey } from '../model/document_key'; +import { FieldTransform } from '../model/mutation'; +import { FieldPath as InternalFieldPath } from '../model/path'; +import { JsonProtoSerializer } from '../remote/serializer'; +import { FirestoreError } from '../util/error'; + +/** Contains the settings that are mutated as we parse user data. */ +export interface ContextSettings { + /** Indicates what kind of API method this data came from. */ + readonly dataSource: UserDataSource; + /** The name of the method the user called to create the ParseContext. */ + readonly methodName: string; + /** The document the user is attempting to modify, if that applies. */ + readonly targetDoc?: DocumentKey; + /** + * A path within the object being parsed. This could be an empty path (in + * which case the context represents the root of the data being parsed), or a + * nonempty path (indicating the context represents a nested location within + * the data). + */ + readonly path?: InternalFieldPath; + /** + * Whether or not this context corresponds to an element of an array. + * If not set, elements are treated as if they were outside of arrays. + */ + readonly arrayElement?: boolean; + /** + * Whether or not a converter was specified in this context. If true, error + * messages will reference the converter when invalid data is provided. + */ + readonly hasConverter?: boolean; +} export interface ParseContext { + readonly settings: ContextSettings; readonly databaseId: DatabaseId; + readonly serializer: JsonProtoSerializer; readonly ignoreUndefinedProperties: boolean; + fieldTransforms: FieldTransform[]; + fieldMask: InternalFieldPath[]; + get path(): InternalFieldPath | undefined; + get dataSource(): UserDataSource; + contextWith(configuration: Partial): ParseContext; + childContextForField(field: string): ParseContext; + childContextForFieldPath(field: InternalFieldPath): ParseContext; + childContextForArray(index: number): ParseContext; + createError(reason: string): FirestoreError; + contains(fieldPath: InternalFieldPath): boolean; } diff --git a/packages/firestore/src/api/pipeline.ts b/packages/firestore/src/api/pipeline.ts new file mode 100644 index 0000000000..311c3a59c4 --- /dev/null +++ b/packages/firestore/src/api/pipeline.ts @@ -0,0 +1,47 @@ +/** + * @license + * Copyright 2024 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 { Pipeline as LitePipeline } from '../lite-api/pipeline'; +import { Stage } from '../lite-api/stage'; +import { UserDataReader } from '../lite-api/user_data_reader'; +import { AbstractUserDataWriter } from '../lite-api/user_data_writer'; + +import { Firestore } from './database'; + +/** + * @beta + */ +export class Pipeline extends LitePipeline { + /** + * @internal + * @private + * @param db + * @param userDataReader + * @param userDataWriter + * @param stages + * @param converter + * @protected + */ + protected newPipeline( + db: Firestore, + userDataReader: UserDataReader, + userDataWriter: AbstractUserDataWriter, + stages: Stage[] + ): Pipeline { + return new Pipeline(db, userDataReader, userDataWriter, stages); + } +} diff --git a/packages/firestore/src/api/pipeline_impl.ts b/packages/firestore/src/api/pipeline_impl.ts new file mode 100644 index 0000000000..e5d650847d --- /dev/null +++ b/packages/firestore/src/api/pipeline_impl.ts @@ -0,0 +1,216 @@ +/** + * @license + * Copyright 2024 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 { Pipeline } from '../api/pipeline'; +import { firestoreClientExecutePipeline } from '../core/firestore_client'; +import { + StructuredPipeline, + StructuredPipelineOptions +} from '../core/structured_pipeline'; +import { Pipeline as LitePipeline } from '../lite-api/pipeline'; +import { PipelineResult, PipelineSnapshot } from '../lite-api/pipeline-result'; +import { PipelineSource } from '../lite-api/pipeline-source'; +import { PipelineExecuteOptions } from '../lite-api/pipeline_options'; +import { Stage } from '../lite-api/stage'; +import { + newUserDataReader, + UserDataReader, + UserDataSource +} from '../lite-api/user_data_reader'; +import { cast } from '../util/input_validation'; + +import { ensureFirestoreConfigured, Firestore } from './database'; +import { DocumentReference } from './reference'; +import { ExpUserDataWriter } from './user_data_writer'; + +declare module './database' { + /** + * @beta + * Creates and returns a new PipelineSource, which allows specifying the source stage of a {@link Pipeline}. + * + * @example + * ``` + * let myPipeline: Pipeline = firestore.pipeline().collection('books'); + * ``` + */ + interface Firestore { + pipeline(): PipelineSource; + } +} + +/** + * @beta + * Executes a pipeline and returns a Promise to represent the asynchronous operation. + * + * The returned Promise can be used to track the progress of the pipeline execution + * and retrieve the results (or handle any errors) asynchronously. + * + * The pipeline results are returned as a {@link PipelineSnapshot} that contains + * a list of {@link PipelineResult} objects. Each {@link PipelineResult} typically + * represents a single key/value map that has passed through all the + * stages of the pipeline, however this might differ depending on the stages involved in the + * pipeline. For example: + * + *

    + *
  • If there are no stages or only transformation stages, each {@link PipelineResult} + * represents a single document.
  • + *
  • If there is an aggregation, only a single {@link PipelineResult} is returned, + * representing the aggregated results over the entire dataset .
  • + *
  • If there is an aggregation stage with grouping, each {@link PipelineResult} represents a + * distinct group and its associated aggregated values.
  • + *
+ * + *

Example: + * + * ```typescript + * const snapshot: PipelineSnapshot = await execute(firestore.pipeline().collection("books") + * .where(gt(field("rating"), 4.5)) + * .select("title", "author", "rating")); + * + * const results: PipelineResults = snapshot.results; + * ``` + * + * @param pipeline The pipeline to execute. + * @return A Promise representing the asynchronous pipeline execution. + */ +export function execute(pipeline: LitePipeline): Promise; +/** + * @beta + * Executes a pipeline and returns a Promise to represent the asynchronous operation. + * + * The returned Promise can be used to track the progress of the pipeline execution + * and retrieve the results (or handle any errors) asynchronously. + * + * The pipeline results are returned as a {@link PipelineSnapshot} that contains + * a list of {@link PipelineResult} objects. Each {@link PipelineResult} typically + * represents a single key/value map that has passed through all the + * stages of the pipeline, however this might differ depending on the stages involved in the + * pipeline. For example: + * + *

    + *
  • If there are no stages or only transformation stages, each {@link PipelineResult} + * represents a single document.
  • + *
  • If there is an aggregation, only a single {@link PipelineResult} is returned, + * representing the aggregated results over the entire dataset .
  • + *
  • If there is an aggregation stage with grouping, each {@link PipelineResult} represents a + * distinct group and its associated aggregated values.
  • + *
+ * + *

Example: + * + * ```typescript + * const snapshot: PipelineSnapshot = await execute(firestore.pipeline().collection("books") + * .where(gt(field("rating"), 4.5)) + * .select("title", "author", "rating")); + * + * const results: PipelineResults = snapshot.results; + * ``` + * + * @param options Specifies the pipeline to execute and other options for execute. + * @return A Promise representing the asynchronous pipeline execution. + */ +export function execute( + options: PipelineExecuteOptions +): Promise; +export function execute( + pipelineOrOptions: LitePipeline | PipelineExecuteOptions +): Promise { + const options: PipelineExecuteOptions = !( + pipelineOrOptions instanceof LitePipeline + ) + ? pipelineOrOptions + : { + pipeline: pipelineOrOptions + }; + + const { pipeline, rawOptions, ...rest } = options; + + const firestore = cast(pipeline._db, Firestore); + const client = ensureFirestoreConfigured(firestore); + + const udr = new UserDataReader( + firestore._databaseId, + /* ignoreUndefinedProperties */ true + ); + const context = udr.createContext(UserDataSource.Argument, 'execute'); + + const structuredPipelineOptions = new StructuredPipelineOptions( + rest, + rawOptions + ); + structuredPipelineOptions._readUserData(context); + + const structuredPipeline: StructuredPipeline = new StructuredPipeline( + pipeline, + structuredPipelineOptions + ); + + return firestoreClientExecutePipeline(client, structuredPipeline).then( + result => { + // Get the execution time from the first result. + // firestoreClientExecutePipeline returns at least one PipelineStreamElement + // even if the returned document set is empty. + const executionTime = + result.length > 0 ? result[0].executionTime?.toTimestamp() : undefined; + + const docs = result + // Currently ignore any response from ExecutePipeline that does + // not contain any document data in the `fields` property. + .filter(element => !!element.fields) + .map( + element => + new PipelineResult( + pipeline._userDataWriter, + element.fields!, + element.key?.path + ? new DocumentReference(firestore, null, element.key) + : undefined, + element.createTime?.toTimestamp(), + element.updateTime?.toTimestamp() + ) + ); + + return new PipelineSnapshot(pipeline, docs, executionTime); + } + ); +} + +/** + * @beta + * Creates and returns a new PipelineSource, which allows specifying the source stage of a {@link Pipeline}. + * + * @example + * ``` + * let myPipeline: Pipeline = firestore.pipeline().collection('books'); + * ``` + */ +// Augment the Firestore class with the pipeline() factory method +Firestore.prototype.pipeline = function (): PipelineSource { + const userDataReader = newUserDataReader(this); + return new PipelineSource( + this._databaseId, + userDataReader, + (stages: Stage[]) => { + return new Pipeline( + this, + userDataReader, + new ExpUserDataWriter(this), + stages + ); + } + ); +}; diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index 8fa21a13e6..4283453d81 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -37,7 +37,6 @@ import { } from '../core/firestore_client'; import { newQueryForPath, Query as InternalQuery } from '../core/query'; import { ViewSnapshot } from '../core/view_snapshot'; -import { Bytes } from '../lite-api/bytes'; import { FieldPath } from '../lite-api/field_path'; import { validateHasExplicitOrderByForLimitToLast } from '../lite-api/query'; import { @@ -59,11 +58,9 @@ import { parseUpdateData, parseUpdateVarargs } from '../lite-api/user_data_reader'; -import { AbstractUserDataWriter } from '../lite-api/user_data_writer'; import { DocumentKey } from '../model/document_key'; import { DeleteMutation, Mutation, Precondition } from '../model/mutation'; import { debugAssert } from '../util/assert'; -import { ByteString } from '../util/byte_string'; import { Code, FirestoreError } from '../util/error'; import { cast } from '../util/input_validation'; @@ -74,6 +71,7 @@ import { QuerySnapshot, SnapshotMetadata } from './snapshot'; +import { ExpUserDataWriter } from './user_data_writer'; /** * An options object that can be passed to {@link (onSnapshot:1)} and {@link @@ -130,21 +128,6 @@ export function getDoc( ).then(snapshot => convertToDocSnapshot(firestore, reference, snapshot)); } -export class ExpUserDataWriter extends AbstractUserDataWriter { - constructor(protected firestore: Firestore) { - super(); - } - - protected convertBytes(bytes: ByteString): Bytes { - return new Bytes(bytes); - } - - protected convertReference(name: string): DocumentReference { - const key = this.convertDocumentKey(name, this.firestore._databaseId); - return new DocumentReference(this.firestore, /* converter= */ null, key); - } -} - /** * Reads the document referred to by this `DocumentReference` from cache. * Returns an error if the document is not currently cached. diff --git a/packages/firestore/src/api/snapshot.ts b/packages/firestore/src/api/snapshot.ts index c82add0642..86e075a4ca 100644 --- a/packages/firestore/src/api/snapshot.ts +++ b/packages/firestore/src/api/snapshot.ts @@ -31,10 +31,12 @@ import { import { LiteUserDataWriter } from '../lite-api/reference_impl'; import { DocumentSnapshot as LiteDocumentSnapshot, - fieldPathFromArgument, FirestoreDataConverter as LiteFirestoreDataConverter } from '../lite-api/snapshot'; -import { UntypedFirestoreDataConverter } from '../lite-api/user_data_reader'; +import { + fieldPathFromArgument, + UntypedFirestoreDataConverter +} from '../lite-api/user_data_reader'; import { AbstractUserDataWriter } from '../lite-api/user_data_writer'; import { fromBundledQuery } from '../local/local_serializer'; import { documentKeySet } from '../model/collections'; diff --git a/packages/firestore/src/api/transaction.ts b/packages/firestore/src/api/transaction.ts index 955866f19b..8f83f52718 100644 --- a/packages/firestore/src/api/transaction.ts +++ b/packages/firestore/src/api/transaction.ts @@ -28,9 +28,9 @@ import { validateReference } from '../lite-api/write_batch'; import { cast } from '../util/input_validation'; import { ensureFirestoreConfigured, Firestore } from './database'; -import { ExpUserDataWriter } from './reference_impl'; import { DocumentSnapshot, SnapshotMetadata } from './snapshot'; import { TransactionOptions } from './transaction_options'; +import { ExpUserDataWriter } from './user_data_writer'; /** * A reference to a transaction. diff --git a/packages/firestore/src/api/user_data_writer.ts b/packages/firestore/src/api/user_data_writer.ts new file mode 100644 index 0000000000..3567f72cd9 --- /dev/null +++ b/packages/firestore/src/api/user_data_writer.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright 2024 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 { Bytes } from '../lite-api/bytes'; +import { DocumentReference } from '../lite-api/reference'; +import { AbstractUserDataWriter } from '../lite-api/user_data_writer'; +import { ByteString } from '../util/byte_string'; + +import { Firestore } from './database'; + +export class ExpUserDataWriter extends AbstractUserDataWriter { + constructor(protected firestore: Firestore) { + super(); + } + + protected convertBytes(bytes: ByteString): Bytes { + return new Bytes(bytes); + } + + protected convertReference(name: string): DocumentReference { + const key = this.convertDocumentKey(name, this.firestore._databaseId); + return new DocumentReference(this.firestore, /* converter= */ null, key); + } +} diff --git a/packages/firestore/src/api_pipelines.ts b/packages/firestore/src/api_pipelines.ts new file mode 100644 index 0000000000..693b7f5468 --- /dev/null +++ b/packages/firestore/src/api_pipelines.ts @@ -0,0 +1,165 @@ +/** + * @license + * 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. + */ + +export { PipelineSource } from './lite-api/pipeline-source'; + +export { OneOf } from './util/types'; + +export { + PipelineResult, + PipelineSnapshot, + pipelineResultEqual +} from './lite-api/pipeline-result'; + +export { Pipeline } from './api/pipeline'; + +export { execute } from './api/pipeline_impl'; + +export { PipelineExecuteOptions } from './lite-api/pipeline_options'; + +export { + StageOptions, + CollectionStageOptions, + CollectionGroupStageOptions, + DatabaseStageOptions, + DocumentsStageOptions, + AddFieldsStageOptions, + RemoveFieldsStageOptions, + SelectStageOptions, + WhereStageOptions, + OffsetStageOptions, + LimitStageOptions, + DistinctStageOptions, + AggregateStageOptions, + FindNearestStageOptions, + ReplaceWithStageOptions, + SampleStageOptions, + UnionStageOptions, + UnnestStageOptions, + SortStageOptions +} from './lite-api/stage_options'; + +export { + field, + constant, + add, + subtract, + multiply, + divide, + mod, + equal, + notEqual, + lessThan, + lessThanOrEqual, + greaterThan, + greaterThanOrEqual, + arrayConcat, + arrayContains, + arrayContainsAny, + arrayContainsAll, + arrayLength, + equalAny, + notEqualAny, + xor, + conditional, + not, + logicalMaximum, + logicalMinimum, + exists, + reverse, + byteLength, + charLength, + like, + regexContains, + regexMatch, + stringContains, + startsWith, + endsWith, + toLower, + toUpper, + trim, + stringConcat, + mapGet, + countAll, + count, + sum, + average, + and, + or, + minimum, + maximum, + cosineDistance, + dotProduct, + euclideanDistance, + vectorLength, + unixMicrosToTimestamp, + timestampToUnixMicros, + unixMillisToTimestamp, + timestampToUnixMillis, + unixSecondsToTimestamp, + timestampToUnixSeconds, + timestampAdd, + timestampSubtract, + ascending, + descending, + countIf, + array, + arrayGet, + isError, + ifError, + isAbsent, + map, + mapRemove, + mapMerge, + documentId, + substring, + countDistinct, + ceil, + floor, + exp, + pow, + round, + collectionId, + ln, + log, + sqrt, + stringReverse, + length, + abs, + concat, + currentTimestamp, + ifAbsent, + join, + log10, + arraySum, + timestampTruncate, + split, + type, + Expression, + AliasedExpression, + Field, + FunctionExpression, + Ordering, + BooleanExpression, + AggregateFunction, + ExpressionType, + AliasedAggregate, + Selectable, + TimeGranularity +} from './lite-api/expressions'; + +export { _internalPipelineToExecutePipelineRequestProto } from './remote/internal_serializer'; diff --git a/packages/firestore/src/core/database_info.ts b/packages/firestore/src/core/database_info.ts index a057516763..ec75ba2486 100644 --- a/packages/firestore/src/core/database_info.ts +++ b/packages/firestore/src/core/database_info.ts @@ -49,7 +49,8 @@ export class DatabaseInfo { readonly autoDetectLongPolling: boolean, readonly longPollingOptions: ExperimentalLongPollingOptions, readonly useFetchStreams: boolean, - readonly isUsingEmulator: boolean + readonly isUsingEmulator: boolean, + readonly apiKey: string | undefined ) {} } diff --git a/packages/firestore/src/core/firestore_client.ts b/packages/firestore/src/core/firestore_client.ts index 39bb8dd4eb..7535a87a60 100644 --- a/packages/firestore/src/core/firestore_client.ts +++ b/packages/firestore/src/core/firestore_client.ts @@ -38,11 +38,16 @@ import { Document } from '../model/document'; import { DocumentKey } from '../model/document_key'; import { FieldIndex } from '../model/field_index'; import { Mutation } from '../model/mutation'; +import { PipelineStreamElement } from '../model/pipeline_stream_element'; import { toByteStreamReader } from '../platform/byte_stream_reader'; import { newSerializer } from '../platform/serializer'; import { newTextEncoder } from '../platform/text_serializer'; import { ApiClientObjectMap, Value } from '../protos/firestore_proto_api'; -import { Datastore, invokeRunAggregationQueryRpc } from '../remote/datastore'; +import { + Datastore, + invokeExecutePipeline, + invokeRunAggregationQueryRpc +} from '../remote/datastore'; import { RemoteStore, remoteStoreDisableNetwork, @@ -82,6 +87,7 @@ import { removeSnapshotsInSyncListener } from './event_manager'; import { newQueryForPath, Query } from './query'; +import { StructuredPipeline } from './structured_pipeline'; import { SyncEngine } from './sync_engine'; import { syncEngineListen, @@ -140,7 +146,10 @@ export class FirestoreClient { * an async I/O to complete). */ public asyncQueue: AsyncQueue, - private databaseInfo: DatabaseInfo, + /** + * Exposed for testing + */ + public _databaseInfo: DatabaseInfo, componentProvider?: { _offline: OfflineComponentProvider; _online: OnlineComponentProvider; @@ -161,7 +170,7 @@ export class FirestoreClient { get configuration(): ComponentConfiguration { return { asyncQueue: this.asyncQueue, - databaseInfo: this.databaseInfo, + databaseInfo: this._databaseInfo, clientId: this.clientId, authCredentials: this.authCredentials, appCheckCredentials: this.appCheckCredentials, @@ -550,6 +559,23 @@ export function firestoreClientRunAggregateQuery( return deferred.promise; } +export function firestoreClientExecutePipeline( + client: FirestoreClient, + pipeline: StructuredPipeline +): Promise { + const deferred = new Deferred(); + + client.asyncQueue.enqueueAndForget(async () => { + try { + const datastore = await getDatastore(client); + deferred.resolve(invokeExecutePipeline(datastore, pipeline)); + } catch (e) { + deferred.reject(e as Error); + } + }); + return deferred.promise; +} + export function firestoreClientWrite( client: FirestoreClient, mutations: Mutation[] diff --git a/packages/firestore/src/core/options_util.ts b/packages/firestore/src/core/options_util.ts new file mode 100644 index 0000000000..f723385064 --- /dev/null +++ b/packages/firestore/src/core/options_util.ts @@ -0,0 +1,95 @@ +/** + * @license + * Copyright 2025 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 { ParseContext } from '../api/parse_context'; +import { parseData } from '../lite-api/user_data_reader'; +import { ObjectValue } from '../model/object_value'; +import { FieldPath } from '../model/path'; +import { ApiClientObjectMap, Value } from '../protos/firestore_proto_api'; +import { isPlainObject } from '../util/input_validation'; +import { mapToArray } from '../util/obj'; +export type OptionsDefinitions = Record; +export interface OptionDefinition { + serverName: string; + nestedOptions?: OptionsDefinitions; +} + +export class OptionsUtil { + constructor(private optionDefinitions: OptionsDefinitions) {} + + private _getKnownOptions( + options: Record, + context: ParseContext + ): ObjectValue { + const knownOptions: ObjectValue = ObjectValue.empty(); + + // SERIALIZE KNOWN OPTIONS + for (const knownOptionKey in this.optionDefinitions) { + if (this.optionDefinitions.hasOwnProperty(knownOptionKey)) { + const optionDefinition: OptionDefinition = + this.optionDefinitions[knownOptionKey]; + + if (knownOptionKey in options) { + const optionValue: unknown = options[knownOptionKey]; + let protoValue: Value | undefined = undefined; + + if (optionDefinition.nestedOptions && isPlainObject(optionValue)) { + const nestedUtil = new OptionsUtil(optionDefinition.nestedOptions); + protoValue = { + mapValue: { + fields: nestedUtil.getOptionsProto(context, optionValue) + } + }; + } else if (optionValue) { + protoValue = parseData(optionValue, context) ?? undefined; + } + + if (protoValue) { + knownOptions.set( + FieldPath.fromServerFormat(optionDefinition.serverName), + protoValue + ); + } + } + } + } + + return knownOptions; + } + + getOptionsProto( + context: ParseContext, + knownOptions: Record, + optionsOverride?: Record + ): ApiClientObjectMap | undefined { + const result: ObjectValue = this._getKnownOptions(knownOptions, context); + + // APPLY OPTIONS OVERRIDES + if (optionsOverride) { + const optionsMap = new Map( + mapToArray(optionsOverride, (value, key) => [ + FieldPath.fromServerFormat(key), + value !== undefined ? parseData(value, context) : null + ]) + ); + result.setAll(optionsMap); + } + + // Return MapValue from `result` or empty map value + return result.value.mapValue.fields ?? {}; + } +} diff --git a/packages/firestore/src/core/pipeline-util.ts b/packages/firestore/src/core/pipeline-util.ts new file mode 100644 index 0000000000..b27babf695 --- /dev/null +++ b/packages/firestore/src/core/pipeline-util.ts @@ -0,0 +1,271 @@ +/** + * @license + * Copyright 2024 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 { Firestore } from '../lite-api/database'; +import { + Constant, + BooleanExpression, + and, + or, + Ordering, + lessThan, + greaterThan, + field +} from '../lite-api/expressions'; +import { Pipeline } from '../lite-api/pipeline'; +import { doc } from '../lite-api/reference'; +import { fail } from '../util/assert'; + +import { Bound } from './bound'; +import { + CompositeFilter as CompositeFilterInternal, + CompositeOperator, + FieldFilter as FieldFilterInternal, + Filter as FilterInternal, + Operator +} from './filter'; +import { Direction } from './order_by'; +import { + isCollectionGroupQuery, + isDocumentQuery, + LimitType, + Query, + queryNormalizedOrderBy +} from './query'; + +/* eslint @typescript-eslint/no-explicit-any: 0 */ + +export function toPipelineBooleanExpr(f: FilterInternal): BooleanExpression { + if (f instanceof FieldFilterInternal) { + const fieldValue = field(f.field.toString()); + // Comparison filters + const value = f.value; + switch (f.op) { + case Operator.LESS_THAN: + return and( + fieldValue.exists(), + fieldValue.lessThan(Constant._fromProto(value)) + ); + case Operator.LESS_THAN_OR_EQUAL: + return and( + fieldValue.exists(), + fieldValue.lessThanOrEqual(Constant._fromProto(value)) + ); + case Operator.GREATER_THAN: + return and( + fieldValue.exists(), + fieldValue.greaterThan(Constant._fromProto(value)) + ); + case Operator.GREATER_THAN_OR_EQUAL: + return and( + fieldValue.exists(), + fieldValue.greaterThanOrEqual(Constant._fromProto(value)) + ); + case Operator.EQUAL: + return and( + fieldValue.exists(), + fieldValue.equal(Constant._fromProto(value)) + ); + case Operator.NOT_EQUAL: + return and( + fieldValue.exists(), + fieldValue.notEqual(Constant._fromProto(value)) + ); + case Operator.ARRAY_CONTAINS: + return and( + fieldValue.exists(), + fieldValue.arrayContains(Constant._fromProto(value)) + ); + case Operator.IN: { + const values = value?.arrayValue?.values?.map((val: any) => + Constant._fromProto(val) + ); + if (!values) { + return and(fieldValue.exists(), fieldValue.equalAny([])); + } else if (values.length === 1) { + return and(fieldValue.exists(), fieldValue.equal(values[0])); + } else { + return and(fieldValue.exists(), fieldValue.equalAny(values)); + } + } + case Operator.ARRAY_CONTAINS_ANY: { + const values = value?.arrayValue?.values?.map((val: any) => + Constant._fromProto(val) + ); + return and(fieldValue.exists(), fieldValue.arrayContainsAny(values!)); + } + case Operator.NOT_IN: { + const values = value?.arrayValue?.values?.map((val: any) => + Constant._fromProto(val) + ); + if (!values) { + return and(fieldValue.exists(), fieldValue.notEqualAny([])); + } else if (values.length === 1) { + return and(fieldValue.exists(), fieldValue.notEqual(values[0])); + } else { + return and(fieldValue.exists(), fieldValue.notEqualAny(values)); + } + } + default: + fail(0x9047, 'Unexpected operator'); + } + } else if (f instanceof CompositeFilterInternal) { + switch (f.op) { + case CompositeOperator.AND: { + const conditions = f.getFilters().map(f => toPipelineBooleanExpr(f)); + return and(conditions[0], conditions[1], ...conditions.slice(2)); + } + case CompositeOperator.OR: { + const conditions = f.getFilters().map(f => toPipelineBooleanExpr(f)); + return or(conditions[0], conditions[1], ...conditions.slice(2)); + } + default: + fail(0x89ea, 'Unexpected operator'); + } + } + + throw new Error(`Failed to convert filter to pipeline conditions: ${f}`); +} + +function reverseOrderings(orderings: Ordering[]): Ordering[] { + return orderings.map( + o => + new Ordering( + o.expr, + o.direction === 'ascending' ? 'descending' : 'ascending', + undefined + ) + ); +} + +export function toPipeline(query: Query, db: Firestore): Pipeline { + let pipeline: Pipeline; + if (isCollectionGroupQuery(query)) { + pipeline = db.pipeline().collectionGroup(query.collectionGroup!); + } else if (isDocumentQuery(query)) { + pipeline = db.pipeline().documents([doc(db, query.path.canonicalString())]); + } else { + pipeline = db.pipeline().collection(query.path.canonicalString()); + } + + // filters + for (const filter of query.filters) { + pipeline = pipeline.where(toPipelineBooleanExpr(filter)); + } + + // orders + const orders = queryNormalizedOrderBy(query); + const existsConditions = orders.map(order => + field(order.field.canonicalString()).exists() + ); + if (existsConditions.length > 1) { + pipeline = pipeline.where( + and( + existsConditions[0], + existsConditions[1], + ...existsConditions.slice(2) + ) + ); + } else { + pipeline = pipeline.where(existsConditions[0]); + } + + const orderings = orders.map(order => + order.dir === Direction.ASCENDING + ? field(order.field.canonicalString()).ascending() + : field(order.field.canonicalString()).descending() + ); + + if (orderings.length > 0) { + if (query.limitType === LimitType.Last) { + const actualOrderings = reverseOrderings(orderings); + pipeline = pipeline.sort(actualOrderings[0], ...actualOrderings.slice(1)); + // cursors + if (query.startAt !== null) { + pipeline = pipeline.where( + whereConditionsFromCursor(query.startAt, orderings, 'after') + ); + } + + if (query.endAt !== null) { + pipeline = pipeline.where( + whereConditionsFromCursor(query.endAt, orderings, 'before') + ); + } + + pipeline = pipeline.limit(query.limit!); + pipeline = pipeline.sort(orderings[0], ...orderings.slice(1)); + } else { + pipeline = pipeline.sort(orderings[0], ...orderings.slice(1)); + if (query.startAt !== null) { + pipeline = pipeline.where( + whereConditionsFromCursor(query.startAt, orderings, 'after') + ); + } + if (query.endAt !== null) { + pipeline = pipeline.where( + whereConditionsFromCursor(query.endAt, orderings, 'before') + ); + } + + if (query.limit !== null) { + pipeline = pipeline.limit(query.limit); + } + } + } + + return pipeline; +} + +function whereConditionsFromCursor( + bound: Bound, + orderings: Ordering[], + position: 'before' | 'after' +): BooleanExpression { + // The filterFunc is either greater than or less than + const filterFunc = position === 'before' ? lessThan : greaterThan; + const cursors = bound.position.map(value => Constant._fromProto(value)); + const size = cursors.length; + + let field = orderings[size - 1].expr; + let value = cursors[size - 1]; + + // Add condition for last bound + let condition: BooleanExpression = filterFunc(field, value); + if (bound.inclusive) { + // When the cursor bound is inclusive, then the last bound + // can be equal to the value, otherwise it's not equal + condition = or(condition, field.equal(value)); + } + + // Iterate backwards over the remaining bounds, adding + // a condition for each one + for (let i = size - 2; i >= 0; i--) { + field = orderings[i].expr; + value = cursors[i]; + + // For each field in the orderings, the condition is either + // a) lt|gt the cursor value, + // b) or equal the cursor value and lt|gt the cursor values for other fields + condition = or( + filterFunc(field, value), + and(field.equal(value), condition) + ); + } + + return condition; +} diff --git a/packages/firestore/src/core/structured_pipeline.ts b/packages/firestore/src/core/structured_pipeline.ts new file mode 100644 index 0000000000..ac8ee4284f --- /dev/null +++ b/packages/firestore/src/core/structured_pipeline.ts @@ -0,0 +1,67 @@ +/** + * @license + * Copyright 2025 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 { ParseContext } from '../api/parse_context'; +import { UserData } from '../lite-api/user_data_reader'; +import { + ApiClientObjectMap, + firestoreV1ApiClientInterfaces, + Pipeline as PipelineProto, + StructuredPipeline as StructuredPipelineProto +} from '../protos/firestore_proto_api'; +import { JsonProtoSerializer, ProtoSerializable } from '../remote/serializer'; + +import { OptionsUtil } from './options_util'; + +export class StructuredPipelineOptions implements UserData { + proto: ApiClientObjectMap | undefined; + + readonly optionsUtil = new OptionsUtil({ + indexMode: { + serverName: 'index_mode' + } + }); + + constructor( + private _userOptions: Record = {}, + private _optionsOverride: Record = {} + ) {} + + _readUserData(context: ParseContext): void { + this.proto = this.optionsUtil.getOptionsProto( + context, + this._userOptions, + this._optionsOverride + ); + } +} + +export class StructuredPipeline + implements ProtoSerializable +{ + constructor( + private pipeline: ProtoSerializable, + private options: StructuredPipelineOptions + ) {} + + _toProto(serializer: JsonProtoSerializer): StructuredPipelineProto { + return { + pipeline: this.pipeline._toProto(serializer), + options: this.options.proto + }; + } +} diff --git a/packages/firestore/src/global.ts b/packages/firestore/src/global.ts new file mode 100644 index 0000000000..a04572dac0 --- /dev/null +++ b/packages/firestore/src/global.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2025 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. + */ + +// This file supports a special internal build that includes the entire +// Firestore classic and pipeline api surface in one bundle. + +import * as pipelines from './api_pipelines'; +export * from './api'; +export { pipelines }; + +// Console specific exports +export { + type firestoreV1ApiClientInterfaces, + type Timestamp as ProtoTimestamp +} from './protos/firestore_proto_api'; diff --git a/packages/firestore/src/lite-api/aggregate_types.ts b/packages/firestore/src/lite-api/aggregate_types.ts index a87b3ca695..292a2edd4f 100644 --- a/packages/firestore/src/lite-api/aggregate_types.ts +++ b/packages/firestore/src/lite-api/aggregate_types.ts @@ -16,8 +16,13 @@ */ import { AggregateType } from '../core/aggregate'; +import { ObjectValue } from '../model/object_value'; import { FieldPath as InternalFieldPath } from '../model/path'; -import { ApiClientObjectMap, Value } from '../protos/firestore_proto_api'; +import { + ApiClientObjectMap, + firestoreV1ApiClientInterfaces, + Value +} from '../protos/firestore_proto_api'; import { average, count, sum } from './aggregate'; import { DocumentData, Query } from './reference'; @@ -116,4 +121,22 @@ export class AggregateQuerySnapshot< this._data ) as AggregateSpecData; } + + /** + * @internal + * @private + * + * Retrieves all fields in the snapshot as a proto value. + * + * @returns An `Object` containing all fields in the snapshot. + */ + _fieldsProto(): { [key: string]: firestoreV1ApiClientInterfaces.Value } { + // Wrap data in an ObjectValue to clone it. + const dataClone = new ObjectValue({ + mapValue: { fields: this._data } + }).clone(); + + // Return the cloned value to prevent manipulation of the Snapshot's data + return dataClone.value.mapValue.fields!; + } } diff --git a/packages/firestore/src/lite-api/components.ts b/packages/firestore/src/lite-api/components.ts index 52c3b3729e..d956fcd31f 100644 --- a/packages/firestore/src/lite-api/components.ts +++ b/packages/firestore/src/lite-api/components.ts @@ -75,6 +75,7 @@ export function getDatastore(firestore: FirestoreService): Datastore { firestore._databaseId, firestore.app.options.appId || '', firestore._persistenceKey, + firestore.app.options.apiKey, firestore._freezeSettings() ); const connection = newConnection(databaseInfo); @@ -108,6 +109,7 @@ export function makeDatabaseInfo( databaseId: DatabaseId, appId: string, persistenceKey: string, + apiKey: string | undefined, settings: FirestoreSettingsImpl ): DatabaseInfo { return new DatabaseInfo( @@ -120,6 +122,7 @@ export function makeDatabaseInfo( settings.experimentalAutoDetectLongPolling, cloneLongPollingOptions(settings.experimentalLongPollingOptions), settings.useFetchStreams, - settings.isUsingEmulator + settings.isUsingEmulator, + apiKey ); } diff --git a/packages/firestore/src/lite-api/database_augmentation.ts b/packages/firestore/src/lite-api/database_augmentation.ts new file mode 100644 index 0000000000..bf25e9c59c --- /dev/null +++ b/packages/firestore/src/lite-api/database_augmentation.ts @@ -0,0 +1,16 @@ +/** + * @license + * Copyright 2024 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. + */ diff --git a/packages/firestore/src/lite-api/expressions.ts b/packages/firestore/src/lite-api/expressions.ts new file mode 100644 index 0000000000..7b236ea5c6 --- /dev/null +++ b/packages/firestore/src/lite-api/expressions.ts @@ -0,0 +1,8183 @@ +/** + * @license + * Copyright 2024 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 { FirestoreError } from '../api'; +import { ParseContext } from '../api/parse_context'; +import { + DOCUMENT_KEY_NAME, + FieldPath as InternalFieldPath +} from '../model/path'; +import { Value as ProtoValue } from '../protos/firestore_proto_api'; +import { + JsonProtoSerializer, + ProtoValueSerializable, + toMapValue, + toStringValue +} from '../remote/serializer'; +import { hardAssert } from '../util/assert'; +import { isPlainObject } from '../util/input_validation'; +import { isFirestoreValue } from '../util/proto'; +import { isString } from '../util/types'; + +import { Bytes } from './bytes'; +import { documentId as documentIdFieldPath, FieldPath } from './field_path'; +import { vector } from './field_value_impl'; +import { GeoPoint } from './geo_point'; +import { DocumentReference } from './reference'; +import { Timestamp } from './timestamp'; +import { fieldPathFromArgument, parseData, UserData } from './user_data_reader'; +import { VectorValue } from './vector_value'; + +/** + * @beta + * + * An enumeration of the different types of expressions. + */ +export type ExpressionType = + | 'Field' + | 'Constant' + | 'Function' + | 'AggregateFunction' + | 'ListOfExpressions' + | 'AliasedExpression'; + +/** + * Converts a value to an Expr, Returning either a Constant, MapFunction, + * ArrayFunction, or the input itself (if it's already an expression). + * + * @private + * @internal + * @param value + */ +function valueToDefaultExpr(value: unknown): Expression { + let result: Expression | undefined; + if (value instanceof Expression) { + return value; + } else if (isPlainObject(value)) { + result = _map(value as Record, undefined); + } else if (value instanceof Array) { + result = array(value); + } else { + result = _constant(value, undefined); + } + + return result; +} + +/** + * Converts a value to an Expr, Returning either a Constant, MapFunction, + * ArrayFunction, or the input itself (if it's already an expression). + * + * @private + * @internal + * @param value + */ +function vectorToExpr(value: VectorValue | number[] | Expression): Expression { + if (value instanceof Expression) { + return value; + } else if (value instanceof VectorValue) { + return constant(value); + } else if (Array.isArray(value)) { + return constant(vector(value)); + } else { + throw new Error('Unsupported value: ' + typeof value); + } +} + +/** + * Converts a value to an Expr, Returning either a Constant, MapFunction, + * ArrayFunction, or the input itself (if it's already an expression). + * If the input is a string, it is assumed to be a field name, and a + * field(value) is returned. + * + * @private + * @internal + * @param value + */ +function fieldOrExpression(value: unknown): Expression { + if (isString(value)) { + const result = field(value); + return result; + } else { + return valueToDefaultExpr(value); + } +} + +/** + * @beta + * + * Represents an expression that can be evaluated to a value within the execution of a {@link + * Pipeline}. + * + * Expressions are the building blocks for creating complex queries and transformations in + * Firestore pipelines. They can represent: + * + * - **Field references:** Access values from document fields. + * - **Literals:** Represent constant values (strings, numbers, booleans). + * - **Function calls:** Apply functions to one or more expressions. + * + * The `Expr` class provides a fluent API for building expressions. You can chain together + * method calls to create complex expressions. + */ +export abstract class Expression implements ProtoValueSerializable, UserData { + abstract readonly expressionType: ExpressionType; + + abstract readonly _methodName?: string; + + /** + * @private + * @internal + */ + abstract _toProto(serializer: JsonProtoSerializer): ProtoValue; + _protoValueType = 'ProtoValue' as const; + + /** + * @private + * @internal + */ + abstract _readUserData(context: ParseContext): void; + + /** + * Creates an expression that adds this expression to another expression. + * + * ```typescript + * // Add the value of the 'quantity' field and the 'reserve' field. + * field("quantity").add(field("reserve")); + * ``` + * + * @param second The expression or literal to add to this expression. + * @param others Optional additional expressions or literals to add to this expression. + * @return A new `Expr` representing the addition operation. + */ + add(second: Expression | unknown): FunctionExpression { + return new FunctionExpression( + 'add', + [this, valueToDefaultExpr(second)], + 'add' + ); + } + + /** + * @beta + * Wraps the expression in a [BooleanExpression]. + * + * @return A [BooleanExpression] representing the same expression. + */ + asBoolean(): BooleanExpression { + if (this instanceof BooleanExpression) { + return this; + } else if (this instanceof Constant) { + return new BooleanConstant(this); + } else if (this instanceof Field) { + return new BooleanField(this); + } else if (this instanceof FunctionExpression) { + return new BooleanFunctionExpression(this); + } else { + throw new FirestoreError( + 'invalid-argument', + `Conversion of type ${typeof this} to BooleanExpression not supported.` + ); + } + } + + /** + * @beta + * Creates an expression that subtracts another expression from this expression. + * + * ```typescript + * // Subtract the 'discount' field from the 'price' field + * field("price").subtract(field("discount")); + * ``` + * + * @param subtrahend The expression to subtract from this expression. + * @return A new `Expr` representing the subtraction operation. + */ + subtract(subtrahend: Expression): FunctionExpression; + + /** + * @beta + * Creates an expression that subtracts a constant value from this expression. + * + * ```typescript + * // Subtract 20 from the value of the 'total' field + * field("total").subtract(20); + * ``` + * + * @param subtrahend The constant value to subtract. + * @return A new `Expr` representing the subtraction operation. + */ + subtract(subtrahend: number): FunctionExpression; + subtract(subtrahend: number | Expression): FunctionExpression { + return new FunctionExpression( + 'subtract', + [this, valueToDefaultExpr(subtrahend)], + 'subtract' + ); + } + + /** + * @beta + * Creates an expression that multiplies this expression by another expression. + * + * ```typescript + * // Multiply the 'quantity' field by the 'price' field + * field("quantity").multiply(field("price")); + * ``` + * + * @param second The second expression or literal to multiply by. + * @param others Optional additional expressions or literals to multiply by. + * @return A new `Expr` representing the multiplication operation. + */ + multiply(second: Expression | number): FunctionExpression { + return new FunctionExpression( + 'multiply', + [this, valueToDefaultExpr(second)], + 'multiply' + ); + } + + /** + * @beta + * Creates an expression that divides this expression by another expression. + * + * ```typescript + * // Divide the 'total' field by the 'count' field + * field("total").divide(field("count")); + * ``` + * + * @param divisor The expression to divide by. + * @return A new `Expr` representing the division operation. + */ + divide(divisor: Expression): FunctionExpression; + + /** + * @beta + * Creates an expression that divides this expression by a constant value. + * + * ```typescript + * // Divide the 'value' field by 10 + * field("value").divide(10); + * ``` + * + * @param divisor The constant value to divide by. + * @return A new `Expr` representing the division operation. + */ + divide(divisor: number): FunctionExpression; + divide(divisor: number | Expression): FunctionExpression { + return new FunctionExpression( + 'divide', + [this, valueToDefaultExpr(divisor)], + 'divide' + ); + } + + /** + * @beta + * Creates an expression that calculates the modulo (remainder) of dividing this expression by another expression. + * + * ```typescript + * // Calculate the remainder of dividing the 'value' field by the 'divisor' field + * field("value").mod(field("divisor")); + * ``` + * + * @param expression The expression to divide by. + * @return A new `Expr` representing the modulo operation. + */ + mod(expression: Expression): FunctionExpression; + + /** + * @beta + * Creates an expression that calculates the modulo (remainder) of dividing this expression by a constant value. + * + * ```typescript + * // Calculate the remainder of dividing the 'value' field by 10 + * field("value").mod(10); + * ``` + * + * @param value The constant value to divide by. + * @return A new `Expr` representing the modulo operation. + */ + mod(value: number): FunctionExpression; + mod(other: number | Expression): FunctionExpression { + return new FunctionExpression( + 'mod', + [this, valueToDefaultExpr(other)], + 'mod' + ); + } + + /** + * @beta + * Creates an expression that checks if this expression is equal to another expression. + * + * ```typescript + * // Check if the 'age' field is equal to 21 + * field("age").equal(21); + * ``` + * + * @param expression The expression to compare for equality. + * @return A new `Expr` representing the equality comparison. + */ + equal(expression: Expression): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if this expression is equal to a constant value. + * + * ```typescript + * // Check if the 'city' field is equal to "London" + * field("city").equal("London"); + * ``` + * + * @param value The constant value to compare for equality. + * @return A new `Expr` representing the equality comparison. + */ + equal(value: unknown): BooleanExpression; + equal(other: unknown): BooleanExpression { + return new FunctionExpression( + 'equal', + [this, valueToDefaultExpr(other)], + 'equal' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that checks if this expression is not equal to another expression. + * + * ```typescript + * // Check if the 'status' field is not equal to "completed" + * field("status").notEqual("completed"); + * ``` + * + * @param expression The expression to compare for inequality. + * @return A new `Expr` representing the inequality comparison. + */ + notEqual(expression: Expression): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if this expression is not equal to a constant value. + * + * ```typescript + * // Check if the 'country' field is not equal to "USA" + * field("country").notEqual("USA"); + * ``` + * + * @param value The constant value to compare for inequality. + * @return A new `Expr` representing the inequality comparison. + */ + notEqual(value: unknown): BooleanExpression; + notEqual(other: unknown): BooleanExpression { + return new FunctionExpression( + 'not_equal', + [this, valueToDefaultExpr(other)], + 'notEqual' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that checks if this expression is less than another expression. + * + * ```typescript + * // Check if the 'age' field is less than 'limit' + * field("age").lessThan(field('limit')); + * ``` + * + * @param experession The expression to compare for less than. + * @return A new `Expr` representing the less than comparison. + */ + lessThan(experession: Expression): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if this expression is less than a constant value. + * + * ```typescript + * // Check if the 'price' field is less than 50 + * field("price").lessThan(50); + * ``` + * + * @param value The constant value to compare for less than. + * @return A new `Expr` representing the less than comparison. + */ + lessThan(value: unknown): BooleanExpression; + lessThan(other: unknown): BooleanExpression { + return new FunctionExpression( + 'less_than', + [this, valueToDefaultExpr(other)], + 'lessThan' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that checks if this expression is less than or equal to another + * expression. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to 20 + * field("quantity").lessThan(constant(20)); + * ``` + * + * @param expression The expression to compare for less than or equal to. + * @return A new `Expr` representing the less than or equal to comparison. + */ + lessThanOrEqual(expression: Expression): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if this expression is less than or equal to a constant value. + * + * ```typescript + * // Check if the 'score' field is less than or equal to 70 + * field("score").lessThan(70); + * ``` + * + * @param value The constant value to compare for less than or equal to. + * @return A new `Expr` representing the less than or equal to comparison. + */ + lessThanOrEqual(value: unknown): BooleanExpression; + lessThanOrEqual(other: unknown): BooleanExpression { + return new FunctionExpression( + 'less_than_or_equal', + [this, valueToDefaultExpr(other)], + 'lessThanOrEqual' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that checks if this expression is greater than another expression. + * + * ```typescript + * // Check if the 'age' field is greater than the 'limit' field + * field("age").greaterThan(field("limit")); + * ``` + * + * @param expression The expression to compare for greater than. + * @return A new `Expr` representing the greater than comparison. + */ + greaterThan(expression: Expression): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if this expression is greater than a constant value. + * + * ```typescript + * // Check if the 'price' field is greater than 100 + * field("price").greaterThan(100); + * ``` + * + * @param value The constant value to compare for greater than. + * @return A new `Expr` representing the greater than comparison. + */ + greaterThan(value: unknown): BooleanExpression; + greaterThan(other: unknown): BooleanExpression { + return new FunctionExpression( + 'greater_than', + [this, valueToDefaultExpr(other)], + 'greaterThan' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that checks if this expression is greater than or equal to another + * expression. + * + * ```typescript + * // Check if the 'quantity' field is greater than or equal to field 'requirement' plus 1 + * field("quantity").greaterThanOrEqual(field('requirement').add(1)); + * ``` + * + * @param expression The expression to compare for greater than or equal to. + * @return A new `Expr` representing the greater than or equal to comparison. + */ + greaterThanOrEqual(expression: Expression): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if this expression is greater than or equal to a constant + * value. + * + * ```typescript + * // Check if the 'score' field is greater than or equal to 80 + * field("score").greaterThanOrEqual(80); + * ``` + * + * @param value The constant value to compare for greater than or equal to. + * @return A new `Expr` representing the greater than or equal to comparison. + */ + greaterThanOrEqual(value: unknown): BooleanExpression; + greaterThanOrEqual(other: unknown): BooleanExpression { + return new FunctionExpression( + 'greater_than_or_equal', + [this, valueToDefaultExpr(other)], + 'greaterThanOrEqual' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that concatenates an array expression with one or more other arrays. + * + * ```typescript + * // Combine the 'items' array with another array field. + * field("items").arrayConcat(field("otherItems")); + * ``` + * @param secondArray Second array expression or array literal to concatenate. + * @param otherArrays Optional additional array expressions or array literals to concatenate. + * @return A new `Expr` representing the concatenated array. + */ + arrayConcat( + secondArray: Expression | unknown[], + ...otherArrays: Array + ): FunctionExpression { + const elements = [secondArray, ...otherArrays]; + const exprValues = elements.map(value => valueToDefaultExpr(value)); + return new FunctionExpression( + 'array_concat', + [this, ...exprValues], + 'arrayConcat' + ); + } + + /** + * @beta + * Creates an expression that checks if an array contains a specific element. + * + * ```typescript + * // Check if the 'sizes' array contains the value from the 'selectedSize' field + * field("sizes").arrayContains(field("selectedSize")); + * ``` + * + * @param expression The element to search for in the array. + * @return A new `Expr` representing the 'array_contains' comparison. + */ + arrayContains(expression: Expression): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if an array contains a specific value. + * + * ```typescript + * // Check if the 'colors' array contains "red" + * field("colors").arrayContains("red"); + * ``` + * + * @param value The element to search for in the array. + * @return A new `Expr` representing the 'array_contains' comparison. + */ + arrayContains(value: unknown): BooleanExpression; + arrayContains(element: unknown): BooleanExpression { + return new FunctionExpression( + 'array_contains', + [this, valueToDefaultExpr(element)], + 'arrayContains' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that checks if an array contains all the specified elements. + * + * ```typescript + * // Check if the 'tags' array contains both the value in field "tag1" and the literal value "tag2" + * field("tags").arrayContainsAll([field("tag1"), "tag2"]); + * ``` + * + * @param values The elements to check for in the array. + * @return A new `Expr` representing the 'array_contains_all' comparison. + */ + arrayContainsAll(values: Array): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if an array contains all the specified elements. + * + * ```typescript + * // Check if the 'tags' array contains both of the values from field "tag1" and the literal value "tag2" + * field("tags").arrayContainsAll(array([field("tag1"), "tag2"])); + * ``` + * + * @param arrayExpression The elements to check for in the array. + * @return A new `Expr` representing the 'array_contains_all' comparison. + */ + arrayContainsAll(arrayExpression: Expression): BooleanExpression; + arrayContainsAll(values: unknown[] | Expression): BooleanExpression { + const normalizedExpr = Array.isArray(values) + ? new ListOfExprs(values.map(valueToDefaultExpr), 'arrayContainsAll') + : values; + return new FunctionExpression( + 'array_contains_all', + [this, normalizedExpr], + 'arrayContainsAll' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that checks if an array contains any of the specified elements. + * + * ```typescript + * // Check if the 'categories' array contains either values from field "cate1" or "cate2" + * field("categories").arrayContainsAny([field("cate1"), field("cate2")]); + * ``` + * + * @param values The elements to check for in the array. + * @return A new `Expr` representing the 'array_contains_any' comparison. + */ + arrayContainsAny(values: Array): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if an array contains any of the specified elements. + * + * ```typescript + * // Check if the 'groups' array contains either the value from the 'userGroup' field + * // or the value "guest" + * field("groups").arrayContainsAny(array([field("userGroup"), "guest"])); + * ``` + * + * @param arrayExpression The elements to check for in the array. + * @return A new `Expr` representing the 'array_contains_any' comparison. + */ + arrayContainsAny(arrayExpression: Expression): BooleanExpression; + arrayContainsAny( + values: Array | Expression + ): BooleanExpression { + const normalizedExpr = Array.isArray(values) + ? new ListOfExprs(values.map(valueToDefaultExpr), 'arrayContainsAny') + : values; + return new FunctionExpression( + 'array_contains_any', + [this, normalizedExpr], + 'arrayContainsAny' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that reverses an array. + * + * ```typescript + * // Reverse the value of the 'myArray' field. + * field("myArray").arrayReverse(); + * ``` + * + * @return A new {@code Expr} representing the reversed array. + */ + arrayReverse(): FunctionExpression { + return new FunctionExpression('array_reverse', [this]); + } + + /** + * @beta + * Creates an expression that calculates the length of an array. + * + * ```typescript + * // Get the number of items in the 'cart' array + * field("cart").arrayLength(); + * ``` + * + * @return A new `Expr` representing the length of the array. + */ + arrayLength(): FunctionExpression { + return new FunctionExpression('array_length', [this], 'arrayLength'); + } + + /** + * @beta + * Creates an expression that checks if this expression is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * field("category").equalAny("Electronics", field("primaryType")); + * ``` + * + * @param values The values or expressions to check against. + * @return A new `Expr` representing the 'IN' comparison. + */ + equalAny(values: Array): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if this expression is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * field("category").equalAny(array(["Electronics", field("primaryType")])); + * ``` + * + * @param arrayExpression An expression that evaluates to an array of values to check against. + * @return A new `Expr` representing the 'IN' comparison. + */ + equalAny(arrayExpression: Expression): BooleanExpression; + equalAny(others: unknown[] | Expression): BooleanExpression { + const exprOthers = Array.isArray(others) + ? new ListOfExprs(others.map(valueToDefaultExpr), 'equalAny') + : others; + return new FunctionExpression( + 'equal_any', + [this, exprOthers], + 'equalAny' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that checks if this expression is not equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' + * field("status").notEqualAny(["pending", field("rejectedStatus")]); + * ``` + * + * @param values The values or expressions to check against. + * @return A new `Expr` representing the 'notEqualAny' comparison. + */ + notEqualAny(values: Array): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if this expression is not equal to any of the values in the evaluated expression. + * + * ```typescript + * // Check if the 'status' field is not equal to any value in the field 'rejectedStatuses' + * field("status").notEqualAny(field('rejectedStatuses')); + * ``` + * + * @param arrayExpression The values or expressions to check against. + * @return A new `Expr` representing the 'notEqualAny' comparison. + */ + notEqualAny(arrayExpression: Expression): BooleanExpression; + notEqualAny(others: unknown[] | Expression): BooleanExpression { + const exprOthers = Array.isArray(others) + ? new ListOfExprs(others.map(valueToDefaultExpr), 'notEqualAny') + : others; + return new FunctionExpression( + 'not_equal_any', + [this, exprOthers], + 'notEqualAny' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that checks if a field exists in the document. + * + * ```typescript + * // Check if the document has a field named "phoneNumber" + * field("phoneNumber").exists(); + * ``` + * + * @return A new `Expr` representing the 'exists' check. + */ + exists(): BooleanExpression { + return new FunctionExpression('exists', [this], 'exists').asBoolean(); + } + + /** + * @beta + * Creates an expression that calculates the character length of a string in UTF-8. + * + * ```typescript + * // Get the character length of the 'name' field in its UTF-8 form. + * field("name").charLength(); + * ``` + * + * @return A new `Expr` representing the length of the string. + */ + charLength(): FunctionExpression { + return new FunctionExpression('char_length', [this], 'charLength'); + } + + /** + * @beta + * Creates an expression that performs a case-sensitive string comparison. + * + * ```typescript + * // Check if the 'title' field contains the word "guide" (case-sensitive) + * field("title").like("%guide%"); + * ``` + * + * @param pattern The pattern to search for. You can use "%" as a wildcard character. + * @return A new `Expr` representing the 'like' comparison. + */ + like(pattern: string): BooleanExpression; + + /** + * @beta + * Creates an expression that performs a case-sensitive string comparison. + * + * ```typescript + * // Check if the 'title' field contains the word "guide" (case-sensitive) + * field("title").like("%guide%"); + * ``` + * + * @param pattern The pattern to search for. You can use "%" as a wildcard character. + * @return A new `Expr` representing the 'like' comparison. + */ + like(pattern: Expression): BooleanExpression; + like(stringOrExpr: string | Expression): BooleanExpression { + return new FunctionExpression( + 'like', + [this, valueToDefaultExpr(stringOrExpr)], + 'like' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that checks if a string contains a specified regular expression as a + * substring. + * + * ```typescript + * // Check if the 'description' field contains "example" (case-insensitive) + * field("description").regexContains("(?i)example"); + * ``` + * + * @param pattern The regular expression to use for the search. + * @return A new `Expr` representing the 'contains' comparison. + */ + regexContains(pattern: string): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if a string contains a specified regular expression as a + * substring. + * + * ```typescript + * // Check if the 'description' field contains the regular expression stored in field 'regex' + * field("description").regexContains(field("regex")); + * ``` + * + * @param pattern The regular expression to use for the search. + * @return A new `Expr` representing the 'contains' comparison. + */ + regexContains(pattern: Expression): BooleanExpression; + regexContains(stringOrExpr: string | Expression): BooleanExpression { + return new FunctionExpression( + 'regex_contains', + [this, valueToDefaultExpr(stringOrExpr)], + 'regexContains' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that checks if a string matches a specified regular expression. + * + * ```typescript + * // Check if the 'email' field matches a valid email pattern + * field("email").regexMatch("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"); + * ``` + * + * @param pattern The regular expression to use for the match. + * @return A new `Expr` representing the regular expression match. + */ + regexMatch(pattern: string): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if a string matches a specified regular expression. + * + * ```typescript + * // Check if the 'email' field matches a regular expression stored in field 'regex' + * field("email").regexMatch(field("regex")); + * ``` + * + * @param pattern The regular expression to use for the match. + * @return A new `Expr` representing the regular expression match. + */ + regexMatch(pattern: Expression): BooleanExpression; + regexMatch(stringOrExpr: string | Expression): BooleanExpression { + return new FunctionExpression( + 'regex_match', + [this, valueToDefaultExpr(stringOrExpr)], + 'regexMatch' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that checks if a string contains a specified substring. + * + * ```typescript + * // Check if the 'description' field contains "example". + * field("description").stringContains("example"); + * ``` + * + * @param substring The substring to search for. + * @return A new `Expr` representing the 'contains' comparison. + */ + stringContains(substring: string): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if a string contains the string represented by another expression. + * + * ```typescript + * // Check if the 'description' field contains the value of the 'keyword' field. + * field("description").stringContains(field("keyword")); + * ``` + * + * @param expr The expression representing the substring to search for. + * @return A new `Expr` representing the 'contains' comparison. + */ + stringContains(expr: Expression): BooleanExpression; + stringContains(stringOrExpr: string | Expression): BooleanExpression { + return new FunctionExpression( + 'string_contains', + [this, valueToDefaultExpr(stringOrExpr)], + 'stringContains' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that checks if a string starts with a given prefix. + * + * ```typescript + * // Check if the 'name' field starts with "Mr." + * field("name").startsWith("Mr."); + * ``` + * + * @param prefix The prefix to check for. + * @return A new `Expr` representing the 'starts with' comparison. + */ + startsWith(prefix: string): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if a string starts with a given prefix (represented as an + * expression). + * + * ```typescript + * // Check if the 'fullName' field starts with the value of the 'firstName' field + * field("fullName").startsWith(field("firstName")); + * ``` + * + * @param prefix The prefix expression to check for. + * @return A new `Expr` representing the 'starts with' comparison. + */ + startsWith(prefix: Expression): BooleanExpression; + startsWith(stringOrExpr: string | Expression): BooleanExpression { + return new FunctionExpression( + 'starts_with', + [this, valueToDefaultExpr(stringOrExpr)], + 'startsWith' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that checks if a string ends with a given postfix. + * + * ```typescript + * // Check if the 'filename' field ends with ".txt" + * field("filename").endsWith(".txt"); + * ``` + * + * @param suffix The postfix to check for. + * @return A new `Expr` representing the 'ends with' comparison. + */ + endsWith(suffix: string): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if a string ends with a given postfix (represented as an + * expression). + * + * ```typescript + * // Check if the 'url' field ends with the value of the 'extension' field + * field("url").endsWith(field("extension")); + * ``` + * + * @param suffix The postfix expression to check for. + * @return A new `Expr` representing the 'ends with' comparison. + */ + endsWith(suffix: Expression): BooleanExpression; + endsWith(stringOrExpr: string | Expression): BooleanExpression { + return new FunctionExpression( + 'ends_with', + [this, valueToDefaultExpr(stringOrExpr)], + 'endsWith' + ).asBoolean(); + } + + /** + * @beta + * Creates an expression that converts a string to lowercase. + * + * ```typescript + * // Convert the 'name' field to lowercase + * field("name").toLower(); + * ``` + * + * @return A new `Expr` representing the lowercase string. + */ + toLower(): FunctionExpression { + return new FunctionExpression('to_lower', [this], 'toLower'); + } + + /** + * @beta + * Creates an expression that converts a string to uppercase. + * + * ```typescript + * // Convert the 'title' field to uppercase + * field("title").toUpper(); + * ``` + * + * @return A new `Expr` representing the uppercase string. + */ + toUpper(): FunctionExpression { + return new FunctionExpression('to_upper', [this], 'toUpper'); + } + + /** + * @beta + * Creates an expression that removes leading and trailing characters from a string or byte array. + * + * ```typescript + * // Trim whitespace from the 'userInput' field + * field("userInput").trim(); + * + * // Trim quotes from the 'userInput' field + * field("userInput").trim('"'); + * ``` + * @param valueToTrim Optional This parameter is treated as a set of characters or bytes that will be + * trimmed from the input. If not specified, then whitespace will be trimmed. + * @return A new `Expr` representing the trimmed string or byte array. + */ + trim(valueToTrim?: string | Expression | Bytes): FunctionExpression { + const args: Expression[] = [this]; + if (valueToTrim) { + args.push(valueToDefaultExpr(valueToTrim)); + } + return new FunctionExpression('trim', args, 'trim'); + } + + /** + * @beta + * Creates an expression that concatenates string expressions together. + * + * ```typescript + * // Combine the 'firstName', " ", and 'lastName' fields into a single string + * field("firstName").stringConcat(constant(" "), field("lastName")); + * ``` + * + * @param secondString The additional expression or string literal to concatenate. + * @param otherStrings Optional additional expressions or string literals to concatenate. + * @return A new `Expr` representing the concatenated string. + */ + stringConcat( + secondString: Expression | string, + ...otherStrings: Array + ): FunctionExpression { + const elements = [secondString, ...otherStrings]; + const exprs = elements.map(valueToDefaultExpr); + return new FunctionExpression( + 'string_concat', + [this, ...exprs], + 'stringConcat' + ); + } + + /** + * @beta + * Creates an expression that concatenates expression results together. + * + * ```typescript + * // Combine the 'firstName', ' ', and 'lastName' fields into a single value. + * field("firstName").concat(constant(" "), field("lastName")); + * ``` + * + * @param second The additional expression or literal to concatenate. + * @param others Optional additional expressions or literals to concatenate. + * @return A new `Expr` representing the concatenated value. + */ + concat( + second: Expression | unknown, + ...others: Array + ): FunctionExpression { + const elements = [second, ...others]; + const exprs = elements.map(valueToDefaultExpr); + return new FunctionExpression('concat', [this, ...exprs], 'concat'); + } + + /** + * @beta + * Creates an expression that reverses this string expression. + * + * ```typescript + * // Reverse the value of the 'myString' field. + * field("myString").reverse(); + * ``` + * + * @return A new {@code Expr} representing the reversed string. + */ + reverse(): FunctionExpression { + return new FunctionExpression('reverse', [this], 'reverse'); + } + + /** + * @beta + * Creates an expression that calculates the length of this string expression in bytes. + * + * ```typescript + * // Calculate the length of the 'myString' field in bytes. + * field("myString").byteLength(); + * ``` + * + * @return A new {@code Expr} representing the length of the string in bytes. + */ + byteLength(): FunctionExpression { + return new FunctionExpression('byte_length', [this], 'byteLength'); + } + + /** + * @beta + * Creates an expression that computes the ceiling of a numeric value. + * + * ```typescript + * // Compute the ceiling of the 'price' field. + * field("price").ceil(); + * ``` + * + * @return A new {@code Expr} representing the ceiling of the numeric value. + */ + ceil(): FunctionExpression { + return new FunctionExpression('ceil', [this]); + } + + /** + * @beta + * Creates an expression that computes the floor of a numeric value. + * + * ```typescript + * // Compute the floor of the 'price' field. + * field("price").floor(); + * ``` + * + * @return A new {@code Expr} representing the floor of the numeric value. + */ + floor(): FunctionExpression { + return new FunctionExpression('floor', [this]); + } + + /** + * @beta + * Creates an expression that computes the absolute value of a numeric value. + * + * ```typescript + * // Compute the absolute value of the 'price' field. + * field("price").abs(); + * ``` + * + * @return A new {@code Expr} representing the absolute value of the numeric value. + */ + abs(): FunctionExpression { + return new FunctionExpression('abs', [this]); + } + + /** + * @beta + * Creates an expression that computes e to the power of this expression. + * + * ```typescript + * // Compute e to the power of the 'value' field. + * field("value").exp(); + * ``` + * + * @return A new {@code Expr} representing the exp of the numeric value. + */ + exp(): FunctionExpression { + return new FunctionExpression('exp', [this]); + } + + /** + * @beta + * Accesses a value from a map (object) field using the provided key. + * + * ```typescript + * // Get the 'city' value from the 'address' map field + * field("address").mapGet("city"); + * ``` + * + * @param subfield The key to access in the map. + * @return A new `Expr` representing the value associated with the given key in the map. + */ + mapGet(subfield: string): FunctionExpression { + return new FunctionExpression( + 'map_get', + [this, constant(subfield)], + 'mapGet' + ); + } + + /** + * @beta + * Creates an aggregation that counts the number of stage inputs with valid evaluations of the + * expression or field. + * + * ```typescript + * // Count the total number of products + * field("productId").count().as("totalProducts"); + * ``` + * + * @return A new `AggregateFunction` representing the 'count' aggregation. + */ + count(): AggregateFunction { + return AggregateFunction._create('count', [this], 'count'); + } + + /** + * @beta + * Creates an aggregation that calculates the sum of a numeric field across multiple stage inputs. + * + * ```typescript + * // Calculate the total revenue from a set of orders + * field("orderAmount").sum().as("totalRevenue"); + * ``` + * + * @return A new `AggregateFunction` representing the 'sum' aggregation. + */ + sum(): AggregateFunction { + return AggregateFunction._create('sum', [this], 'sum'); + } + + /** + * @beta + * Creates an aggregation that calculates the average (mean) of a numeric field across multiple + * stage inputs. + * + * ```typescript + * // Calculate the average age of users + * field("age").average().as("averageAge"); + * ``` + * + * @return A new `AggregateFunction` representing the 'average' aggregation. + */ + average(): AggregateFunction { + return AggregateFunction._create('average', [this], 'average'); + } + + /** + * @beta + * Creates an aggregation that finds the minimum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the lowest price of all products + * field("price").minimum().as("lowestPrice"); + * ``` + * + * @return A new `AggregateFunction` representing the 'minimum' aggregation. + */ + minimum(): AggregateFunction { + return AggregateFunction._create('minimum', [this], 'minimum'); + } + + /** + * @beta + * Creates an aggregation that finds the maximum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the highest score in a leaderboard + * field("score").maximum().as("highestScore"); + * ``` + * + * @return A new `AggregateFunction` representing the 'maximum' aggregation. + */ + maximum(): AggregateFunction { + return AggregateFunction._create('maximum', [this], 'maximum'); + } + + /** + * @beta + * Creates an aggregation that counts the number of distinct values of the expression or field. + * + * ```typescript + * // Count the distinct number of products + * field("productId").countDistinct().as("distinctProducts"); + * ``` + * + * @return A new `AggregateFunction` representing the 'count_distinct' aggregation. + */ + countDistinct(): AggregateFunction { + return AggregateFunction._create('count_distinct', [this], 'countDistinct'); + } + + /** + * @beta + * Creates an expression that returns the larger value between this expression and another expression, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the larger value between the 'timestamp' field and the current timestamp. + * field("timestamp").logicalMaximum(Function.currentTimestamp()); + * ``` + * + * @param second The second expression or literal to compare with. + * @param others Optional additional expressions or literals to compare with. + * @return A new {@code Expr} representing the logical maximum operation. + */ + logicalMaximum( + second: Expression | unknown, + ...others: Array + ): FunctionExpression { + const values = [second, ...others]; + return new FunctionExpression( + 'maximum', + [this, ...values.map(valueToDefaultExpr)], + 'logicalMaximum' + ); + } + + /** + * @beta + * Creates an expression that returns the smaller value between this expression and another expression, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the smaller value between the 'timestamp' field and the current timestamp. + * field("timestamp").logicalMinimum(Function.currentTimestamp()); + * ``` + * + * @param second The second expression or literal to compare with. + * @param others Optional additional expressions or literals to compare with. + * @return A new {@code Expr} representing the logical minimum operation. + */ + logicalMinimum( + second: Expression | unknown, + ...others: Array + ): FunctionExpression { + const values = [second, ...others]; + return new FunctionExpression( + 'minimum', + [this, ...values.map(valueToDefaultExpr)], + 'minimum' + ); + } + + /** + * @beta + * Creates an expression that calculates the length (number of dimensions) of this Firestore Vector expression. + * + * ```typescript + * // Get the vector length (dimension) of the field 'embedding'. + * field("embedding").vectorLength(); + * ``` + * + * @return A new {@code Expr} representing the length of the vector. + */ + vectorLength(): FunctionExpression { + return new FunctionExpression('vector_length', [this], 'vectorLength'); + } + + /** + * @beta + * Calculates the cosine distance between two vectors. + * + * ```typescript + * // Calculate the cosine distance between the 'userVector' field and the 'itemVector' field + * field("userVector").cosineDistance(field("itemVector")); + * ``` + * + * @param vectorExpression The other vector (represented as an Expr) to compare against. + * @return A new `Expr` representing the cosine distance between the two vectors. + */ + cosineDistance(vectorExpression: Expression): FunctionExpression; + /** + * @beta + * Calculates the Cosine distance between two vectors. + * + * ```typescript + * // Calculate the Cosine distance between the 'location' field and a target location + * field("location").cosineDistance(new VectorValue([37.7749, -122.4194])); + * ``` + * + * @param vector The other vector (as a VectorValue) to compare against. + * @return A new `Expr` representing the Cosine* distance between the two vectors. + */ + cosineDistance(vector: VectorValue | number[]): FunctionExpression; + cosineDistance( + other: Expression | VectorValue | number[] + ): FunctionExpression { + return new FunctionExpression( + 'cosine_distance', + [this, vectorToExpr(other)], + 'cosineDistance' + ); + } + + /** + * @beta + * Calculates the dot product between two vectors. + * + * ```typescript + * // Calculate the dot product between a feature vector and a target vector + * field("features").dotProduct([0.5, 0.8, 0.2]); + * ``` + * + * @param vectorExpression The other vector (as an array of numbers) to calculate with. + * @return A new `Expr` representing the dot product between the two vectors. + */ + dotProduct(vectorExpression: Expression): FunctionExpression; + + /** + * @beta + * Calculates the dot product between two vectors. + * + * ```typescript + * // Calculate the dot product between a feature vector and a target vector + * field("features").dotProduct(new VectorValue([0.5, 0.8, 0.2])); + * ``` + * + * @param vector The other vector (as an array of numbers) to calculate with. + * @return A new `Expr` representing the dot product between the two vectors. + */ + dotProduct(vector: VectorValue | number[]): FunctionExpression; + dotProduct(other: Expression | VectorValue | number[]): FunctionExpression { + return new FunctionExpression( + 'dot_product', + [this, vectorToExpr(other)], + 'dotProduct' + ); + } + + /** + * @beta + * Calculates the Euclidean distance between two vectors. + * + * ```typescript + * // Calculate the Euclidean distance between the 'location' field and a target location + * field("location").euclideanDistance([37.7749, -122.4194]); + * ``` + * + * @param vectorExpression The other vector (as an array of numbers) to calculate with. + * @return A new `Expr` representing the Euclidean distance between the two vectors. + */ + euclideanDistance(vectorExpression: Expression): FunctionExpression; + + /** + * @beta + * Calculates the Euclidean distance between two vectors. + * + * ```typescript + * // Calculate the Euclidean distance between the 'location' field and a target location + * field("location").euclideanDistance(new VectorValue([37.7749, -122.4194])); + * ``` + * + * @param vector The other vector (as a VectorValue) to compare against. + * @return A new `Expr` representing the Euclidean distance between the two vectors. + */ + euclideanDistance(vector: VectorValue | number[]): FunctionExpression; + euclideanDistance( + other: Expression | VectorValue | number[] + ): FunctionExpression { + return new FunctionExpression( + 'euclidean_distance', + [this, vectorToExpr(other)], + 'euclideanDistance' + ); + } + + /** + * @beta + * Creates an expression that interprets this expression as the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'microseconds' field as microseconds since epoch. + * field("microseconds").unixMicrosToTimestamp(); + * ``` + * + * @return A new {@code Expr} representing the timestamp. + */ + unixMicrosToTimestamp(): FunctionExpression { + return new FunctionExpression( + 'unix_micros_to_timestamp', + [this], + 'unixMicrosToTimestamp' + ); + } + + /** + * @beta + * Creates an expression that converts this timestamp expression to the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to microseconds since epoch. + * field("timestamp").timestampToUnixMicros(); + * ``` + * + * @return A new {@code Expr} representing the number of microseconds since epoch. + */ + timestampToUnixMicros(): FunctionExpression { + return new FunctionExpression( + 'timestamp_to_unix_micros', + [this], + 'timestampToUnixMicros' + ); + } + + /** + * @beta + * Creates an expression that interprets this expression as the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'milliseconds' field as milliseconds since epoch. + * field("milliseconds").unixMillisToTimestamp(); + * ``` + * + * @return A new {@code Expr} representing the timestamp. + */ + unixMillisToTimestamp(): FunctionExpression { + return new FunctionExpression( + 'unix_millis_to_timestamp', + [this], + 'unixMillisToTimestamp' + ); + } + + /** + * @beta + * Creates an expression that converts this timestamp expression to the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to milliseconds since epoch. + * field("timestamp").timestampToUnixMillis(); + * ``` + * + * @return A new {@code Expr} representing the number of milliseconds since epoch. + */ + timestampToUnixMillis(): FunctionExpression { + return new FunctionExpression( + 'timestamp_to_unix_millis', + [this], + 'timestampToUnixMillis' + ); + } + + /** + * @beta + * Creates an expression that interprets this expression as the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'seconds' field as seconds since epoch. + * field("seconds").unixSecondsToTimestamp(); + * ``` + * + * @return A new {@code Expr} representing the timestamp. + */ + unixSecondsToTimestamp(): FunctionExpression { + return new FunctionExpression( + 'unix_seconds_to_timestamp', + [this], + 'unixSecondsToTimestamp' + ); + } + + /** + * @beta + * Creates an expression that converts this timestamp expression to the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to seconds since epoch. + * field("timestamp").timestampToUnixSeconds(); + * ``` + * + * @return A new {@code Expr} representing the number of seconds since epoch. + */ + timestampToUnixSeconds(): FunctionExpression { + return new FunctionExpression( + 'timestamp_to_unix_seconds', + [this], + 'timestampToUnixSeconds' + ); + } + + /** + * @beta + * Creates an expression that adds a specified amount of time to this timestamp expression. + * + * ```typescript + * // Add some duration determined by field 'unit' and 'amount' to the 'timestamp' field. + * field("timestamp").timestampAdd(field("unit"), field("amount")); + * ``` + * + * @param unit The expression evaluates to unit of time, must be one of 'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'. + * @param amount The expression evaluates to amount of the unit. + * @return A new {@code Expr} representing the resulting timestamp. + */ + timestampAdd(unit: Expression, amount: Expression): FunctionExpression; + + /** + * @beta + * Creates an expression that adds a specified amount of time to this timestamp expression. + * + * ```typescript + * // Add 1 day to the 'timestamp' field. + * field("timestamp").timestampAdd("day", 1); + * ``` + * + * @param unit The unit of time to add (e.g., "day", "hour"). + * @param amount The amount of time to add. + * @return A new {@code Expr} representing the resulting timestamp. + */ + timestampAdd( + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number + ): FunctionExpression; + timestampAdd( + unit: + | Expression + | 'microsecond' + | 'millisecond' + | 'second' + | 'minute' + | 'hour' + | 'day', + amount: Expression | number + ): FunctionExpression { + return new FunctionExpression( + 'timestamp_add', + [this, valueToDefaultExpr(unit), valueToDefaultExpr(amount)], + 'timestampAdd' + ); + } + + /** + * @beta + * Creates an expression that subtracts a specified amount of time from this timestamp expression. + * + * ```typescript + * // Subtract some duration determined by field 'unit' and 'amount' from the 'timestamp' field. + * field("timestamp").timestampSubtract(field("unit"), field("amount")); + * ``` + * + * @param unit The expression evaluates to unit of time, must be one of 'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'. + * @param amount The expression evaluates to amount of the unit. + * @return A new {@code Expr} representing the resulting timestamp. + */ + timestampSubtract(unit: Expression, amount: Expression): FunctionExpression; + + /** + * @beta + * Creates an expression that subtracts a specified amount of time from this timestamp expression. + * + * ```typescript + * // Subtract 1 day from the 'timestamp' field. + * field("timestamp").timestampSubtract("day", 1); + * ``` + * + * @param unit The unit of time to subtract (e.g., "day", "hour"). + * @param amount The amount of time to subtract. + * @return A new {@code Expr} representing the resulting timestamp. + */ + timestampSubtract( + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number + ): FunctionExpression; + timestampSubtract( + unit: + | Expression + | 'microsecond' + | 'millisecond' + | 'second' + | 'minute' + | 'hour' + | 'day', + amount: Expression | number + ): FunctionExpression { + return new FunctionExpression( + 'timestamp_subtract', + [this, valueToDefaultExpr(unit), valueToDefaultExpr(amount)], + 'timestampSubtract' + ); + } + + /** + * @beta + * + * Creates an expression that returns the document ID from a path. + * + * ```typescript + * // Get the document ID from a path. + * field("__path__").documentId(); + * ``` + * + * @return A new {@code Expr} representing the documentId operation. + */ + documentId(): FunctionExpression { + return new FunctionExpression('document_id', [this], 'documentId'); + } + + /** + * @beta + * + * Creates an expression that returns a substring of the results of this expression. + * + * @param position Index of the first character of the substring. + * @param length Length of the substring. If not provided, the substring will + * end at the end of the input. + */ + substring(position: number, length?: number): FunctionExpression; + + /** + * @beta + * + * Creates an expression that returns a substring of the results of this expression. + * + * @param position An expression returning the index of the first character of the substring. + * @param length An expression returning the length of the substring. If not provided the + * substring will end at the end of the input. + */ + substring(position: Expression, length?: Expression): FunctionExpression; + substring( + position: Expression | number, + length?: Expression | number + ): FunctionExpression { + const positionExpr = valueToDefaultExpr(position); + if (length === undefined) { + return new FunctionExpression( + 'substring', + [this, positionExpr], + 'substring' + ); + } else { + return new FunctionExpression( + 'substring', + [this, positionExpr, valueToDefaultExpr(length)], + 'substring' + ); + } + } + + /** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and returns the element. If the offset exceeds the array length, an error is + * returned. A negative offset, starts from the end. + * + * ```typescript + * // Return the value in the 'tags' field array at index `1`. + * field('tags').arrayGet(1); + * ``` + * + * @param offset The index of the element to return. + * @return A new Expr representing the 'arrayGet' operation. + */ + arrayGet(offset: number): FunctionExpression; + + /** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and returns the element. If the offset exceeds the array length, an error is + * returned. A negative offset, starts from the end. + * + * ```typescript + * // Return the value in the tags field array at index specified by field + * // 'favoriteTag'. + * field('tags').arrayGet(field('favoriteTag')); + * ``` + * + * @param offsetExpr An Expr evaluating to the index of the element to return. + * @return A new Expr representing the 'arrayGet' operation. + */ + arrayGet(offsetExpr: Expression): FunctionExpression; + arrayGet(offset: Expression | number): FunctionExpression { + return new FunctionExpression( + 'array_get', + [this, valueToDefaultExpr(offset)], + 'arrayGet' + ); + } + + /** + * @beta + * + * Creates an expression that checks if a given expression produces an error. + * + * ```typescript + * // Check if the result of a calculation is an error + * field("title").arrayContains(1).isError(); + * ``` + * + * @return A new {@code BooleanExpr} representing the 'isError' check. + */ + isError(): BooleanExpression { + return new FunctionExpression('is_error', [this], 'isError').asBoolean(); + } + + /** + * @beta + * + * Creates an expression that returns the result of the `catchExpr` argument + * if there is an error, else return the result of this expression. + * + * ```typescript + * // Returns the first item in the title field arrays, or returns + * // the entire title field if the array is empty or the field is another type. + * field("title").arrayGet(0).ifError(field("title")); + * ``` + * + * @param catchExpr The catch expression that will be evaluated and + * returned if this expression produces an error. + * @return A new {@code Expr} representing the 'ifError' operation. + */ + ifError(catchExpr: Expression): FunctionExpression; + + /** + * @beta + * + * Creates an expression that returns the `catch` argument if there is an + * error, else return the result of this expression. + * + * ```typescript + * // Returns the first item in the title field arrays, or returns + * // "Default Title" + * field("title").arrayGet(0).ifError("Default Title"); + * ``` + * + * @param catchValue The value that will be returned if this expression + * produces an error. + * @return A new {@code Expr} representing the 'ifError' operation. + */ + ifError(catchValue: unknown): FunctionExpression; + ifError(catchValue: unknown): FunctionExpression | BooleanExpression { + const result = new FunctionExpression( + 'if_error', + [this, valueToDefaultExpr(catchValue)], + 'ifError' + ); + + return catchValue instanceof BooleanExpression + ? result.asBoolean() + : result; + } + + /** + * @beta + * + * Creates an expression that returns `true` if the result of this expression + * is absent. Otherwise, returns `false` even if the value is `null`. + * + * ```typescript + * // Check if the field `value` is absent. + * field("value").isAbsent(); + * ``` + * + * @return A new {@code BooleanExpr} representing the 'isAbsent' check. + */ + isAbsent(): BooleanExpression { + return new FunctionExpression('is_absent', [this], 'isAbsent').asBoolean(); + } + + /** + * @beta + * + * Creates an expression that removes a key from the map produced by evaluating this expression. + * + * ``` + * // Removes the key 'baz' from the input map. + * map({foo: 'bar', baz: true}).mapRemove('baz'); + * ``` + * + * @param key The name of the key to remove from the input map. + * @returns A new {@code FirestoreFunction} representing the 'mapRemove' operation. + */ + mapRemove(key: string): FunctionExpression; + /** + * @beta + * + * Creates an expression that removes a key from the map produced by evaluating this expression. + * + * ``` + * // Removes the key 'baz' from the input map. + * map({foo: 'bar', baz: true}).mapRemove(constant('baz')); + * ``` + * + * @param keyExpr An expression that produces the name of the key to remove from the input map. + * @returns A new {@code FirestoreFunction} representing the 'mapRemove' operation. + */ + mapRemove(keyExpr: Expression): FunctionExpression; + mapRemove(stringExpr: Expression | string): FunctionExpression { + return new FunctionExpression( + 'map_remove', + [this, valueToDefaultExpr(stringExpr)], + 'mapRemove' + ); + } + + /** + * @beta + * + * Creates an expression that merges multiple map values. + * + * ``` + * // Merges the map in the settings field with, a map literal, and a map in + * // that is conditionally returned by another expression + * field('settings').mapMerge({ enabled: true }, conditional(field('isAdmin'), { admin: true}, {}) + * ``` + * + * @param secondMap A required second map to merge. Represented as a literal or + * an expression that returns a map. + * @param otherMaps Optional additional maps to merge. Each map is represented + * as a literal or an expression that returns a map. + * + * @returns A new {@code FirestoreFunction} representing the 'mapMerge' operation. + */ + mapMerge( + secondMap: Record | Expression, + ...otherMaps: Array | Expression> + ): FunctionExpression { + const secondMapExpr = valueToDefaultExpr(secondMap); + const otherMapExprs = otherMaps.map(valueToDefaultExpr); + return new FunctionExpression( + 'map_merge', + [this, secondMapExpr, ...otherMapExprs], + 'mapMerge' + ); + } + + /** + * @beta + * Creates an expression that returns the value of this expression raised to the power of another expression. + * + * ```typescript + * // Raise the value of the 'base' field to the power of the 'exponent' field. + * field("base").pow(field("exponent")); + * ``` + * + * @param exponent The expression to raise this expression to the power of. + * @return A new `Expr` representing the power operation. + */ + pow(exponent: Expression): FunctionExpression; + + /** + * @beta + * Creates an expression that returns the value of this expression raised to the power of a constant value. + * + * ```typescript + * // Raise the value of the 'base' field to the power of 2. + * field("base").pow(2); + * ``` + * + * @param exponent The constant value to raise this expression to the power of. + * @return A new `Expr` representing the power operation. + */ + pow(exponent: number): FunctionExpression; + pow(exponent: number | Expression): FunctionExpression { + return new FunctionExpression('pow', [this, valueToDefaultExpr(exponent)]); + } + + /** + * @beta + * Creates an expression that rounds a numeric value to the nearest whole number. + * + * ```typescript + * // Round the value of the 'price' field. + * field("price").round(); + * ``` + * + * @return A new `Expr` representing the rounded value. + */ + round(): FunctionExpression; + /** + * @beta + * Creates an expression that rounds a numeric value to the specified number of decimal places. + * + * ```typescript + * // Round the value of the 'price' field to two decimal places. + * field("price").round(2); + * ``` + * + * @param decimalPlaces A constant specifying the rounding precision in decimal places. + * + * @return A new `Expr` representing the rounded value. + */ + round(decimalPlaces: number): FunctionExpression; + /** + * @beta + * Creates an expression that rounds a numeric value to the specified number of decimal places. + * + * ```typescript + * // Round the value of the 'price' field to two decimal places. + * field("price").round(constant(2)); + * ``` + * + * @param decimalPlaces An expression specifying the rounding precision in decimal places. + * + * @return A new `Expr` representing the rounded value. + */ + round(decimalPlaces: Expression): FunctionExpression; + round(decimalPlaces?: number | Expression): FunctionExpression { + if (decimalPlaces === undefined) { + return new FunctionExpression('round', [this]); + } else { + return new FunctionExpression( + 'round', + [this, valueToDefaultExpr(decimalPlaces)], + 'round' + ); + } + } + + /** + * @beta + * Creates an expression that returns the collection ID from a path. + * + * ```typescript + * // Get the collection ID from a path. + * field("__path__").collectionId(); + * ``` + * + * @return A new {@code Expr} representing the collectionId operation. + */ + collectionId(): FunctionExpression { + return new FunctionExpression('collection_id', [this]); + } + + /** + * @beta + * Creates an expression that calculates the length of a string, array, map, vector, or bytes. + * + * ```typescript + * // Get the length of the 'name' field. + * field("name").length(); + * + * // Get the number of items in the 'cart' array. + * field("cart").length(); + * ``` + * + * @return A new `Expr` representing the length of the string, array, map, vector, or bytes. + */ + length(): FunctionExpression { + return new FunctionExpression('length', [this]); + } + + /** + * @beta + * Creates an expression that computes the natural logarithm of a numeric value. + * + * ```typescript + * // Compute the natural logarithm of the 'value' field. + * field("value").ln(); + * ``` + * + * @return A new {@code Expr} representing the natural logarithm of the numeric value. + */ + ln(): FunctionExpression { + return new FunctionExpression('ln', [this]); + } + + /** + * @beta + * Creates an expression that computes the square root of a numeric value. + * + * ```typescript + * // Compute the square root of the 'value' field. + * field("value").sqrt(); + * ``` + * + * @return A new {@code Expr} representing the square root of the numeric value. + */ + sqrt(): FunctionExpression { + return new FunctionExpression('sqrt', [this]); + } + + /** + * @beta + * Creates an expression that reverses a string. + * + * ```typescript + * // Reverse the value of the 'myString' field. + * field("myString").stringReverse(); + * ``` + * + * @return A new {@code Expr} representing the reversed string. + */ + stringReverse(): FunctionExpression { + return new FunctionExpression('string_reverse', [this]); + } + + /** + * @beta + * Creates an expression that returns the `elseValue` argument if this expression results in an absent value, else + * return the result of the this expression evaluation. + * + * ```typescript + * // Returns the value of the optional field 'optional_field', or returns 'default_value' + * // if the field is absent. + * field("optional_field").ifAbsent("default_value") + * ``` + * + * @param elseValue The value that will be returned if this Expression evaluates to an absent value. + * @return A new [Expression] representing the ifAbsent operation. + */ + ifAbsent(elseValue: unknown): Expression; + + /** + * @beta + * Creates an expression that returns the `elseValue` argument if this expression results in an absent value, else + * return the result of this expression evaluation. + * + * ```typescript + * // Returns the value of the optional field 'optional_field', or if that is + * // absent, then returns the value of the field ` + * field("optional_field").ifAbsent(field('default_field')) + * ``` + * + * @param elseExpression The Expression that will be evaluated if this Expression evaluates to an absent value. + * @return A new [Expression] representing the ifAbsent operation. + */ + ifAbsent(elseExpression: unknown): Expression; + + ifAbsent(elseValueOrExpression: Expression | unknown): Expression { + return new FunctionExpression( + 'if_absent', + [this, valueToDefaultExpr(elseValueOrExpression)], + 'ifAbsent' + ); + } + + /** + * @beta + * Creates an expression that joins the elements of an array into a string. + * + * ```typescript + * // Join the elements of the 'tags' field with the delimiter from the 'separator' field. + * field("tags").join(field("separator")) + * ``` + * + * @param delimiterExpression The expression that evaluates to the delimiter string. + * @return A new Expression representing the join operation. + */ + join(delimiterExpression: Expression): Expression; + + /** + * @beta + * Creates an expression that joins the elements of an array field into a string. + * + * ```typescript + * // Join the elements of the 'tags' field with a comma and space. + * field("tags").join(", ") + * ``` + * + * @param delimiter The string to use as a delimiter. + * @return A new Expression representing the join operation. + */ + join(delimiter: string): Expression; + + join(delimeterValueOrExpression: string | Expression): Expression { + return new FunctionExpression( + 'join', + [this, valueToDefaultExpr(delimeterValueOrExpression)], + 'join' + ); + } + + /** + * @beta + * Creates an expression that computes the base-10 logarithm of a numeric value. + * + * ```typescript + * // Compute the base-10 logarithm of the 'value' field. + * field("value").log10(); + * ``` + * + * @return A new {@code Expr} representing the base-10 logarithm of the numeric value. + */ + log10(): FunctionExpression { + return new FunctionExpression('log10', [this]); + } + + /** + * @beta + * Creates an expression that computes the sum of the elements in an array. + * + * ```typescript + * // Compute the sum of the elements in the 'scores' field. + * field("scores").arraySum(); + * ``` + * + * @return A new {@code Expr} representing the sum of the elements in the array. + */ + arraySum(): FunctionExpression { + return new FunctionExpression('sum', [this]); + } + + /** + * @beta + * Creates an expression that splits the result of this expression into an + * array of substrings based on the provided delimiter. + * + * @example + * ```typescript + * // Split the 'scoresCsv' field on delimiter ',' + * field('scoresCsv').split(',') + * ``` + * + * @return A new {@code Expression} representing the split function. + */ + split(delimiter: string): FunctionExpression; + + /** + * @beta + * Creates an expression that splits the result of this expression into an + * array of substrings based on the provided delimiter. + * + * @example + * ```typescript + * // Split the 'scores' field on delimiter ',' or ':' depending on the stored format + * field('scores').split(conditional(field('format').equal('csv'), constant(','), constant(':')) + * ``` + * + * @return A new {@code Expression} representing the split function. + */ + split(delimiter: Expression): FunctionExpression; + split(delimiter: string | Expression): FunctionExpression { + return new FunctionExpression('split', [ + this, + valueToDefaultExpr(delimiter) + ]); + } + + /** + * Creates an expression that truncates a timestamp to a specified granularity. + * + * @example + * ```typescript + * // Truncate the 'createdAt' timestamp to the beginning of the day. + * field('createdAt').timestampTruncate('day') + * ``` + * + * @param granularity The granularity to truncate to. + * @param timezone The timezone to use for truncation. Valid values are from + * the TZ database (e.g., "America/Los_Angeles") or in the format "Etc/GMT-1". + * @return A new {Expression} representing the truncated timestamp. + */ + timestampTruncate( + granularity: TimeGranularity, + timezone?: string | Expression + ): FunctionExpression; + + /** + * Creates an expression that truncates a timestamp to a specified granularity. + * + * @example + * ```typescript + * // Truncate the 'createdAt' timestamp to the granularity specified in the field 'granularity'. + * field('createdAt').timestampTruncate(field('granularity')) + * ``` + * + * @param granularity The granularity to truncate to. + * @param timezone The timezone to use for truncation. Valid values are from + * the TZ database (e.g., "America/Los_Angeles") or in the format "Etc/GMT-1". + * @return A new {Expression} representing the truncated timestamp. + */ + timestampTruncate( + granularity: Expression, + timezone?: string | Expression + ): FunctionExpression; + timestampTruncate( + granularity: TimeGranularity | Expression, + timezone?: string | Expression + ): FunctionExpression { + const internalGranularity = isString(granularity) + ? granularity.toLowerCase() + : granularity; + + const args = [this, valueToDefaultExpr(internalGranularity)]; + if (timezone) { + args.push(valueToDefaultExpr(timezone)); + } + return new FunctionExpression('timestamp_trunc', args); + } + + /** + * @beta + * Creates an expression that returns the data type of this expression's result, as a string. + * + * @example + * ```typescript + * // Get the data type of the value in field 'title' + * field('title').type() + * ``` + * + * @return A new {Expression} representing the data type. + */ + type(): FunctionExpression { + return new FunctionExpression('type', [this]); + } + + // TODO(new-expression): Add new expression method definitions above this line + + /** + * @beta + * Creates an {@link Ordering} that sorts documents in ascending order based on this expression. + * + * ```typescript + * // Sort documents by the 'name' field in ascending order + * pipeline().collection("users") + * .sort(field("name").ascending()); + * ``` + * + * @return A new `Ordering` for ascending sorting. + */ + ascending(): Ordering { + return ascending(this); + } + + /** + * @beta + * Creates an {@link Ordering} that sorts documents in descending order based on this expression. + * + * ```typescript + * // Sort documents by the 'createdAt' field in descending order + * firestore.pipeline().collection("users") + * .sort(field("createdAt").descending()); + * ``` + * + * @return A new `Ordering` for descending sorting. + */ + descending(): Ordering { + return descending(this); + } + + /** + * @beta + * Assigns an alias to this expression. + * + * Aliases are useful for renaming fields in the output of a stage or for giving meaningful + * names to calculated values. + * + * ```typescript + * // Calculate the total price and assign it the alias "totalPrice" and add it to the output. + * firestore.pipeline().collection("items") + * .addFields(field("price").multiply(field("quantity")).as("totalPrice")); + * ``` + * + * @param name The alias to assign to this expression. + * @return A new {@link AliasedExpression} that wraps this + * expression and associates it with the provided alias. + */ + as(name: string): AliasedExpression { + return new AliasedExpression(this, name, 'as'); + } +} + +/** + * @beta + * Specify time granularity for expressions. + */ +export type TimeGranularity = + | 'microsecond' + | 'millisecond' + | 'second' + | 'minute' + | 'hour' + | 'day' + | 'week' + | 'week(monday)' + | 'week(tuesday)' + | 'week(wednesday)' + | 'week(thursday)' + | 'week(friday)' + | 'week(saturday)' + | 'week(sunday)' + | 'isoWeek' + | 'month' + | 'quarter' + | 'year' + | 'isoYear'; + +/** + * @beta + * + * An interface that represents a selectable expression. + */ +export interface Selectable { + selectable: true; + /** + * @private + * @internal + */ + readonly alias: string; + /** + * @private + * @internal + */ + readonly expr: Expression; +} + +/** + * @beta + * + * A class that represents an aggregate function. + */ +export class AggregateFunction implements ProtoValueSerializable, UserData { + exprType: ExpressionType = 'AggregateFunction'; + + /** + * @internal + */ + _methodName?: string; + + constructor(private name: string, private params: Expression[]) {} + + /** + * @internal + * @private + */ + static _create( + name: string, + params: Expression[], + methodName: string + ): AggregateFunction { + const af = new AggregateFunction(name, params); + af._methodName = methodName; + + return af; + } + + /** + * @beta + * Assigns an alias to this AggregateFunction. The alias specifies the name that + * the aggregated value will have in the output document. + * + * ```typescript + * // Calculate the average price of all items and assign it the alias "averagePrice". + * firestore.pipeline().collection("items") + * .aggregate(field("price").average().as("averagePrice")); + * ``` + * + * @param name The alias to assign to this AggregateFunction. + * @return A new {@link AliasedAggregate} that wraps this + * AggregateFunction and associates it with the provided alias. + */ + as(name: string): AliasedAggregate { + return new AliasedAggregate(this, name, 'as'); + } + + /** + * @private + * @internal + */ + _toProto(serializer: JsonProtoSerializer): ProtoValue { + return { + functionValue: { + name: this.name, + args: this.params.map(p => p._toProto(serializer)) + } + }; + } + + _protoValueType = 'ProtoValue' as const; + + /** + * @private + * @internal + */ + _readUserData(context: ParseContext): void { + context = this._methodName + ? context.contextWith({ methodName: this._methodName }) + : context; + this.params.forEach(expr => { + return expr._readUserData(context); + }); + } +} + +/** + * @beta + * + * An AggregateFunction with alias. + */ +export class AliasedAggregate implements UserData { + constructor( + readonly aggregate: AggregateFunction, + readonly alias: string, + readonly _methodName: string | undefined + ) {} + + /** + * @private + * @internal + */ + _readUserData(context: ParseContext): void { + this.aggregate._readUserData(context); + } +} + +/** + * @beta + */ +export class AliasedExpression implements Selectable, UserData { + exprType: ExpressionType = 'AliasedExpression'; + selectable = true as const; + + constructor( + readonly expr: Expression, + readonly alias: string, + readonly _methodName: string | undefined + ) {} + + /** + * @private + * @internal + */ + _readUserData(context: ParseContext): void { + this.expr._readUserData(context); + } +} + +/** + * @internal + */ +class ListOfExprs extends Expression implements UserData { + expressionType: ExpressionType = 'ListOfExpressions'; + + constructor( + private exprs: Expression[], + readonly _methodName: string | undefined + ) { + super(); + } + + /** + * @private + * @internal + */ + _toProto(serializer: JsonProtoSerializer): ProtoValue { + return { + arrayValue: { + values: this.exprs.map(p => p._toProto(serializer)!) + } + }; + } + + /** + * @private + * @internal + */ + _readUserData(context: ParseContext): void { + this.exprs.forEach((expr: Expression) => expr._readUserData(context)); + } +} + +/** + * @beta + * + * Represents a reference to a field in a Firestore document, or outputs of a {@link Pipeline} stage. + * + *

Field references are used to access document field values in expressions and to specify fields + * for sorting, filtering, and projecting data in Firestore pipelines. + * + *

You can create a `Field` instance using the static {@link #of} method: + * + * ```typescript + * // Create a Field instance for the 'name' field + * const nameField = field("name"); + * + * // Create a Field instance for a nested field 'address.city' + * const cityField = field("address.city"); + * ``` + */ +export class Field extends Expression implements Selectable { + readonly expressionType: ExpressionType = 'Field'; + selectable = true as const; + + /** + * @internal + * @private + * @hideconstructor + * @param fieldPath + */ + constructor( + private fieldPath: InternalFieldPath, + readonly _methodName: string | undefined + ) { + super(); + } + + get fieldName(): string { + return this.fieldPath.canonicalString(); + } + + get alias(): string { + return this.fieldName; + } + + get expr(): Expression { + return this; + } + + /** + * @private + * @internal + */ + _toProto(serializer: JsonProtoSerializer): ProtoValue { + return { + fieldReferenceValue: this.fieldPath.canonicalString() + }; + } + + /** + * @private + * @internal + */ + _readUserData(context: ParseContext): void {} +} + +/** + * @beta + * Creates a {@code Field} instance representing the field at the given path. + * + * The path can be a simple field name (e.g., "name") or a dot-separated path to a nested field + * (e.g., "address.city"). + * + * ```typescript + * // Create a Field instance for the 'title' field + * const titleField = field("title"); + * + * // Create a Field instance for a nested field 'author.firstName' + * const authorFirstNameField = field("author.firstName"); + * ``` + * + * @param name The path to the field. + * @return A new {@code Field} instance representing the specified field. + */ +export function field(name: string): Field; + +/** + * @beta + * Creates a {@code Field} instance representing the field at the given path. + * + * @param path A FieldPath specifying the field. + * @return A new {@code Field} instance representing the specified field. + */ +export function field(path: FieldPath): Field; +export function field(nameOrPath: string | FieldPath): Field { + return _field(nameOrPath, 'field'); +} + +export function _field( + nameOrPath: string | FieldPath, + methodName: string | undefined +): Field { + if (typeof nameOrPath === 'string') { + if (DOCUMENT_KEY_NAME === nameOrPath) { + return new Field(documentIdFieldPath()._internalPath, methodName); + } + return new Field(fieldPathFromArgument('field', nameOrPath), methodName); + } else { + return new Field(nameOrPath._internalPath, methodName); + } +} + +/** + * @internal + * + * Represents a constant value that can be used in a Firestore pipeline expression. + * + * You can create a `Constant` instance using the static {@link #of} method: + * + * ```typescript + * // Create a Constant instance for the number 10 + * const ten = constant(10); + * + * // Create a Constant instance for the string "hello" + * const hello = constant("hello"); + * ``` + */ +export class Constant extends Expression { + readonly expressionType: ExpressionType = 'Constant'; + + private _protoValue?: ProtoValue; + + /** + * @private + * @internal + * @hideconstructor + * @param value The value of the constant. + */ + constructor( + private value: unknown, + readonly _methodName: string | undefined + ) { + super(); + } + + /** + * @private + * @internal + */ + static _fromProto(value: ProtoValue): Constant { + const result = new Constant(value, undefined); + result._protoValue = value; + return result; + } + + /** + * @private + * @internal + */ + _toProto(_: JsonProtoSerializer): ProtoValue { + hardAssert( + this._protoValue !== undefined, + 0x00ed, + 'Value of this constant has not been serialized to proto value' + ); + return this._protoValue; + } + + /** + * @private + * @internal + */ + _readUserData(context: ParseContext): void { + context = this._methodName + ? context.contextWith({ methodName: this._methodName }) + : context; + if (isFirestoreValue(this._protoValue)) { + return; + } else { + this._protoValue = parseData(this.value, context)!; + } + } +} + +/** + * @beta + * Creates a `Constant` instance for a number value. + * + * @param value The number value. + * @return A new `Constant` instance. + */ +export function constant(value: number): Expression; + +/** + * @beta + * Creates a `Constant` instance for a string value. + * + * @param value The string value. + * @return A new `Constant` instance. + */ +export function constant(value: string): Expression; + +/** + * @beta + * Creates a `BooleanExpression` instance for a boolean value. + * + * @param value The boolean value. + * @return A new `Constant` instance. + */ +export function constant(value: boolean): BooleanExpression; + +/** + * @beta + * Creates a `Constant` instance for a null value. + * + * @param value The null value. + * @return A new `Constant` instance. + */ +export function constant(value: null): Expression; + +/** + * @beta + * Creates a `Constant` instance for a GeoPoint value. + * + * @param value The GeoPoint value. + * @return A new `Constant` instance. + */ +export function constant(value: GeoPoint): Expression; + +/** + * @beta + * Creates a `Constant` instance for a Timestamp value. + * + * @param value The Timestamp value. + * @return A new `Constant` instance. + */ +export function constant(value: Timestamp): Expression; + +/** + * @beta + * Creates a `Constant` instance for a Date value. + * + * @param value The Date value. + * @return A new `Constant` instance. + */ +export function constant(value: Date): Expression; + +/** + * @beta + * Creates a `Constant` instance for a Bytes value. + * + * @param value The Bytes value. + * @return A new `Constant` instance. + */ +export function constant(value: Bytes): Expression; + +/** + * @beta + * Creates a `Constant` instance for a DocumentReference value. + * + * @param value The DocumentReference value. + * @return A new `Constant` instance. + */ +export function constant(value: DocumentReference): Expression; + +/** + * Creates a `Constant` instance for a Firestore proto value. + * For internal use only. + * @private + * @internal + * @param value The Firestore proto value. + * @return A new `Constant` instance. + */ +export function constant(value: ProtoValue): Expression; + +/** + * @beta + * Creates a `Constant` instance for a VectorValue value. + * + * @param value The VectorValue value. + * @return A new `Constant` instance. + */ +export function constant(value: VectorValue): Expression; + +export function constant(value: unknown): Expression | BooleanExpression { + return _constant(value, 'constant'); +} + +/** + * @internal + * @private + * @param value + * @param methodName + */ +export function _constant( + value: unknown, + methodName: string | undefined +): Constant | BooleanExpression { + const c = new Constant(value, methodName); + if (typeof value === 'boolean') { + return new BooleanConstant(c); + } else { + return c; + } +} + +/** + * Internal only + * @internal + * @private + */ +export class MapValue extends Expression { + constructor( + private plainObject: Map, + readonly _methodName: string | undefined + ) { + super(); + } + + expressionType: ExpressionType = 'Constant'; + + _readUserData(context: ParseContext): void { + context = this._methodName + ? context.contextWith({ methodName: this._methodName }) + : context; + this.plainObject.forEach(expr => { + expr._readUserData(context); + }); + } + + _toProto(serializer: JsonProtoSerializer): ProtoValue { + return toMapValue(serializer, this.plainObject); + } +} + +/** + * @beta + * + * This class defines the base class for Firestore {@link Pipeline} functions, which can be evaluated within pipeline + * execution. + * + * Typically, you would not use this class or its children directly. Use either the functions like {@link and}, {@link equal}, + * or the methods on {@link Expression} ({@link Expression#equal}, {@link Expression#lessThan}, etc.) to construct new Function instances. + */ +export class FunctionExpression extends Expression { + readonly expressionType: ExpressionType = 'Function'; + + constructor(name: string, params: Expression[]); + constructor( + name: string, + params: Expression[], + _methodName: string | undefined + ); + constructor( + private name: string, + private params: Expression[], + readonly _methodName?: string + ) { + super(); + } + + /** + * @private + * @internal + */ + _toProto(serializer: JsonProtoSerializer): ProtoValue { + return { + functionValue: { + name: this.name, + args: this.params.map(p => p._toProto(serializer)) + } + }; + } + + /** + * @private + * @internal + */ + _readUserData(context: ParseContext): void { + context = this._methodName + ? context.contextWith({ methodName: this._methodName }) + : context; + this.params.forEach(expr => { + return expr._readUserData(context); + }); + } +} + +/** + * @beta + * + * An interface that represents a filter condition. + */ +export abstract class BooleanExpression extends Expression { + abstract get _expr(): Expression; + + get _methodName(): string | undefined { + return this._expr._methodName; + } + + /** + * @beta + * Creates an aggregation that finds the count of input documents satisfying + * this boolean expression. + * + * ```typescript + * // Find the count of documents with a score greater than 90 + * field("score").greaterThan(90).countIf().as("highestScore"); + * ``` + * + * @return A new `AggregateFunction` representing the 'countIf' aggregation. + */ + countIf(): AggregateFunction { + return AggregateFunction._create('count_if', [this], 'countIf'); + } + + /** + * @beta + * Creates an expression that negates this boolean expression. + * + * ```typescript + * // Find documents where the 'tags' field does not contain 'completed' + * field("tags").arrayContains("completed").not(); + * ``` + * + * @return A new {@code Expr} representing the negated filter condition. + */ + not(): BooleanExpression { + return new FunctionExpression('not', [this], 'not').asBoolean(); + } + + /** + * @beta + * Creates a conditional expression that evaluates to the 'then' expression + * if `this` expression evaluates to `true`, + * or evaluates to the 'else' expression if `this` expressions evaluates `false`. + * + * ```typescript + * // If 'age' is greater than 18, return "Adult"; otherwise, return "Minor". + * field("age").greaterThanOrEqual(18).conditional(constant("Adult"), constant("Minor")); + * ``` + * + * @param thenExpr The expression to evaluate if the condition is true. + * @param elseExpr The expression to evaluate if the condition is false. + * @return A new {@code Expr} representing the conditional expression. + */ + conditional(thenExpr: Expression, elseExpr: Expression): FunctionExpression { + return new FunctionExpression( + 'conditional', + [this, thenExpr, elseExpr], + 'conditional' + ); + } + + /** + * @beta + * + * Creates an expression that returns the `catch` argument if there is an + * error, else return the result of this expression. + * + * ```typescript + * // Create an expression that protects against a divide by zero error + * // but always returns a boolean expression. + * constant(50).divide('length').gt(1).ifError(constant(false)); + * ``` + * + * @param catchValue The value that will be returned if this expression + * produces an error. + * @return A new {@code Expr} representing the 'ifError' operation. + */ + ifError(catchValue: BooleanExpression): BooleanExpression; + + /** + * @beta + * + * Creates an expression that returns the `catch` argument if there is an + * error, else return the result of this expression. + * + * ```typescript + * // Create an expression that protects against a divide by zero error + * // but always returns a boolean expression. + * constant(50).divide('length').gt(1).ifError(false); + * ``` + * + * @param catchValue The value that will be returned if this expression + * produces an error. + * @return A new {@code Expr} representing the 'ifError' operation. + */ + ifError(catchValue: boolean): BooleanExpression; + + /** + * @beta + * + * Creates an expression that returns the `catch` argument if there is an + * error, else return the result of this expression. + * + * ```typescript + * // Create an expression that protects against a divide by zero error. + * constant(50).divide('length').gt(1).ifError(constant(0)); + * ``` + * + * @param catchValue The value that will be returned if this expression + * produces an error. + * @return A new {@code Expr} representing the 'ifError' operation. + */ + ifError(catchValue: Expression): FunctionExpression; + + /** + * @beta + * + * Creates an expression that returns the `catch` argument if there is an + * error, else return the result of this expression. + * + * ```typescript + * // Create an expression that protects against a divide by zero error. + * constant(50).divide('length').gt(1).ifError(0); + * ``` + * + * @param catchValue The value that will be returned if this expression + * produces an error. + * @return A new {@code Expr} representing the 'ifError' operation. + */ + ifError(catchValue: unknown): FunctionExpression; + ifError(catchValue: unknown): unknown { + const normalizedCatchValue = valueToDefaultExpr(catchValue); + const expr = new FunctionExpression( + 'if_error', + [this, normalizedCatchValue], + 'ifError' + ); + + return normalizedCatchValue instanceof BooleanExpression + ? expr.asBoolean() + : expr; + } + + /** + * @private + * @internal + */ + _toProto(serializer: JsonProtoSerializer): ProtoValue { + return this._expr._toProto(serializer); + } + + /** + * @private + * @internal + */ + _readUserData(context: ParseContext): void { + this._expr._readUserData(context); + } +} + +export class BooleanFunctionExpression extends BooleanExpression { + readonly expressionType: ExpressionType = 'Function'; + constructor(readonly _expr: FunctionExpression) { + super(); + } +} + +export class BooleanConstant extends BooleanExpression { + readonly expressionType: ExpressionType = 'Constant'; + constructor(readonly _expr: Constant) { + super(); + } +} + +export class BooleanField extends BooleanExpression { + readonly expressionType: ExpressionType = 'Field'; + constructor(readonly _expr: Field) { + super(); + } +} + +/** + * @beta + * Creates an aggregation that counts the number of stage inputs where the provided + * boolean expression evaluates to true. + * + * ```typescript + * // Count the number of documents where 'is_active' field equals true + * countIf(field("is_active").equal(true)).as("numActiveDocuments"); + * ``` + * + * @param booleanExpr - The boolean expression to evaluate on each input. + * @returns A new `AggregateFunction` representing the 'countIf' aggregation. + */ +export function countIf(booleanExpr: BooleanExpression): AggregateFunction { + return booleanExpr.countIf(); +} + +/** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the offset exceeds the array length, an error is + * returned. A negative offset, starts from the end. + * + * ```typescript + * // Return the value in the tags field array at index 1. + * arrayGet('tags', 1); + * ``` + * + * @param arrayField The name of the array field. + * @param offset The index of the element to return. + * @return A new Expr representing the 'arrayGet' operation. + */ +export function arrayGet( + arrayField: string, + offset: number +): FunctionExpression; + +/** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the offset exceeds the array length, an error is + * returned. A negative offset, starts from the end. + * + * ```typescript + * // Return the value in the tags field array at index specified by field + * // 'favoriteTag'. + * arrayGet('tags', field('favoriteTag')); + * ``` + * + * @param arrayField The name of the array field. + * @param offsetExpr An Expr evaluating to the index of the element to return. + * @return A new Expr representing the 'arrayGet' operation. + */ +export function arrayGet( + arrayField: string, + offsetExpr: Expression +): FunctionExpression; + +/** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the offset exceeds the array length, an error is + * returned. A negative offset, starts from the end. + * + * ```typescript + * // Return the value in the tags field array at index 1. + * arrayGet(field('tags'), 1); + * ``` + * + * @param arrayExpression An Expr evaluating to an array. + * @param offset The index of the element to return. + * @return A new Expr representing the 'arrayGet' operation. + */ +export function arrayGet( + arrayExpression: Expression, + offset: number +): FunctionExpression; + +/** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the offset exceeds the array length, an error is + * returned. A negative offset, starts from the end. + * + * ```typescript + * // Return the value in the tags field array at index specified by field + * // 'favoriteTag'. + * arrayGet(field('tags'), field('favoriteTag')); + * ``` + * + * @param arrayExpression An Expr evaluating to an array. + * @param offsetExpr An Expr evaluating to the index of the element to return. + * @return A new Expr representing the 'arrayGet' operation. + */ +export function arrayGet( + arrayExpression: Expression, + offsetExpr: Expression +): FunctionExpression; +export function arrayGet( + array: Expression | string, + offset: Expression | number +): FunctionExpression { + return fieldOrExpression(array).arrayGet(valueToDefaultExpr(offset)); +} + +/** + * @beta + * + * Creates an expression that checks if a given expression produces an error. + * + * ```typescript + * // Check if the result of a calculation is an error + * isError(field("title").arrayContains(1)); + * ``` + * + * @param value The expression to check. + * @return A new {@code Expr} representing the 'isError' check. + */ +export function isError(value: Expression): BooleanExpression { + return value.isError().asBoolean(); +} + +/** + * @beta + * + * Creates an expression that returns the `catch` argument if there is an + * error, else return the result of the `try` argument evaluation. + * + * This overload is useful when a BooleanExpression is required. + * + * ```typescript + * // Create an expression that protects against a divide by zero error + * // but always returns a boolean expression. + * ifError(constant(50).divide('length').gt(1), constant(false)); + * ``` + * + * @param tryExpr The try expression. + * @param catchExpr The catch expression that will be evaluated and + * returned if the tryExpr produces an error. + * @return A new {@code Expr} representing the 'ifError' operation. + */ +export function ifError( + tryExpr: BooleanExpression, + catchExpr: BooleanExpression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that returns the `catch` argument if there is an + * error, else return the result of the `try` argument evaluation. + * + * ```typescript + * // Returns the first item in the title field arrays, or returns + * // the entire title field if the array is empty or the field is another type. + * ifError(field("title").arrayGet(0), field("title")); + * ``` + * + * @param tryExpr The try expression. + * @param catchExpr The catch expression that will be evaluated and + * returned if the tryExpr produces an error. + * @return A new {@code Expr} representing the 'ifError' operation. + */ +export function ifError( + tryExpr: Expression, + catchExpr: Expression +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that returns the `catch` argument if there is an + * error, else return the result of the `try` argument evaluation. + * + * ```typescript + * // Returns the first item in the title field arrays, or returns + * // "Default Title" + * ifError(field("title").arrayGet(0), "Default Title"); + * ``` + * + * @param tryExpr The try expression. + * @param catchValue The value that will be returned if the tryExpr produces an + * error. + * @return A new {@code Expr} representing the 'ifError' operation. + */ +export function ifError( + tryExpr: Expression, + catchValue: unknown +): FunctionExpression; + +export function ifError( + tryExpr: Expression, + catchValue: unknown +): FunctionExpression | BooleanExpression { + if ( + tryExpr instanceof BooleanExpression && + catchValue instanceof BooleanExpression + ) { + return tryExpr.ifError(catchValue).asBoolean(); + } else { + return tryExpr.ifError(valueToDefaultExpr(catchValue)); + } +} + +/** + * @beta + * + * Creates an expression that returns `true` if a value is absent. Otherwise, + * returns `false` even if the value is `null`. + * + * ```typescript + * // Check if the field `value` is absent. + * isAbsent(field("value")); + * ``` + * + * @param value The expression to check. + * @return A new {@code Expr} representing the 'isAbsent' check. + */ +export function isAbsent(value: Expression): BooleanExpression; + +/** + * @beta + * + * Creates an expression that returns `true` if a field is absent. Otherwise, + * returns `false` even if the field value is `null`. + * + * ```typescript + * // Check if the field `value` is absent. + * isAbsent("value"); + * ``` + * + * @param field The field to check. + * @return A new {@code Expr} representing the 'isAbsent' check. + */ +export function isAbsent(field: string): BooleanExpression; +export function isAbsent(value: Expression | string): BooleanExpression { + return fieldOrExpression(value).isAbsent(); +} + +/** + * @beta + * + * Creates an expression that removes a key from the map at the specified field name. + * + * ``` + * // Removes the key 'city' field from the map in the address field of the input document. + * mapRemove('address', 'city'); + * ``` + * + * @param mapField The name of a field containing a map value. + * @param key The name of the key to remove from the input map. + */ +export function mapRemove(mapField: string, key: string): FunctionExpression; +/** + * @beta + * + * Creates an expression that removes a key from the map produced by evaluating an expression. + * + * ``` + * // Removes the key 'baz' from the input map. + * mapRemove(map({foo: 'bar', baz: true}), 'baz'); + * ``` + * + * @param mapExpr An expression return a map value. + * @param key The name of the key to remove from the input map. + */ +export function mapRemove(mapExpr: Expression, key: string): FunctionExpression; +/** + * @beta + * + * Creates an expression that removes a key from the map at the specified field name. + * + * ``` + * // Removes the key 'city' field from the map in the address field of the input document. + * mapRemove('address', constant('city')); + * ``` + * + * @param mapField The name of a field containing a map value. + * @param keyExpr An expression that produces the name of the key to remove from the input map. + */ +export function mapRemove( + mapField: string, + keyExpr: Expression +): FunctionExpression; +/** + * @beta + * + * Creates an expression that removes a key from the map produced by evaluating an expression. + * + * ``` + * // Removes the key 'baz' from the input map. + * mapRemove(map({foo: 'bar', baz: true}), constant('baz')); + * ``` + * + * @param mapExpr An expression return a map value. + * @param keyExpr An expression that produces the name of the key to remove from the input map. + */ +export function mapRemove( + mapExpr: Expression, + keyExpr: Expression +): FunctionExpression; + +export function mapRemove( + mapExpr: Expression | string, + stringExpr: Expression | string +): FunctionExpression { + return fieldOrExpression(mapExpr).mapRemove(valueToDefaultExpr(stringExpr)); +} + +/** + * @beta + * + * Creates an expression that merges multiple map values. + * + * ``` + * // Merges the map in the settings field with, a map literal, and a map in + * // that is conditionally returned by another expression + * mapMerge('settings', { enabled: true }, conditional(field('isAdmin'), { admin: true}, {}) + * ``` + * + * @param mapField Name of a field containing a map value that will be merged. + * @param secondMap A required second map to merge. Represented as a literal or + * an expression that returns a map. + * @param otherMaps Optional additional maps to merge. Each map is represented + * as a literal or an expression that returns a map. + */ +export function mapMerge( + mapField: string, + secondMap: Record | Expression, + ...otherMaps: Array | Expression> +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that merges multiple map values. + * + * ``` + * // Merges the map in the settings field with, a map literal, and a map in + * // that is conditionally returned by another expression + * mapMerge(field('settings'), { enabled: true }, conditional(field('isAdmin'), { admin: true}, {}) + * ``` + * + * @param firstMap An expression or literal map value that will be merged. + * @param secondMap A required second map to merge. Represented as a literal or + * an expression that returns a map. + * @param otherMaps Optional additional maps to merge. Each map is represented + * as a literal or an expression that returns a map. + */ +export function mapMerge( + firstMap: Record | Expression, + secondMap: Record | Expression, + ...otherMaps: Array | Expression> +): FunctionExpression; + +export function mapMerge( + firstMap: string | Record | Expression, + secondMap: Record | Expression, + ...otherMaps: Array | Expression> +): FunctionExpression { + const secondMapExpr = valueToDefaultExpr(secondMap); + const otherMapExprs = otherMaps.map(valueToDefaultExpr); + return fieldOrExpression(firstMap).mapMerge(secondMapExpr, ...otherMapExprs); +} + +/** + * @beta + * + * Creates an expression that returns the document ID from a path. + * + * ```typescript + * // Get the document ID from a path. + * documentId(myDocumentReference); + * ``` + * + * @return A new {@code Expr} representing the documentId operation. + */ +export function documentId( + documentPath: string | DocumentReference +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that returns the document ID from a path. + * + * ```typescript + * // Get the document ID from a path. + * documentId(field("__path__")); + * ``` + * + * @return A new {@code Expr} representing the documentId operation. + */ +export function documentId(documentPathExpr: Expression): FunctionExpression; + +export function documentId( + documentPath: Expression | string | DocumentReference +): FunctionExpression { + // @ts-ignore + const documentPathExpr = valueToDefaultExpr(documentPath); + return documentPathExpr.documentId(); +} + +/** + * @beta + * + * Creates an expression that returns a substring of a string or byte array. + * + * @param field The name of a field containing a string or byte array to compute the substring from. + * @param position Index of the first character of the substring. + * @param length Length of the substring. + */ +export function substring( + field: string, + position: number, + length?: number +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that returns a substring of a string or byte array. + * + * @param input An expression returning a string or byte array to compute the substring from. + * @param position Index of the first character of the substring. + * @param length Length of the substring. + */ +export function substring( + input: Expression, + position: number, + length?: number +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that returns a substring of a string or byte array. + * + * @param field The name of a field containing a string or byte array to compute the substring from. + * @param position An expression that returns the index of the first character of the substring. + * @param length An expression that returns the length of the substring. + */ +export function substring( + field: string, + position: Expression, + length?: Expression +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that returns a substring of a string or byte array. + * + * @param input An expression returning a string or byte array to compute the substring from. + * @param position An expression that returns the index of the first character of the substring. + * @param length An expression that returns the length of the substring. + */ +export function substring( + input: Expression, + position: Expression, + length?: Expression +): FunctionExpression; + +export function substring( + field: Expression | string, + position: Expression | number, + length?: Expression | number +): FunctionExpression { + const fieldExpr = fieldOrExpression(field); + const positionExpr = valueToDefaultExpr(position); + const lengthExpr = + length === undefined ? undefined : valueToDefaultExpr(length); + return fieldExpr.substring(positionExpr, lengthExpr); +} + +/** + * @beta + * + * Creates an expression that adds two expressions together. + * + * ```typescript + * // Add the value of the 'quantity' field and the 'reserve' field. + * add(field("quantity"), field("reserve")); + * ``` + * + * @param first The first expression to add. + * @param second The second expression or literal to add. + * @param others Optional other expressions or literals to add. + * @return A new {@code Expr} representing the addition operation. + */ +export function add( + first: Expression, + second: Expression | unknown +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that adds a field's value to an expression. + * + * ```typescript + * // Add the value of the 'quantity' field and the 'reserve' field. + * add("quantity", field("reserve")); + * ``` + * + * @param fieldName The name of the field containing the value to add. + * @param second The second expression or literal to add. + * @param others Optional other expressions or literals to add. + * @return A new {@code Expr} representing the addition operation. + */ +export function add( + fieldName: string, + second: Expression | unknown +): FunctionExpression; + +export function add( + first: Expression | string, + second: Expression | unknown +): FunctionExpression { + return fieldOrExpression(first).add(valueToDefaultExpr(second)); +} + +/** + * @beta + * + * Creates an expression that subtracts two expressions. + * + * ```typescript + * // Subtract the 'discount' field from the 'price' field + * subtract(field("price"), field("discount")); + * ``` + * + * @param left The expression to subtract from. + * @param right The expression to subtract. + * @return A new {@code Expr} representing the subtraction operation. + */ +export function subtract( + left: Expression, + right: Expression +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that subtracts a constant value from an expression. + * + * ```typescript + * // Subtract the constant value 2 from the 'value' field + * subtract(field("value"), 2); + * ``` + * + * @param expression The expression to subtract from. + * @param value The constant value to subtract. + * @return A new {@code Expr} representing the subtraction operation. + */ +export function subtract( + expression: Expression, + value: unknown +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that subtracts an expression from a field's value. + * + * ```typescript + * // Subtract the 'discount' field from the 'price' field + * subtract("price", field("discount")); + * ``` + * + * @param fieldName The field name to subtract from. + * @param expression The expression to subtract. + * @return A new {@code Expr} representing the subtraction operation. + */ +export function subtract( + fieldName: string, + expression: Expression +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that subtracts a constant value from a field's value. + * + * ```typescript + * // Subtract 20 from the value of the 'total' field + * subtract("total", 20); + * ``` + * + * @param fieldName The field name to subtract from. + * @param value The constant value to subtract. + * @return A new {@code Expr} representing the subtraction operation. + */ +export function subtract(fieldName: string, value: unknown): FunctionExpression; +export function subtract( + left: Expression | string, + right: Expression | unknown +): FunctionExpression { + const normalizedLeft = typeof left === 'string' ? field(left) : left; + const normalizedRight = valueToDefaultExpr(right); + return normalizedLeft.subtract(normalizedRight); +} + +/** + * @beta + * + * Creates an expression that multiplies two expressions together. + * + * ```typescript + * // Multiply the 'quantity' field by the 'price' field + * multiply(field("quantity"), field("price")); + * ``` + * + * @param first The first expression to multiply. + * @param second The second expression or literal to multiply. + * @param others Optional additional expressions or literals to multiply. + * @return A new {@code Expr} representing the multiplication operation. + */ +export function multiply( + first: Expression, + second: Expression | unknown +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that multiplies a field's value by an expression. + * + * ```typescript + * // Multiply the 'quantity' field by the 'price' field + * multiply("quantity", field("price")); + * ``` + * + * @param fieldName The name of the field containing the value to add. + * @param second The second expression or literal to add. + * @param others Optional other expressions or literals to add. + * @return A new {@code Expr} representing the multiplication operation. + */ +export function multiply( + fieldName: string, + second: Expression | unknown +): FunctionExpression; + +export function multiply( + first: Expression | string, + second: Expression | unknown +): FunctionExpression { + return fieldOrExpression(first).multiply(valueToDefaultExpr(second)); +} + +/** + * @beta + * + * Creates an expression that divides two expressions. + * + * ```typescript + * // Divide the 'total' field by the 'count' field + * divide(field("total"), field("count")); + * ``` + * + * @param left The expression to be divided. + * @param right The expression to divide by. + * @return A new {@code Expr} representing the division operation. + */ +export function divide(left: Expression, right: Expression): FunctionExpression; + +/** + * @beta + * + * Creates an expression that divides an expression by a constant value. + * + * ```typescript + * // Divide the 'value' field by 10 + * divide(field("value"), 10); + * ``` + * + * @param expression The expression to be divided. + * @param value The constant value to divide by. + * @return A new {@code Expr} representing the division operation. + */ +export function divide( + expression: Expression, + value: unknown +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that divides a field's value by an expression. + * + * ```typescript + * // Divide the 'total' field by the 'count' field + * divide("total", field("count")); + * ``` + * + * @param fieldName The field name to be divided. + * @param expressions The expression to divide by. + * @return A new {@code Expr} representing the division operation. + */ +export function divide( + fieldName: string, + expressions: Expression +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that divides a field's value by a constant value. + * + * ```typescript + * // Divide the 'value' field by 10 + * divide("value", 10); + * ``` + * + * @param fieldName The field name to be divided. + * @param value The constant value to divide by. + * @return A new {@code Expr} representing the division operation. + */ +export function divide(fieldName: string, value: unknown): FunctionExpression; +export function divide( + left: Expression | string, + right: Expression | unknown +): FunctionExpression { + const normalizedLeft = typeof left === 'string' ? field(left) : left; + const normalizedRight = valueToDefaultExpr(right); + return normalizedLeft.divide(normalizedRight); +} + +/** + * @beta + * + * Creates an expression that calculates the modulo (remainder) of dividing two expressions. + * + * ```typescript + * // Calculate the remainder of dividing 'field1' by 'field2'. + * mod(field("field1"), field("field2")); + * ``` + * + * @param left The dividend expression. + * @param right The divisor expression. + * @return A new {@code Expr} representing the modulo operation. + */ +export function mod(left: Expression, right: Expression): FunctionExpression; + +/** + * @beta + * + * Creates an expression that calculates the modulo (remainder) of dividing an expression by a constant. + * + * ```typescript + * // Calculate the remainder of dividing 'field1' by 5. + * mod(field("field1"), 5); + * ``` + * + * @param expression The dividend expression. + * @param value The divisor constant. + * @return A new {@code Expr} representing the modulo operation. + */ +export function mod(expression: Expression, value: unknown): FunctionExpression; + +/** + * @beta + * + * Creates an expression that calculates the modulo (remainder) of dividing a field's value by an expression. + * + * ```typescript + * // Calculate the remainder of dividing 'field1' by 'field2'. + * mod("field1", field("field2")); + * ``` + * + * @param fieldName The dividend field name. + * @param expression The divisor expression. + * @return A new {@code Expr} representing the modulo operation. + */ +export function mod( + fieldName: string, + expression: Expression +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that calculates the modulo (remainder) of dividing a field's value by a constant. + * + * ```typescript + * // Calculate the remainder of dividing 'field1' by 5. + * mod("field1", 5); + * ``` + * + * @param fieldName The dividend field name. + * @param value The divisor constant. + * @return A new {@code Expr} representing the modulo operation. + */ +export function mod(fieldName: string, value: unknown): FunctionExpression; +export function mod( + left: Expression | string, + right: Expression | unknown +): FunctionExpression { + const normalizedLeft = typeof left === 'string' ? field(left) : left; + const normalizedRight = valueToDefaultExpr(right); + return normalizedLeft.mod(normalizedRight); +} + +/** + * @beta + * + * Creates an expression that creates a Firestore map value from an input object. + * + * ```typescript + * // Create a map from the input object and reference the 'baz' field value from the input document. + * map({foo: 'bar', baz: Field.of('baz')}).as('data'); + * ``` + * + * @param elements The input map to evaluate in the expression. + * @return A new {@code Expr} representing the map function. + */ +export function map(elements: Record): FunctionExpression { + return _map(elements, 'map'); +} +export function _map( + elements: Record, + methodName: string | undefined +): FunctionExpression { + const result: Expression[] = []; + for (const key in elements) { + if (Object.prototype.hasOwnProperty.call(elements, key)) { + const value = elements[key]; + result.push(constant(key)); + result.push(valueToDefaultExpr(value)); + } + } + return new FunctionExpression('map', result, 'map'); +} + +/** + * Internal use only + * Converts a plainObject to a mapValue in the proto representation, + * rather than a functionValue+map that is the result of the map(...) function. + * This behaves different from constant(plainObject) because it + * traverses the input object, converts values in the object to expressions, + * and calls _readUserData on each of these expressions. + * @private + * @internal + * @param plainObject + */ +export function _mapValue(plainObject: Record): MapValue { + const result: Map = new Map(); + for (const key in plainObject) { + if (Object.prototype.hasOwnProperty.call(plainObject, key)) { + const value = plainObject[key]; + result.set(key, valueToDefaultExpr(value)); + } + } + return new MapValue(result, undefined); +} + +/** + * @beta + * + * Creates an expression that creates a Firestore array value from an input array. + * + * ```typescript + * // Create an array value from the input array and reference the 'baz' field value from the input document. + * array(['bar', Field.of('baz')]).as('foo'); + * ``` + * + * @param elements The input array to evaluate in the expression. + * @return A new {@code Expr} representing the array function. + */ +export function array(elements: unknown[]): FunctionExpression { + return _array(elements, 'array'); +} +export function _array( + elements: unknown[], + methodName: string | undefined +): FunctionExpression { + return new FunctionExpression( + 'array', + elements.map(element => valueToDefaultExpr(element)), + methodName + ); +} + +/** + * @beta + * + * Creates an expression that checks if two expressions are equal. + * + * ```typescript + * // Check if the 'age' field is equal to an expression + * equal(field("age"), field("minAge").add(10)); + * ``` + * + * @param left The first expression to compare. + * @param right The second expression to compare. + * @return A new `Expr` representing the equality comparison. + */ +export function equal(left: Expression, right: Expression): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if an expression is equal to a constant value. + * + * ```typescript + * // Check if the 'age' field is equal to 21 + * equal(field("age"), 21); + * ``` + * + * @param expression The expression to compare. + * @param value The constant value to compare to. + * @return A new `Expr` representing the equality comparison. + */ +export function equal( + expression: Expression, + value: unknown +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value is equal to an expression. + * + * ```typescript + * // Check if the 'age' field is equal to the 'limit' field + * equal("age", field("limit")); + * ``` + * + * @param fieldName The field name to compare. + * @param expression The expression to compare to. + * @return A new `Expr` representing the equality comparison. + */ +export function equal( + fieldName: string, + expression: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value is equal to a constant value. + * + * ```typescript + * // Check if the 'city' field is equal to string constant "London" + * equal("city", "London"); + * ``` + * + * @param fieldName The field name to compare. + * @param value The constant value to compare to. + * @return A new `Expr` representing the equality comparison. + */ +export function equal(fieldName: string, value: unknown): BooleanExpression; +export function equal( + left: Expression | string, + right: unknown +): BooleanExpression { + const leftExpr = left instanceof Expression ? left : field(left); + const rightExpr = valueToDefaultExpr(right); + return leftExpr.equal(rightExpr); +} + +/** + * @beta + * + * Creates an expression that checks if two expressions are not equal. + * + * ```typescript + * // Check if the 'status' field is not equal to field 'finalState' + * notEqual(field("status"), field("finalState")); + * ``` + * + * @param left The first expression to compare. + * @param right The second expression to compare. + * @return A new `Expr` representing the inequality comparison. + */ +export function notEqual( + left: Expression, + right: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if an expression is not equal to a constant value. + * + * ```typescript + * // Check if the 'status' field is not equal to "completed" + * notEqual(field("status"), "completed"); + * ``` + * + * @param expression The expression to compare. + * @param value The constant value to compare to. + * @return A new `Expr` representing the inequality comparison. + */ +export function notEqual( + expression: Expression, + value: unknown +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value is not equal to an expression. + * + * ```typescript + * // Check if the 'status' field is not equal to the value of 'expectedStatus' + * notEqual("status", field("expectedStatus")); + * ``` + * + * @param fieldName The field name to compare. + * @param expression The expression to compare to. + * @return A new `Expr` representing the inequality comparison. + */ +export function notEqual( + fieldName: string, + expression: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value is not equal to a constant value. + * + * ```typescript + * // Check if the 'country' field is not equal to "USA" + * notEqual("country", "USA"); + * ``` + * + * @param fieldName The field name to compare. + * @param value The constant value to compare to. + * @return A new `Expr` representing the inequality comparison. + */ +export function notEqual(fieldName: string, value: unknown): BooleanExpression; +export function notEqual( + left: Expression | string, + right: unknown +): BooleanExpression { + const leftExpr = left instanceof Expression ? left : field(left); + const rightExpr = valueToDefaultExpr(right); + return leftExpr.notEqual(rightExpr); +} + +/** + * @beta + * + * Creates an expression that checks if the first expression is less than the second expression. + * + * ```typescript + * // Check if the 'age' field is less than 30 + * lessThan(field("age"), field("limit")); + * ``` + * + * @param left The first expression to compare. + * @param right The second expression to compare. + * @return A new `Expr` representing the less than comparison. + */ +export function lessThan( + left: Expression, + right: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if an expression is less than a constant value. + * + * ```typescript + * // Check if the 'age' field is less than 30 + * lessThan(field("age"), 30); + * ``` + * + * @param expression The expression to compare. + * @param value The constant value to compare to. + * @return A new `Expr` representing the less than comparison. + */ +export function lessThan( + expression: Expression, + value: unknown +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value is less than an expression. + * + * ```typescript + * // Check if the 'age' field is less than the 'limit' field + * lessThan("age", field("limit")); + * ``` + * + * @param fieldName The field name to compare. + * @param expression The expression to compare to. + * @return A new `Expr` representing the less than comparison. + */ +export function lessThan( + fieldName: string, + expression: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value is less than a constant value. + * + * ```typescript + * // Check if the 'price' field is less than 50 + * lessThan("price", 50); + * ``` + * + * @param fieldName The field name to compare. + * @param value The constant value to compare to. + * @return A new `Expr` representing the less than comparison. + */ +export function lessThan(fieldName: string, value: unknown): BooleanExpression; +export function lessThan( + left: Expression | string, + right: unknown +): BooleanExpression { + const leftExpr = left instanceof Expression ? left : field(left); + const rightExpr = valueToDefaultExpr(right); + return leftExpr.lessThan(rightExpr); +} + +/** + * @beta + * + * Creates an expression that checks if the first expression is less than or equal to the second + * expression. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to 20 + * lessThan(field("quantity"), field("limit")); + * ``` + * + * @param left The first expression to compare. + * @param right The second expression to compare. + * @return A new `Expr` representing the less than or equal to comparison. + */ +export function lessThanOrEqual( + left: Expression, + right: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if an expression is less than or equal to a constant value. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to 20 + * lessThan(field("quantity"), 20); + * ``` + * + * @param expression The expression to compare. + * @param value The constant value to compare to. + * @return A new `Expr` representing the less than or equal to comparison. + */ +export function lessThanOrEqual( + expression: Expression, + value: unknown +): BooleanExpression; + +/** + * @beta + * Creates an expression that checks if a field's value is less than or equal to an expression. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to the 'limit' field + * lessThan("quantity", field("limit")); + * ``` + * + * @param fieldName The field name to compare. + * @param expression The expression to compare to. + * @return A new `Expr` representing the less than or equal to comparison. + */ +export function lessThanOrEqual( + fieldName: string, + expression: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value is less than or equal to a constant value. + * + * ```typescript + * // Check if the 'score' field is less than or equal to 70 + * lessThan("score", 70); + * ``` + * + * @param fieldName The field name to compare. + * @param value The constant value to compare to. + * @return A new `Expr` representing the less than or equal to comparison. + */ +export function lessThanOrEqual( + fieldName: string, + value: unknown +): BooleanExpression; +export function lessThanOrEqual( + left: Expression | string, + right: unknown +): BooleanExpression { + const leftExpr = left instanceof Expression ? left : field(left); + const rightExpr = valueToDefaultExpr(right); + return leftExpr.lessThanOrEqual(rightExpr); +} + +/** + * @beta + * + * Creates an expression that checks if the first expression is greater than the second + * expression. + * + * ```typescript + * // Check if the 'age' field is greater than 18 + * greaterThan(field("age"), Constant(9).add(9)); + * ``` + * + * @param left The first expression to compare. + * @param right The second expression to compare. + * @return A new `Expr` representing the greater than comparison. + */ +export function greaterThan( + left: Expression, + right: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if an expression is greater than a constant value. + * + * ```typescript + * // Check if the 'age' field is greater than 18 + * greaterThan(field("age"), 18); + * ``` + * + * @param expression The expression to compare. + * @param value The constant value to compare to. + * @return A new `Expr` representing the greater than comparison. + */ +export function greaterThan( + expression: Expression, + value: unknown +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value is greater than an expression. + * + * ```typescript + * // Check if the value of field 'age' is greater than the value of field 'limit' + * greaterThan("age", field("limit")); + * ``` + * + * @param fieldName The field name to compare. + * @param expression The expression to compare to. + * @return A new `Expr` representing the greater than comparison. + */ +export function greaterThan( + fieldName: string, + expression: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value is greater than a constant value. + * + * ```typescript + * // Check if the 'price' field is greater than 100 + * greaterThan("price", 100); + * ``` + * + * @param fieldName The field name to compare. + * @param value The constant value to compare to. + * @return A new `Expr` representing the greater than comparison. + */ +export function greaterThan( + fieldName: string, + value: unknown +): BooleanExpression; +export function greaterThan( + left: Expression | string, + right: unknown +): BooleanExpression { + const leftExpr = left instanceof Expression ? left : field(left); + const rightExpr = valueToDefaultExpr(right); + return leftExpr.greaterThan(rightExpr); +} + +/** + * @beta + * + * Creates an expression that checks if the first expression is greater than or equal to the + * second expression. + * + * ```typescript + * // Check if the 'quantity' field is greater than or equal to the field "threshold" + * greaterThanOrEqual(field("quantity"), field("threshold")); + * ``` + * + * @param left The first expression to compare. + * @param right The second expression to compare. + * @return A new `Expr` representing the greater than or equal to comparison. + */ +export function greaterThanOrEqual( + left: Expression, + right: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if an expression is greater than or equal to a constant + * value. + * + * ```typescript + * // Check if the 'quantity' field is greater than or equal to 10 + * greaterThanOrEqual(field("quantity"), 10); + * ``` + * + * @param expression The expression to compare. + * @param value The constant value to compare to. + * @return A new `Expr` representing the greater than or equal to comparison. + */ +export function greaterThanOrEqual( + expression: Expression, + value: unknown +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value is greater than or equal to an expression. + * + * ```typescript + * // Check if the value of field 'age' is greater than or equal to the value of field 'limit' + * greaterThanOrEqual("age", field("limit")); + * ``` + * + * @param fieldName The field name to compare. + * @param value The expression to compare to. + * @return A new `Expr` representing the greater than or equal to comparison. + */ +export function greaterThanOrEqual( + fieldName: string, + value: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value is greater than or equal to a constant + * value. + * + * ```typescript + * // Check if the 'score' field is greater than or equal to 80 + * greaterThanOrEqual("score", 80); + * ``` + * + * @param fieldName The field name to compare. + * @param value The constant value to compare to. + * @return A new `Expr` representing the greater than or equal to comparison. + */ +export function greaterThanOrEqual( + fieldName: string, + value: unknown +): BooleanExpression; +export function greaterThanOrEqual( + left: Expression | string, + right: unknown +): BooleanExpression { + const leftExpr = left instanceof Expression ? left : field(left); + const rightExpr = valueToDefaultExpr(right); + return leftExpr.greaterThanOrEqual(rightExpr); +} + +/** + * @beta + * + * Creates an expression that concatenates an array expression with other arrays. + * + * ```typescript + * // Combine the 'items' array with two new item arrays + * arrayConcat(field("items"), [field("newItems"), field("otherItems")]); + * ``` + * + * @param firstArray The first array expression to concatenate to. + * @param secondArray The second array expression or array literal to concatenate to. + * @param otherArrays Optional additional array expressions or array literals to concatenate. + * @return A new {@code Expr} representing the concatenated array. + */ +export function arrayConcat( + firstArray: Expression, + secondArray: Expression | unknown[], + ...otherArrays: Array +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that concatenates a field's array value with other arrays. + * + * ```typescript + * // Combine the 'items' array with two new item arrays + * arrayConcat("items", [field("newItems"), field("otherItems")]); + * ``` + * + * @param firstArrayField The first array to concatenate to. + * @param secondArray The second array expression or array literal to concatenate to. + * @param otherArrays Optional additional array expressions or array literals to concatenate. + * @return A new {@code Expr} representing the concatenated array. + */ +export function arrayConcat( + firstArrayField: string, + secondArray: Expression | unknown[], + ...otherArrays: Array +): FunctionExpression; + +export function arrayConcat( + firstArray: Expression | string, + secondArray: Expression | unknown[], + ...otherArrays: Array +): FunctionExpression { + const exprValues = otherArrays.map(element => valueToDefaultExpr(element)); + return fieldOrExpression(firstArray).arrayConcat( + fieldOrExpression(secondArray), + ...exprValues + ); +} + +/** + * @beta + * + * Creates an expression that checks if an array expression contains a specific element. + * + * ```typescript + * // Check if the 'colors' array contains the value of field 'selectedColor' + * arrayContains(field("colors"), field("selectedColor")); + * ``` + * + * @param array The array expression to check. + * @param element The element to search for in the array. + * @return A new {@code Expr} representing the 'array_contains' comparison. + */ +export function arrayContains( + array: Expression, + element: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if an array expression contains a specific element. + * + * ```typescript + * // Check if the 'colors' array contains "red" + * arrayContains(field("colors"), "red"); + * ``` + * + * @param array The array expression to check. + * @param element The element to search for in the array. + * @return A new {@code Expr} representing the 'array_contains' comparison. + */ +export function arrayContains( + array: Expression, + element: unknown +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's array value contains a specific element. + * + * ```typescript + * // Check if the 'colors' array contains the value of field 'selectedColor' + * arrayContains("colors", field("selectedColor")); + * ``` + * + * @param fieldName The field name to check. + * @param element The element to search for in the array. + * @return A new {@code Expr} representing the 'array_contains' comparison. + */ +export function arrayContains( + fieldName: string, + element: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's array value contains a specific value. + * + * ```typescript + * // Check if the 'colors' array contains "red" + * arrayContains("colors", "red"); + * ``` + * + * @param fieldName The field name to check. + * @param element The element to search for in the array. + * @return A new {@code Expr} representing the 'array_contains' comparison. + */ +export function arrayContains( + fieldName: string, + element: unknown +): BooleanExpression; +export function arrayContains( + array: Expression | string, + element: unknown +): BooleanExpression { + const arrayExpr = fieldOrExpression(array); + const elementExpr = valueToDefaultExpr(element); + return arrayExpr.arrayContains(elementExpr); +} + +/** + * @beta + * + * Creates an expression that checks if an array expression contains any of the specified + * elements. + * + * ```typescript + * // Check if the 'categories' array contains either values from field "cate1" or "Science" + * arrayContainsAny(field("categories"), [field("cate1"), "Science"]); + * ``` + * + * @param array The array expression to check. + * @param values The elements to check for in the array. + * @return A new {@code Expr} representing the 'array_contains_any' comparison. + */ +export function arrayContainsAny( + array: Expression, + values: Array +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's array value contains any of the specified + * elements. + * + * ```typescript + * // Check if the 'groups' array contains either the value from the 'userGroup' field + * // or the value "guest" + * arrayContainsAny("categories", [field("cate1"), "Science"]); + * ``` + * + * @param fieldName The field name to check. + * @param values The elements to check for in the array. + * @return A new {@code Expr} representing the 'array_contains_any' comparison. + */ +export function arrayContainsAny( + fieldName: string, + values: Array +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if an array expression contains any of the specified + * elements. + * + * ```typescript + * // Check if the 'categories' array contains either values from field "cate1" or "Science" + * arrayContainsAny(field("categories"), array([field("cate1"), "Science"])); + * ``` + * + * @param array The array expression to check. + * @param values An expression that evaluates to an array, whose elements to check for in the array. + * @return A new {@code Expr} representing the 'array_contains_any' comparison. + */ +export function arrayContainsAny( + array: Expression, + values: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's array value contains any of the specified + * elements. + * + * ```typescript + * // Check if the 'groups' array contains either the value from the 'userGroup' field + * // or the value "guest" + * arrayContainsAny("categories", array([field("cate1"), "Science"])); + * ``` + * + * @param fieldName The field name to check. + * @param values An expression that evaluates to an array, whose elements to check for in the array field. + * @return A new {@code Expr} representing the 'array_contains_any' comparison. + */ +export function arrayContainsAny( + fieldName: string, + values: Expression +): BooleanExpression; +export function arrayContainsAny( + array: Expression | string, + values: unknown[] | Expression +): BooleanExpression { + // @ts-ignore implementation accepts both types + return fieldOrExpression(array).arrayContainsAny(values); +} + +/** + * @beta + * + * Creates an expression that checks if an array expression contains all the specified elements. + * + * ```typescript + * // Check if the "tags" array contains all of the values: "SciFi", "Adventure", and the value from field "tag1" + * arrayContainsAll(field("tags"), [field("tag1"), constant("SciFi"), "Adventure"]); + * ``` + * + * @param array The array expression to check. + * @param values The elements to check for in the array. + * @return A new {@code Expr} representing the 'array_contains_all' comparison. + */ +export function arrayContainsAll( + array: Expression, + values: Array +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's array value contains all the specified values or + * expressions. + * + * ```typescript + * // Check if the 'tags' array contains both of the values from field 'tag1', the value "SciFi", and "Adventure" + * arrayContainsAll("tags", [field("tag1"), "SciFi", "Adventure"]); + * ``` + * + * @param fieldName The field name to check. + * @param values The elements to check for in the array. + * @return A new {@code Expr} representing the 'array_contains_all' comparison. + */ +export function arrayContainsAll( + fieldName: string, + values: Array +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if an array expression contains all the specified elements. + * + * ```typescript + * // Check if the "tags" array contains all of the values: "SciFi", "Adventure", and the value from field "tag1" + * arrayContainsAll(field("tags"), [field("tag1"), constant("SciFi"), "Adventure"]); + * ``` + * + * @param array The array expression to check. + * @param arrayExpression The elements to check for in the array. + * @return A new {@code Expr} representing the 'array_contains_all' comparison. + */ +export function arrayContainsAll( + array: Expression, + arrayExpression: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's array value contains all the specified values or + * expressions. + * + * ```typescript + * // Check if the 'tags' array contains both of the values from field 'tag1', the value "SciFi", and "Adventure" + * arrayContainsAll("tags", [field("tag1"), "SciFi", "Adventure"]); + * ``` + * + * @param fieldName The field name to check. + * @param arrayExpression The elements to check for in the array. + * @return A new {@code Expr} representing the 'array_contains_all' comparison. + */ +export function arrayContainsAll( + fieldName: string, + arrayExpression: Expression +): BooleanExpression; +export function arrayContainsAll( + array: Expression | string, + values: unknown[] | Expression +): BooleanExpression { + // @ts-ignore implementation accepts both types + return fieldOrExpression(array).arrayContainsAll(values); +} + +/** + * @beta + * + * Creates an expression that calculates the length of an array in a specified field. + * + * ```typescript + * // Get the number of items in field 'cart' + * arrayLength('cart'); + * ``` + * + * @param fieldName The name of the field containing an array to calculate the length of. + * @return A new {@code Expr} representing the length of the array. + */ +export function arrayLength(fieldName: string): FunctionExpression; + +/** + * @beta + * + * Creates an expression that calculates the length of an array expression. + * + * ```typescript + * // Get the number of items in the 'cart' array + * arrayLength(field("cart")); + * ``` + * + * @param array The array expression to calculate the length of. + * @return A new {@code Expr} representing the length of the array. + */ +export function arrayLength(array: Expression): FunctionExpression; +export function arrayLength(array: Expression | string): FunctionExpression { + return fieldOrExpression(array).arrayLength(); +} + +/** + * @beta + * + * Creates an expression that checks if an expression, when evaluated, is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * equalAny(field("category"), [constant("Electronics"), field("primaryType")]); + * ``` + * + * @param expression The expression whose results to compare. + * @param values The values to check against. + * @return A new {@code Expr} representing the 'IN' comparison. + */ +export function equalAny( + expression: Expression, + values: Array +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if an expression is equal to any of the provided values. + * + * ```typescript + * // Check if the 'category' field is set to a value in the disabledCategories field + * equalAny(field("category"), field('disabledCategories')); + * ``` + * + * @param expression The expression whose results to compare. + * @param arrayExpression An expression that evaluates to an array, whose elements to check for equality to the input. + * @return A new {@code Expr} representing the 'IN' comparison. + */ +export function equalAny( + expression: Expression, + arrayExpression: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * equalAny("category", [constant("Electronics"), field("primaryType")]); + * ``` + * + * @param fieldName The field to compare. + * @param values The values to check against. + * @return A new {@code Expr} representing the 'IN' comparison. + */ +export function equalAny( + fieldName: string, + values: Array +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * equalAny("category", ["Electronics", field("primaryType")]); + * ``` + * + * @param fieldName The field to compare. + * @param arrayExpression An expression that evaluates to an array, whose elements to check for equality to the input field. + * @return A new {@code Expr} representing the 'IN' comparison. + */ +export function equalAny( + fieldName: string, + arrayExpression: Expression +): BooleanExpression; +export function equalAny( + element: Expression | string, + values: unknown[] | Expression +): BooleanExpression { + // @ts-ignore implementation accepts both types + return fieldOrExpression(element).equalAny(values); +} + +/** + * @beta + * + * Creates an expression that checks if an expression is not equal to any of the provided values + * or expressions. + * + * ```typescript + * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' + * notEqualAny(field("status"), ["pending", field("rejectedStatus")]); + * ``` + * + * @param element The expression to compare. + * @param values The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. + */ +export function notEqualAny( + element: Expression, + values: Array +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value is not equal to any of the provided values + * or expressions. + * + * ```typescript + * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' + * notEqualAny("status", [constant("pending"), field("rejectedStatus")]); + * ``` + * + * @param fieldName The field name to compare. + * @param values The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. + */ +export function notEqualAny( + fieldName: string, + values: Array +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if an expression is not equal to any of the provided values + * or expressions. + * + * ```typescript + * // Check if the 'status' field is neither "pending" nor the value of the field 'rejectedStatus' + * notEqualAny(field("status"), ["pending", field("rejectedStatus")]); + * ``` + * + * @param element The expression to compare. + * @param arrayExpression The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. + */ +export function notEqualAny( + element: Expression, + arrayExpression: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value is not equal to any of the values in the evaluated expression. + * + * ```typescript + * // Check if the 'status' field is not equal to any value in the field 'rejectedStatuses' + * notEqualAny("status", field("rejectedStatuses")); + * ``` + * + * @param fieldName The field name to compare. + * @param arrayExpression The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. + */ +export function notEqualAny( + fieldName: string, + arrayExpression: Expression +): BooleanExpression; + +export function notEqualAny( + element: Expression | string, + values: unknown[] | Expression +): BooleanExpression { + // @ts-ignore implementation accepts both types + return fieldOrExpression(element).notEqualAny(values); +} + +/** + * @beta + * + * Creates an expression that performs a logical 'XOR' (exclusive OR) operation on multiple BooleanExpressions. + * + * ```typescript + * // Check if only one of the conditions is true: 'age' greater than 18, 'city' is "London", + * // or 'status' is "active". + * const condition = xor( + * greaterThan("age", 18), + * equal("city", "London"), + * equal("status", "active")); + * ``` + * + * @param first The first condition. + * @param second The second condition. + * @param additionalConditions Additional conditions to 'XOR' together. + * @return A new {@code Expr} representing the logical 'XOR' operation. + */ +export function xor( + first: BooleanExpression, + second: BooleanExpression, + ...additionalConditions: BooleanExpression[] +): BooleanExpression { + return new FunctionExpression( + 'xor', + [first, second, ...additionalConditions], + 'xor' + ).asBoolean(); +} + +/** + * @beta + * + * Creates a conditional expression that evaluates to a 'then' expression if a condition is true + * and an 'else' expression if the condition is false. + * + * ```typescript + * // If 'age' is greater than 18, return "Adult"; otherwise, return "Minor". + * conditional( + * greaterThan("age", 18), constant("Adult"), constant("Minor")); + * ``` + * + * @param condition The condition to evaluate. + * @param thenExpr The expression to evaluate if the condition is true. + * @param elseExpr The expression to evaluate if the condition is false. + * @return A new {@code Expr} representing the conditional expression. + */ +export function conditional( + condition: BooleanExpression, + thenExpr: Expression, + elseExpr: Expression +): FunctionExpression { + return new FunctionExpression( + 'conditional', + [condition, thenExpr, elseExpr], + 'conditional' + ); +} + +/** + * @beta + * + * Creates an expression that negates a filter condition. + * + * ```typescript + * // Find documents where the 'completed' field is NOT true + * not(equal("completed", true)); + * ``` + * + * @param booleanExpr The filter condition to negate. + * @return A new {@code Expr} representing the negated filter condition. + */ +export function not(booleanExpr: BooleanExpression): BooleanExpression { + return booleanExpr.not(); +} + +/** + * @beta + * + * Creates an expression that returns the largest value between multiple input + * expressions or literal values. Based on Firestore's value type ordering. + * + * ```typescript + * // Returns the largest value between the 'field1' field, the 'field2' field, + * // and 1000 + * logicalMaximum(field("field1"), field("field2"), 1000); + * ``` + * + * @param first The first operand expression. + * @param second The second expression or literal. + * @param others Optional additional expressions or literals. + * @return A new {@code Expr} representing the logical maximum operation. + */ +export function logicalMaximum( + first: Expression, + second: Expression | unknown, + ...others: Array +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that returns the largest value between multiple input + * expressions or literal values. Based on Firestore's value type ordering. + * + * ```typescript + * // Returns the largest value between the 'field1' field, the 'field2' field, + * // and 1000. + * logicalMaximum("field1", field("field2"), 1000); + * ``` + * + * @param fieldName The first operand field name. + * @param second The second expression or literal. + * @param others Optional additional expressions or literals. + * @return A new {@code Expr} representing the logical maximum operation. + */ +export function logicalMaximum( + fieldName: string, + second: Expression | unknown, + ...others: Array +): FunctionExpression; + +export function logicalMaximum( + first: Expression | string, + second: Expression | unknown, + ...others: Array +): FunctionExpression { + return fieldOrExpression(first).logicalMaximum( + valueToDefaultExpr(second), + ...others.map(value => valueToDefaultExpr(value)) + ); +} + +/** + * @beta + * + * Creates an expression that returns the smallest value between multiple input + * expressions and literal values. Based on Firestore's value type ordering. + * + * ```typescript + * // Returns the smallest value between the 'field1' field, the 'field2' field, + * // and 1000. + * logicalMinimum(field("field1"), field("field2"), 1000); + * ``` + * + * @param first The first operand expression. + * @param second The second expression or literal. + * @param others Optional additional expressions or literals. + * @return A new {@code Expr} representing the logical minimum operation. + */ +export function logicalMinimum( + first: Expression, + second: Expression | unknown, + ...others: Array +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that returns the smallest value between a field's value + * and other input expressions or literal values. + * Based on Firestore's value type ordering. + * + * ```typescript + * // Returns the smallest value between the 'field1' field, the 'field2' field, + * // and 1000. + * logicalMinimum("field1", field("field2"), 1000); + * ``` + * + * @param fieldName The first operand field name. + * @param second The second expression or literal. + * @param others Optional additional expressions or literals. + * @return A new {@code Expr} representing the logical minimum operation. + */ +export function logicalMinimum( + fieldName: string, + second: Expression | unknown, + ...others: Array +): FunctionExpression; + +export function logicalMinimum( + first: Expression | string, + second: Expression | unknown, + ...others: Array +): FunctionExpression { + return fieldOrExpression(first).logicalMinimum( + valueToDefaultExpr(second), + ...others.map(value => valueToDefaultExpr(value)) + ); +} + +/** + * @beta + * + * Creates an expression that checks if a field exists. + * + * ```typescript + * // Check if the document has a field named "phoneNumber" + * exists(field("phoneNumber")); + * ``` + * + * @param value An expression evaluates to the name of the field to check. + * @return A new {@code Expr} representing the 'exists' check. + */ +export function exists(value: Expression): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field exists. + * + * ```typescript + * // Check if the document has a field named "phoneNumber" + * exists("phoneNumber"); + * ``` + * + * @param fieldName The field name to check. + * @return A new {@code Expr} representing the 'exists' check. + */ +export function exists(fieldName: string): BooleanExpression; +export function exists(valueOrField: Expression | string): BooleanExpression { + return fieldOrExpression(valueOrField).exists(); +} + +/** + * @beta + * + * Creates an expression that reverses a string. + * + * ```typescript + * // Reverse the value of the 'myString' field. + * reverse(field("myString")); + * ``` + * + * @param stringExpression An expression evaluating to a string value, which will be reversed. + * @return A new {@code Expr} representing the reversed string. + */ +export function reverse(stringExpression: Expression): FunctionExpression; + +/** + * @beta + * + * Creates an expression that reverses a string value in the specified field. + * + * ```typescript + * // Reverse the value of the 'myString' field. + * reverse("myString"); + * ``` + * + * @param field The name of the field representing the string to reverse. + * @return A new {@code Expr} representing the reversed string. + */ +export function reverse(field: string): FunctionExpression; +export function reverse(expr: Expression | string): FunctionExpression { + return fieldOrExpression(expr).reverse(); +} + +/** + * @beta + * + * Creates an expression that calculates the byte length of a string in UTF-8, or just the length of a Blob. + * + * ```typescript + * // Calculate the length of the 'myString' field in bytes. + * byteLength(field("myString")); + * ``` + * + * @param expr The expression representing the string. + * @return A new {@code Expr} representing the length of the string in bytes. + */ +export function byteLength(expr: Expression): FunctionExpression; + +/** + * @beta + * + * Creates an expression that calculates the length of a string represented by a field in UTF-8 bytes, or just the length of a Blob. + * + * ```typescript + * // Calculate the length of the 'myString' field in bytes. + * byteLength("myString"); + * ``` + * + * @param fieldName The name of the field containing the string. + * @return A new {@code Expr} representing the length of the string in bytes. + */ +export function byteLength(fieldName: string): FunctionExpression; +export function byteLength(expr: Expression | string): FunctionExpression { + const normalizedExpr = fieldOrExpression(expr); + return normalizedExpr.byteLength(); +} + +/** + * @beta + * Creates an expression that reverses an array. + * + * ```typescript + * // Reverse the value of the 'myArray' field. + * arrayReverse("myArray"); + * ``` + * + * @param fieldName The name of the field to reverse. + * @return A new {@code Expr} representing the reversed array. + */ +export function arrayReverse(fieldName: string): FunctionExpression; + +/** + * @beta + * Creates an expression that reverses an array. + * + * ```typescript + * // Reverse the value of the 'myArray' field. + * arrayReverse(field("myArray")); + * ``` + * + * @param arrayExpression An expression evaluating to an array value, which will be reversed. + * @return A new {@code Expr} representing the reversed array. + */ +export function arrayReverse(arrayExpression: Expression): FunctionExpression; +export function arrayReverse(expr: Expression | string): FunctionExpression { + return fieldOrExpression(expr).arrayReverse(); +} + +/** + * @beta + * Creates an expression that computes e to the power of the expression's result. + * + * ```typescript + * // Compute e to the power of 2. + * exp(constant(2)); + * ``` + * + * @return A new {@code Expr} representing the exp of the numeric value. + */ +export function exp(expression: Expression): FunctionExpression; + +/** + * @beta + * Creates an expression that computes e to the power of the expression's result. + * + * ```typescript + * // Compute e to the power of the 'value' field. + * exp('value'); + * ``` + * + * @return A new {@code Expr} representing the exp of the numeric value. + */ +export function exp(fieldName: string): FunctionExpression; + +export function exp( + expressionOrFieldName: Expression | string +): FunctionExpression { + return fieldOrExpression(expressionOrFieldName).exp(); +} + +/** + * @beta + * Creates an expression that computes the ceiling of a numeric value. + * + * ```typescript + * // Compute the ceiling of the 'price' field. + * ceil("price"); + * ``` + * + * @param fieldName The name of the field to compute the ceiling of. + * @return A new {@code Expr} representing the ceiling of the numeric value. + */ +export function ceil(fieldName: string): FunctionExpression; + +/** + * @beta + * Creates an expression that computes the ceiling of a numeric value. + * + * ```typescript + * // Compute the ceiling of the 'price' field. + * ceil(field("price")); + * ``` + * + * @param expression An expression evaluating to a numeric value, which the ceiling will be computed for. + * @return A new {@code Expr} representing the ceiling of the numeric value. + */ +export function ceil(expression: Expression): FunctionExpression; +export function ceil(expr: Expression | string): FunctionExpression { + return fieldOrExpression(expr).ceil(); +} + +/** + * @beta + * Creates an expression that computes the floor of a numeric value. + * + * @param expr The expression to compute the floor of. + * @return A new {@code Expr} representing the floor of the numeric value. + */ +export function floor(expr: Expression): FunctionExpression; + +/** + * @beta + * Creates an expression that computes the floor of a numeric value. + * + * @param fieldName The name of the field to compute the floor of. + * @return A new {@code Expr} representing the floor of the numeric value. + */ +export function floor(fieldName: string): FunctionExpression; +export function floor(expr: Expression | string): FunctionExpression { + return fieldOrExpression(expr).floor(); +} + +/** + * @beta + * Creates an aggregation that counts the number of distinct values of a field. + * + * @param expr The expression or field to count distinct values of. + * @return A new `AggregateFunction` representing the 'count_distinct' aggregation. + */ +export function countDistinct(expr: Expression | string): AggregateFunction { + return fieldOrExpression(expr).countDistinct(); +} + +/** + * @beta + * + * Creates an expression that calculates the character length of a string field in UTF8. + * + * ```typescript + * // Get the character length of the 'name' field in UTF-8. + * strLength("name"); + * ``` + * + * @param fieldName The name of the field containing the string. + * @return A new {@code Expr} representing the length of the string. + */ +export function charLength(fieldName: string): FunctionExpression; + +/** + * @beta + * + * Creates an expression that calculates the character length of a string expression in UTF-8. + * + * ```typescript + * // Get the character length of the 'name' field in UTF-8. + * strLength(field("name")); + * ``` + * + * @param stringExpression The expression representing the string to calculate the length of. + * @return A new {@code Expr} representing the length of the string. + */ +export function charLength(stringExpression: Expression): FunctionExpression; +export function charLength(value: Expression | string): FunctionExpression { + const valueExpr = fieldOrExpression(value); + return valueExpr.charLength(); +} + +/** + * @beta + * + * Creates an expression that performs a case-sensitive wildcard string comparison against a + * field. + * + * ```typescript + * // Check if the 'title' field contains the string "guide" + * like("title", "%guide%"); + * ``` + * + * @param fieldName The name of the field containing the string. + * @param pattern The pattern to search for. You can use "%" as a wildcard character. + * @return A new {@code Expr} representing the 'like' comparison. + */ +export function like(fieldName: string, pattern: string): BooleanExpression; + +/** + * @beta + * + * Creates an expression that performs a case-sensitive wildcard string comparison against a + * field. + * + * ```typescript + * // Check if the 'title' field contains the string "guide" + * like("title", field("pattern")); + * ``` + * + * @param fieldName The name of the field containing the string. + * @param pattern The pattern to search for. You can use "%" as a wildcard character. + * @return A new {@code Expr} representing the 'like' comparison. + */ +export function like(fieldName: string, pattern: Expression): BooleanExpression; + +/** + * @beta + * + * Creates an expression that performs a case-sensitive wildcard string comparison. + * + * ```typescript + * // Check if the 'title' field contains the string "guide" + * like(field("title"), "%guide%"); + * ``` + * + * @param stringExpression The expression representing the string to perform the comparison on. + * @param pattern The pattern to search for. You can use "%" as a wildcard character. + * @return A new {@code Expr} representing the 'like' comparison. + */ +export function like( + stringExpression: Expression, + pattern: string +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that performs a case-sensitive wildcard string comparison. + * + * ```typescript + * // Check if the 'title' field contains the string "guide" + * like(field("title"), field("pattern")); + * ``` + * + * @param stringExpression The expression representing the string to perform the comparison on. + * @param pattern The pattern to search for. You can use "%" as a wildcard character. + * @return A new {@code Expr} representing the 'like' comparison. + */ +export function like( + stringExpression: Expression, + pattern: Expression +): BooleanExpression; +export function like( + left: Expression | string, + pattern: Expression | string +): BooleanExpression { + const leftExpr = fieldOrExpression(left); + const patternExpr = valueToDefaultExpr(pattern); + return leftExpr.like(patternExpr); +} + +/** + * @beta + * + * Creates an expression that checks if a string field contains a specified regular expression as + * a substring. + * + * ```typescript + * // Check if the 'description' field contains "example" (case-insensitive) + * regexContains("description", "(?i)example"); + * ``` + * + * @param fieldName The name of the field containing the string. + * @param pattern The regular expression to use for the search. + * @return A new {@code Expr} representing the 'contains' comparison. + */ +export function regexContains( + fieldName: string, + pattern: string +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a string field contains a specified regular expression as + * a substring. + * + * ```typescript + * // Check if the 'description' field contains "example" (case-insensitive) + * regexContains("description", field("pattern")); + * ``` + * + * @param fieldName The name of the field containing the string. + * @param pattern The regular expression to use for the search. + * @return A new {@code Expr} representing the 'contains' comparison. + */ +export function regexContains( + fieldName: string, + pattern: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a string expression contains a specified regular + * expression as a substring. + * + * ```typescript + * // Check if the 'description' field contains "example" (case-insensitive) + * regexContains(field("description"), "(?i)example"); + * ``` + * + * @param stringExpression The expression representing the string to perform the comparison on. + * @param pattern The regular expression to use for the search. + * @return A new {@code Expr} representing the 'contains' comparison. + */ +export function regexContains( + stringExpression: Expression, + pattern: string +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a string expression contains a specified regular + * expression as a substring. + * + * ```typescript + * // Check if the 'description' field contains "example" (case-insensitive) + * regexContains(field("description"), field("pattern")); + * ``` + * + * @param stringExpression The expression representing the string to perform the comparison on. + * @param pattern The regular expression to use for the search. + * @return A new {@code Expr} representing the 'contains' comparison. + */ +export function regexContains( + stringExpression: Expression, + pattern: Expression +): BooleanExpression; +export function regexContains( + left: Expression | string, + pattern: Expression | string +): BooleanExpression { + const leftExpr = fieldOrExpression(left); + const patternExpr = valueToDefaultExpr(pattern); + return leftExpr.regexContains(patternExpr); +} + +/** + * @beta + * + * Creates an expression that checks if a string field matches a specified regular expression. + * + * ```typescript + * // Check if the 'email' field matches a valid email pattern + * regexMatch("email", "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"); + * ``` + * + * @param fieldName The name of the field containing the string. + * @param pattern The regular expression to use for the match. + * @return A new {@code Expr} representing the regular expression match. + */ +export function regexMatch( + fieldName: string, + pattern: string +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a string field matches a specified regular expression. + * + * ```typescript + * // Check if the 'email' field matches a valid email pattern + * regexMatch("email", field("pattern")); + * ``` + * + * @param fieldName The name of the field containing the string. + * @param pattern The regular expression to use for the match. + * @return A new {@code Expr} representing the regular expression match. + */ +export function regexMatch( + fieldName: string, + pattern: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a string expression matches a specified regular + * expression. + * + * ```typescript + * // Check if the 'email' field matches a valid email pattern + * regexMatch(field("email"), "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"); + * ``` + * + * @param stringExpression The expression representing the string to match against. + * @param pattern The regular expression to use for the match. + * @return A new {@code Expr} representing the regular expression match. + */ +export function regexMatch( + stringExpression: Expression, + pattern: string +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a string expression matches a specified regular + * expression. + * + * ```typescript + * // Check if the 'email' field matches a valid email pattern + * regexMatch(field("email"), field("pattern")); + * ``` + * + * @param stringExpression The expression representing the string to match against. + * @param pattern The regular expression to use for the match. + * @return A new {@code Expr} representing the regular expression match. + */ +export function regexMatch( + stringExpression: Expression, + pattern: Expression +): BooleanExpression; +export function regexMatch( + left: Expression | string, + pattern: Expression | string +): BooleanExpression { + const leftExpr = fieldOrExpression(left); + const patternExpr = valueToDefaultExpr(pattern); + return leftExpr.regexMatch(patternExpr); +} + +/** + * @beta + * + * Creates an expression that checks if a string field contains a specified substring. + * + * ```typescript + * // Check if the 'description' field contains "example". + * stringContains("description", "example"); + * ``` + * + * @param fieldName The name of the field containing the string. + * @param substring The substring to search for. + * @return A new {@code Expr} representing the 'contains' comparison. + */ +export function stringContains( + fieldName: string, + substring: string +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a string field contains a substring specified by an expression. + * + * ```typescript + * // Check if the 'description' field contains the value of the 'keyword' field. + * stringContains("description", field("keyword")); + * ``` + * + * @param fieldName The name of the field containing the string. + * @param substring The expression representing the substring to search for. + * @return A new {@code Expr} representing the 'contains' comparison. + */ +export function stringContains( + fieldName: string, + substring: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a string expression contains a specified substring. + * + * ```typescript + * // Check if the 'description' field contains "example". + * stringContains(field("description"), "example"); + * ``` + * + * @param stringExpression The expression representing the string to perform the comparison on. + * @param substring The substring to search for. + * @return A new {@code Expr} representing the 'contains' comparison. + */ +export function stringContains( + stringExpression: Expression, + substring: string +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a string expression contains a substring specified by another expression. + * + * ```typescript + * // Check if the 'description' field contains the value of the 'keyword' field. + * stringContains(field("description"), field("keyword")); + * ``` + * + * @param stringExpression The expression representing the string to perform the comparison on. + * @param substring The expression representing the substring to search for. + * @return A new {@code Expr} representing the 'contains' comparison. + */ +export function stringContains( + stringExpression: Expression, + substring: Expression +): BooleanExpression; +export function stringContains( + left: Expression | string, + substring: Expression | string +): BooleanExpression { + const leftExpr = fieldOrExpression(left); + const substringExpr = valueToDefaultExpr(substring); + return leftExpr.stringContains(substringExpr); +} + +/** + * @beta + * + * Creates an expression that checks if a field's value starts with a given prefix. + * + * ```typescript + * // Check if the 'name' field starts with "Mr." + * startsWith("name", "Mr."); + * ``` + * + * @param fieldName The field name to check. + * @param prefix The prefix to check for. + * @return A new {@code Expr} representing the 'starts with' comparison. + */ +export function startsWith( + fieldName: string, + prefix: string +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value starts with a given prefix. + * + * ```typescript + * // Check if the 'fullName' field starts with the value of the 'firstName' field + * startsWith("fullName", field("firstName")); + * ``` + * + * @param fieldName The field name to check. + * @param prefix The expression representing the prefix. + * @return A new {@code Expr} representing the 'starts with' comparison. + */ +export function startsWith( + fieldName: string, + prefix: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a string expression starts with a given prefix. + * + * ```typescript + * // Check if the result of concatenating 'firstName' and 'lastName' fields starts with "Mr." + * startsWith(field("fullName"), "Mr."); + * ``` + * + * @param stringExpression The expression to check. + * @param prefix The prefix to check for. + * @return A new {@code Expr} representing the 'starts with' comparison. + */ +export function startsWith( + stringExpression: Expression, + prefix: string +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a string expression starts with a given prefix. + * + * ```typescript + * // Check if the result of concatenating 'firstName' and 'lastName' fields starts with "Mr." + * startsWith(field("fullName"), field("prefix")); + * ``` + * + * @param stringExpression The expression to check. + * @param prefix The prefix to check for. + * @return A new {@code Expr} representing the 'starts with' comparison. + */ +export function startsWith( + stringExpression: Expression, + prefix: Expression +): BooleanExpression; +export function startsWith( + expr: Expression | string, + prefix: Expression | string +): BooleanExpression { + return fieldOrExpression(expr).startsWith(valueToDefaultExpr(prefix)); +} + +/** + * @beta + * + * Creates an expression that checks if a field's value ends with a given postfix. + * + * ```typescript + * // Check if the 'filename' field ends with ".txt" + * endsWith("filename", ".txt"); + * ``` + * + * @param fieldName The field name to check. + * @param suffix The postfix to check for. + * @return A new {@code Expr} representing the 'ends with' comparison. + */ +export function endsWith(fieldName: string, suffix: string): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a field's value ends with a given postfix. + * + * ```typescript + * // Check if the 'url' field ends with the value of the 'extension' field + * endsWith("url", field("extension")); + * ``` + * + * @param fieldName The field name to check. + * @param suffix The expression representing the postfix. + * @return A new {@code Expr} representing the 'ends with' comparison. + */ +export function endsWith( + fieldName: string, + suffix: Expression +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a string expression ends with a given postfix. + * + * ```typescript + * // Check if the result of concatenating 'firstName' and 'lastName' fields ends with "Jr." + * endsWith(field("fullName"), "Jr."); + * ``` + * + * @param stringExpression The expression to check. + * @param suffix The postfix to check for. + * @return A new {@code Expr} representing the 'ends with' comparison. + */ +export function endsWith( + stringExpression: Expression, + suffix: string +): BooleanExpression; + +/** + * @beta + * + * Creates an expression that checks if a string expression ends with a given postfix. + * + * ```typescript + * // Check if the result of concatenating 'firstName' and 'lastName' fields ends with "Jr." + * endsWith(field("fullName"), constant("Jr.")); + * ``` + * + * @param stringExpression The expression to check. + * @param suffix The postfix to check for. + * @return A new {@code Expr} representing the 'ends with' comparison. + */ +export function endsWith( + stringExpression: Expression, + suffix: Expression +): BooleanExpression; +export function endsWith( + expr: Expression | string, + suffix: Expression | string +): BooleanExpression { + return fieldOrExpression(expr).endsWith(valueToDefaultExpr(suffix)); +} + +/** + * @beta + * + * Creates an expression that converts a string field to lowercase. + * + * ```typescript + * // Convert the 'name' field to lowercase + * toLower("name"); + * ``` + * + * @param fieldName The name of the field containing the string. + * @return A new {@code Expr} representing the lowercase string. + */ +export function toLower(fieldName: string): FunctionExpression; + +/** + * @beta + * + * Creates an expression that converts a string expression to lowercase. + * + * ```typescript + * // Convert the 'name' field to lowercase + * toLower(field("name")); + * ``` + * + * @param stringExpression The expression representing the string to convert to lowercase. + * @return A new {@code Expr} representing the lowercase string. + */ +export function toLower(stringExpression: Expression): FunctionExpression; +export function toLower(expr: Expression | string): FunctionExpression { + return fieldOrExpression(expr).toLower(); +} + +/** + * @beta + * + * Creates an expression that converts a string field to uppercase. + * + * ```typescript + * // Convert the 'title' field to uppercase + * toUpper("title"); + * ``` + * + * @param fieldName The name of the field containing the string. + * @return A new {@code Expr} representing the uppercase string. + */ +export function toUpper(fieldName: string): FunctionExpression; + +/** + * @beta + * + * Creates an expression that converts a string expression to uppercase. + * + * ```typescript + * // Convert the 'title' field to uppercase + * toUppercase(field("title")); + * ``` + * + * @param stringExpression The expression representing the string to convert to uppercase. + * @return A new {@code Expr} representing the uppercase string. + */ +export function toUpper(stringExpression: Expression): FunctionExpression; +export function toUpper(expr: Expression | string): FunctionExpression { + return fieldOrExpression(expr).toUpper(); +} + +/** + * @beta + * + * Creates an expression that removes leading and trailing whitespace from a string or byte array. + * + * ```typescript + * // Trim whitespace from the 'userInput' field + * trim("userInput"); + * + * // Trim quotes from the 'userInput' field + * trim("userInput", '"'); + * ``` + * + * @param fieldName The name of the field containing the string or byte array. + * @param valueToTrim Optional This parameter is treated as a set of characters or bytes that will be + * trimmed from the input. If not specified, then whitespace will be trimmed. + * @return A new {@code Expr} representing the trimmed string. + */ +export function trim( + fieldName: string, + valueToTrim?: string | Expression +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that removes leading and trailing characters from a string or byte array expression. + * + * ```typescript + * // Trim whitespace from the 'userInput' field + * trim(field("userInput")); + * + * // Trim quotes from the 'userInput' field + * trim(field("userInput"), '"'); + * ``` + * + * @param stringExpression The expression representing the string or byte array to trim. + * @param valueToTrim Optional This parameter is treated as a set of characters or bytes that will be + * trimmed from the input. If not specified, then whitespace will be trimmed. + * @return A new {@code Expr} representing the trimmed string or byte array. + */ +export function trim( + stringExpression: Expression, + valueToTrim?: string | Expression +): FunctionExpression; +export function trim( + expr: Expression | string, + valueToTrim?: string | Expression +): FunctionExpression { + return fieldOrExpression(expr).trim(valueToTrim); +} + +/** + * @beta + * + * Creates an expression that concatenates string functions, fields or constants together. + * + * ```typescript + * // Combine the 'firstName', " ", and 'lastName' fields into a single string + * stringConcat("firstName", " ", field("lastName")); + * ``` + * + * @param fieldName The field name containing the initial string value. + * @param secondString An expression or string literal to concatenate. + * @param otherStrings Optional additional expressions or literals (typically strings) to concatenate. + * @return A new {@code Expr} representing the concatenated string. + */ +export function stringConcat( + fieldName: string, + secondString: Expression | string, + ...otherStrings: Array +): FunctionExpression; + +/** + * @beta + * Creates an expression that concatenates string expressions together. + * + * ```typescript + * // Combine the 'firstName', " ", and 'lastName' fields into a single string + * stringConcat(field("firstName"), " ", field("lastName")); + * ``` + * + * @param firstString The initial string expression to concatenate to. + * @param secondString An expression or string literal to concatenate. + * @param otherStrings Optional additional expressions or literals (typically strings) to concatenate. + * @return A new {@code Expr} representing the concatenated string. + */ +export function stringConcat( + firstString: Expression, + secondString: Expression | string, + ...otherStrings: Array +): FunctionExpression; +export function stringConcat( + first: string | Expression, + second: string | Expression, + ...elements: Array +): FunctionExpression { + return fieldOrExpression(first).stringConcat( + valueToDefaultExpr(second), + ...elements.map(valueToDefaultExpr) + ); +} + +/** + * @beta + * + * Accesses a value from a map (object) field using the provided key. + * + * ```typescript + * // Get the 'city' value from the 'address' map field + * mapGet("address", "city"); + * ``` + * + * @param fieldName The field name of the map field. + * @param subField The key to access in the map. + * @return A new {@code Expr} representing the value associated with the given key in the map. + */ +export function mapGet(fieldName: string, subField: string): FunctionExpression; + +/** + * @beta + * + * Accesses a value from a map (object) expression using the provided key. + * + * ```typescript + * // Get the 'city' value from the 'address' map field + * mapGet(field("address"), "city"); + * ``` + * + * @param mapExpression The expression representing the map. + * @param subField The key to access in the map. + * @return A new {@code Expr} representing the value associated with the given key in the map. + */ +export function mapGet( + mapExpression: Expression, + subField: string +): FunctionExpression; +export function mapGet( + fieldOrExpr: string | Expression, + subField: string +): FunctionExpression { + return fieldOrExpression(fieldOrExpr).mapGet(subField); +} + +/** + * @beta + * + * Creates an aggregation that counts the total number of stage inputs. + * + * ```typescript + * // Count the total number of input documents + * countAll().as("totalDocument"); + * ``` + * + * @return A new {@code AggregateFunction} representing the 'countAll' aggregation. + */ +export function countAll(): AggregateFunction { + return AggregateFunction._create('count', [], 'count'); +} + +/** + * @beta + * + * Creates an aggregation that counts the number of stage inputs with valid evaluations of the + * provided expression. + * + * ```typescript + * // Count the number of items where the price is greater than 10 + * count(field("price").greaterThan(10)).as("expensiveItemCount"); + * ``` + * + * @param expression The expression to count. + * @return A new {@code AggregateFunction} representing the 'count' aggregation. + */ +export function count(expression: Expression): AggregateFunction; + +/** + * @beta + * Creates an aggregation that counts the number of stage inputs where the input field exists. + * + * ```typescript + * // Count the total number of products + * count("productId").as("totalProducts"); + * ``` + * + * @param fieldName The name of the field to count. + * @return A new {@code AggregateFunction} representing the 'count' aggregation. + */ +export function count(fieldName: string): AggregateFunction; +export function count(value: Expression | string): AggregateFunction { + return fieldOrExpression(value).count(); +} + +/** + * @beta + * + * Creates an aggregation that calculates the sum of values from an expression across multiple + * stage inputs. + * + * ```typescript + * // Calculate the total revenue from a set of orders + * sum(field("orderAmount")).as("totalRevenue"); + * ``` + * + * @param expression The expression to sum up. + * @return A new {@code AggregateFunction} representing the 'sum' aggregation. + */ +export function sum(expression: Expression): AggregateFunction; + +/** + * @beta + * + * Creates an aggregation that calculates the sum of a field's values across multiple stage + * inputs. + * + * ```typescript + * // Calculate the total revenue from a set of orders + * sum("orderAmount").as("totalRevenue"); + * ``` + * + * @param fieldName The name of the field containing numeric values to sum up. + * @return A new {@code AggregateFunction} representing the 'sum' aggregation. + */ +export function sum(fieldName: string): AggregateFunction; +export function sum(value: Expression | string): AggregateFunction { + return fieldOrExpression(value).sum(); +} + +/** + * @beta + * + * Creates an aggregation that calculates the average (mean) of values from an expression across + * multiple stage inputs. + * + * ```typescript + * // Calculate the average age of users + * average(field("age")).as("averageAge"); + * ``` + * + * @param expression The expression representing the values to average. + * @return A new {@code AggregateFunction} representing the 'average' aggregation. + */ +export function average(expression: Expression): AggregateFunction; + +/** + * @beta + * + * Creates an aggregation that calculates the average (mean) of a field's values across multiple + * stage inputs. + * + * ```typescript + * // Calculate the average age of users + * average("age").as("averageAge"); + * ``` + * + * @param fieldName The name of the field containing numeric values to average. + * @return A new {@code AggregateFunction} representing the 'average' aggregation. + */ +export function average(fieldName: string): AggregateFunction; +export function average(value: Expression | string): AggregateFunction { + return fieldOrExpression(value).average(); +} + +/** + * @beta + * + * Creates an aggregation that finds the minimum value of an expression across multiple stage + * inputs. + * + * ```typescript + * // Find the lowest price of all products + * minimum(field("price")).as("lowestPrice"); + * ``` + * + * @param expression The expression to find the minimum value of. + * @return A new {@code AggregateFunction} representing the 'minimum' aggregation. + */ +export function minimum(expression: Expression): AggregateFunction; + +/** + * @beta + * + * Creates an aggregation that finds the minimum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the lowest price of all products + * minimum("price").as("lowestPrice"); + * ``` + * + * @param fieldName The name of the field to find the minimum value of. + * @return A new {@code AggregateFunction} representing the 'minimum' aggregation. + */ +export function minimum(fieldName: string): AggregateFunction; +export function minimum(value: Expression | string): AggregateFunction { + return fieldOrExpression(value).minimum(); +} + +/** + * @beta + * + * Creates an aggregation that finds the maximum value of an expression across multiple stage + * inputs. + * + * ```typescript + * // Find the highest score in a leaderboard + * maximum(field("score")).as("highestScore"); + * ``` + * + * @param expression The expression to find the maximum value of. + * @return A new {@code AggregateFunction} representing the 'maximum' aggregation. + */ +export function maximum(expression: Expression): AggregateFunction; + +/** + * @beta + * + * Creates an aggregation that finds the maximum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the highest score in a leaderboard + * maximum("score").as("highestScore"); + * ``` + * + * @param fieldName The name of the field to find the maximum value of. + * @return A new {@code AggregateFunction} representing the 'maximum' aggregation. + */ +export function maximum(fieldName: string): AggregateFunction; +export function maximum(value: Expression | string): AggregateFunction { + return fieldOrExpression(value).maximum(); +} + +/** + * @beta + * + * Calculates the Cosine distance between a field's vector value and a literal vector value. + * + * ```typescript + * // Calculate the Cosine distance between the 'location' field and a target location + * cosineDistance("location", [37.7749, -122.4194]); + * ``` + * + * @param fieldName The name of the field containing the first vector. + * @param vector The other vector (as an array of doubles) or {@link VectorValue} to compare against. + * @return A new {@code Expr} representing the Cosine distance between the two vectors. + */ +export function cosineDistance( + fieldName: string, + vector: number[] | VectorValue +): FunctionExpression; + +/** + * @beta + * + * Calculates the Cosine distance between a field's vector value and a vector expression. + * + * ```typescript + * // Calculate the cosine distance between the 'userVector' field and the 'itemVector' field + * cosineDistance("userVector", field("itemVector")); + * ``` + * + * @param fieldName The name of the field containing the first vector. + * @param vectorExpression The other vector (represented as an Expr) to compare against. + * @return A new {@code Expr} representing the cosine distance between the two vectors. + */ +export function cosineDistance( + fieldName: string, + vectorExpression: Expression +): FunctionExpression; + +/** + * @beta + * + * Calculates the Cosine distance between a vector expression and a vector literal. + * + * ```typescript + * // Calculate the cosine distance between the 'location' field and a target location + * cosineDistance(field("location"), [37.7749, -122.4194]); + * ``` + * + * @param vectorExpression The first vector (represented as an Expr) to compare against. + * @param vector The other vector (as an array of doubles or VectorValue) to compare against. + * @return A new {@code Expr} representing the cosine distance between the two vectors. + */ +export function cosineDistance( + vectorExpression: Expression, + vector: number[] | VectorValue +): FunctionExpression; + +/** + * @beta + * + * Calculates the Cosine distance between two vector expressions. + * + * ```typescript + * // Calculate the cosine distance between the 'userVector' field and the 'itemVector' field + * cosineDistance(field("userVector"), field("itemVector")); + * ``` + * + * @param vectorExpression The first vector (represented as an Expr) to compare against. + * @param otherVectorExpression The other vector (represented as an Expr) to compare against. + * @return A new {@code Expr} representing the cosine distance between the two vectors. + */ +export function cosineDistance( + vectorExpression: Expression, + otherVectorExpression: Expression +): FunctionExpression; +export function cosineDistance( + expr: Expression | string, + other: Expression | number[] | VectorValue +): FunctionExpression { + const expr1 = fieldOrExpression(expr); + const expr2 = vectorToExpr(other); + return expr1.cosineDistance(expr2); +} + +/** + * @beta + * + * Calculates the dot product between a field's vector value and a double array. + * + * ```typescript + * // Calculate the dot product distance between a feature vector and a target vector + * dotProduct("features", [0.5, 0.8, 0.2]); + * ``` + * + * @param fieldName The name of the field containing the first vector. + * @param vector The other vector (as an array of doubles or VectorValue) to calculate with. + * @return A new {@code Expr} representing the dot product between the two vectors. + */ +export function dotProduct( + fieldName: string, + vector: number[] | VectorValue +): FunctionExpression; + +/** + * @beta + * + * Calculates the dot product between a field's vector value and a vector expression. + * + * ```typescript + * // Calculate the dot product distance between two document vectors: 'docVector1' and 'docVector2' + * dotProduct("docVector1", field("docVector2")); + * ``` + * + * @param fieldName The name of the field containing the first vector. + * @param vectorExpression The other vector (represented as an Expr) to calculate with. + * @return A new {@code Expr} representing the dot product between the two vectors. + */ +export function dotProduct( + fieldName: string, + vectorExpression: Expression +): FunctionExpression; + +/** + * @beta + * + * Calculates the dot product between a vector expression and a double array. + * + * ```typescript + * // Calculate the dot product between a feature vector and a target vector + * dotProduct(field("features"), [0.5, 0.8, 0.2]); + * ``` + * + * @param vectorExpression The first vector (represented as an Expr) to calculate with. + * @param vector The other vector (as an array of doubles or VectorValue) to calculate with. + * @return A new {@code Expr} representing the dot product between the two vectors. + */ +export function dotProduct( + vectorExpression: Expression, + vector: number[] | VectorValue +): FunctionExpression; + +/** + * @beta + * + * Calculates the dot product between two vector expressions. + * + * ```typescript + * // Calculate the dot product between two document vectors: 'docVector1' and 'docVector2' + * dotProduct(field("docVector1"), field("docVector2")); + * ``` + * + * @param vectorExpression The first vector (represented as an Expr) to calculate with. + * @param otherVectorExpression The other vector (represented as an Expr) to calculate with. + * @return A new {@code Expr} representing the dot product between the two vectors. + */ +export function dotProduct( + vectorExpression: Expression, + otherVectorExpression: Expression +): FunctionExpression; +export function dotProduct( + expr: Expression | string, + other: Expression | number[] | VectorValue +): FunctionExpression { + const expr1 = fieldOrExpression(expr); + const expr2 = vectorToExpr(other); + return expr1.dotProduct(expr2); +} + +/** + * @beta + * + * Calculates the Euclidean distance between a field's vector value and a double array. + * + * ```typescript + * // Calculate the Euclidean distance between the 'location' field and a target location + * euclideanDistance("location", [37.7749, -122.4194]); + * ``` + * + * @param fieldName The name of the field containing the first vector. + * @param vector The other vector (as an array of doubles or VectorValue) to compare against. + * @return A new {@code Expr} representing the Euclidean distance between the two vectors. + */ +export function euclideanDistance( + fieldName: string, + vector: number[] | VectorValue +): FunctionExpression; + +/** + * @beta + * + * Calculates the Euclidean distance between a field's vector value and a vector expression. + * + * ```typescript + * // Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB' + * euclideanDistance("pointA", field("pointB")); + * ``` + * + * @param fieldName The name of the field containing the first vector. + * @param vectorExpression The other vector (represented as an Expr) to compare against. + * @return A new {@code Expr} representing the Euclidean distance between the two vectors. + */ +export function euclideanDistance( + fieldName: string, + vectorExpression: Expression +): FunctionExpression; + +/** + * @beta + * + * Calculates the Euclidean distance between a vector expression and a double array. + * + * ```typescript + * // Calculate the Euclidean distance between the 'location' field and a target location + * + * euclideanDistance(field("location"), [37.7749, -122.4194]); + * ``` + * + * @param vectorExpression The first vector (represented as an Expr) to compare against. + * @param vector The other vector (as an array of doubles or VectorValue) to compare against. + * @return A new {@code Expr} representing the Euclidean distance between the two vectors. + */ +export function euclideanDistance( + vectorExpression: Expression, + vector: number[] | VectorValue +): FunctionExpression; + +/** + * @beta + * + * Calculates the Euclidean distance between two vector expressions. + * + * ```typescript + * // Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB' + * euclideanDistance(field("pointA"), field("pointB")); + * ``` + * + * @param vectorExpression The first vector (represented as an Expr) to compare against. + * @param otherVectorExpression The other vector (represented as an Expr) to compare against. + * @return A new {@code Expr} representing the Euclidean distance between the two vectors. + */ +export function euclideanDistance( + vectorExpression: Expression, + otherVectorExpression: Expression +): FunctionExpression; +export function euclideanDistance( + expr: Expression | string, + other: Expression | number[] | VectorValue +): FunctionExpression { + const expr1 = fieldOrExpression(expr); + const expr2 = vectorToExpr(other); + return expr1.euclideanDistance(expr2); +} + +/** + * @beta + * + * Creates an expression that calculates the length of a Firestore Vector. + * + * ```typescript + * // Get the vector length (dimension) of the field 'embedding'. + * vectorLength(field("embedding")); + * ``` + * + * @param vectorExpression The expression representing the Firestore Vector. + * @return A new {@code Expr} representing the length of the array. + */ +export function vectorLength(vectorExpression: Expression): FunctionExpression; + +/** + * @beta + * + * Creates an expression that calculates the length of a Firestore Vector represented by a field. + * + * ```typescript + * // Get the vector length (dimension) of the field 'embedding'. + * vectorLength("embedding"); + * ``` + * + * @param fieldName The name of the field representing the Firestore Vector. + * @return A new {@code Expr} representing the length of the array. + */ +export function vectorLength(fieldName: string): FunctionExpression; +export function vectorLength(expr: Expression | string): FunctionExpression { + return fieldOrExpression(expr).vectorLength(); +} + +/** + * @beta + * + * Creates an expression that interprets an expression as the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'microseconds' field as microseconds since epoch. + * unixMicrosToTimestamp(field("microseconds")); + * ``` + * + * @param expr The expression representing the number of microseconds since epoch. + * @return A new {@code Expr} representing the timestamp. + */ +export function unixMicrosToTimestamp(expr: Expression): FunctionExpression; + +/** + * @beta + * + * Creates an expression that interprets a field's value as the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'microseconds' field as microseconds since epoch. + * unixMicrosToTimestamp("microseconds"); + * ``` + * + * @param fieldName The name of the field representing the number of microseconds since epoch. + * @return A new {@code Expr} representing the timestamp. + */ +export function unixMicrosToTimestamp(fieldName: string): FunctionExpression; +export function unixMicrosToTimestamp( + expr: Expression | string +): FunctionExpression { + return fieldOrExpression(expr).unixMicrosToTimestamp(); +} + +/** + * @beta + * + * Creates an expression that converts a timestamp expression to the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to microseconds since epoch. + * timestampToUnixMicros(field("timestamp")); + * ``` + * + * @param expr The expression representing the timestamp. + * @return A new {@code Expr} representing the number of microseconds since epoch. + */ +export function timestampToUnixMicros(expr: Expression): FunctionExpression; + +/** + * @beta + * + * Creates an expression that converts a timestamp field to the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to microseconds since epoch. + * timestampToUnixMicros("timestamp"); + * ``` + * + * @param fieldName The name of the field representing the timestamp. + * @return A new {@code Expr} representing the number of microseconds since epoch. + */ +export function timestampToUnixMicros(fieldName: string): FunctionExpression; +export function timestampToUnixMicros( + expr: Expression | string +): FunctionExpression { + return fieldOrExpression(expr).timestampToUnixMicros(); +} + +/** + * @beta + * + * Creates an expression that interprets an expression as the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'milliseconds' field as milliseconds since epoch. + * unixMillisToTimestamp(field("milliseconds")); + * ``` + * + * @param expr The expression representing the number of milliseconds since epoch. + * @return A new {@code Expr} representing the timestamp. + */ +export function unixMillisToTimestamp(expr: Expression): FunctionExpression; + +/** + * @beta + * + * Creates an expression that interprets a field's value as the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'milliseconds' field as milliseconds since epoch. + * unixMillisToTimestamp("milliseconds"); + * ``` + * + * @param fieldName The name of the field representing the number of milliseconds since epoch. + * @return A new {@code Expr} representing the timestamp. + */ +export function unixMillisToTimestamp(fieldName: string): FunctionExpression; +export function unixMillisToTimestamp( + expr: Expression | string +): FunctionExpression { + const normalizedExpr = fieldOrExpression(expr); + return normalizedExpr.unixMillisToTimestamp(); +} + +/** + * @beta + * + * Creates an expression that converts a timestamp expression to the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to milliseconds since epoch. + * timestampToUnixMillis(field("timestamp")); + * ``` + * + * @param expr The expression representing the timestamp. + * @return A new {@code Expr} representing the number of milliseconds since epoch. + */ +export function timestampToUnixMillis(expr: Expression): FunctionExpression; + +/** + * @beta + * + * Creates an expression that converts a timestamp field to the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to milliseconds since epoch. + * timestampToUnixMillis("timestamp"); + * ``` + * + * @param fieldName The name of the field representing the timestamp. + * @return A new {@code Expr} representing the number of milliseconds since epoch. + */ +export function timestampToUnixMillis(fieldName: string): FunctionExpression; +export function timestampToUnixMillis( + expr: Expression | string +): FunctionExpression { + const normalizedExpr = fieldOrExpression(expr); + return normalizedExpr.timestampToUnixMillis(); +} + +/** + * @beta + * + * Creates an expression that interprets an expression as the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'seconds' field as seconds since epoch. + * unixSecondsToTimestamp(field("seconds")); + * ``` + * + * @param expr The expression representing the number of seconds since epoch. + * @return A new {@code Expr} representing the timestamp. + */ +export function unixSecondsToTimestamp(expr: Expression): FunctionExpression; + +/** + * @beta + * + * Creates an expression that interprets a field's value as the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'seconds' field as seconds since epoch. + * unixSecondsToTimestamp("seconds"); + * ``` + * + * @param fieldName The name of the field representing the number of seconds since epoch. + * @return A new {@code Expr} representing the timestamp. + */ +export function unixSecondsToTimestamp(fieldName: string): FunctionExpression; +export function unixSecondsToTimestamp( + expr: Expression | string +): FunctionExpression { + const normalizedExpr = fieldOrExpression(expr); + return normalizedExpr.unixSecondsToTimestamp(); +} + +/** + * @beta + * + * Creates an expression that converts a timestamp expression to the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to seconds since epoch. + * timestampToUnixSeconds(field("timestamp")); + * ``` + * + * @param expr The expression representing the timestamp. + * @return A new {@code Expr} representing the number of seconds since epoch. + */ +export function timestampToUnixSeconds(expr: Expression): FunctionExpression; + +/** + * @beta + * + * Creates an expression that converts a timestamp field to the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to seconds since epoch. + * timestampToUnixSeconds("timestamp"); + * ``` + * + * @param fieldName The name of the field representing the timestamp. + * @return A new {@code Expr} representing the number of seconds since epoch. + */ +export function timestampToUnixSeconds(fieldName: string): FunctionExpression; +export function timestampToUnixSeconds( + expr: Expression | string +): FunctionExpression { + const normalizedExpr = fieldOrExpression(expr); + return normalizedExpr.timestampToUnixSeconds(); +} + +/** + * @beta + * + * Creates an expression that adds a specified amount of time to a timestamp. + * + * ```typescript + * // Add some duration determined by field 'unit' and 'amount' to the 'timestamp' field. + * timestampAdd(field("timestamp"), field("unit"), field("amount")); + * ``` + * + * @param timestamp The expression representing the timestamp. + * @param unit The expression evaluates to unit of time, must be one of 'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'. + * @param amount The expression evaluates to amount of the unit. + * @return A new {@code Expr} representing the resulting timestamp. + */ +export function timestampAdd( + timestamp: Expression, + unit: Expression, + amount: Expression +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that adds a specified amount of time to a timestamp. + * + * ```typescript + * // Add 1 day to the 'timestamp' field. + * timestampAdd(field("timestamp"), "day", 1); + * ``` + * + * @param timestamp The expression representing the timestamp. + * @param unit The unit of time to add (e.g., "day", "hour"). + * @param amount The amount of time to add. + * @return A new {@code Expr} representing the resulting timestamp. + */ +export function timestampAdd( + timestamp: Expression, + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that adds a specified amount of time to a timestamp represented by a field. + * + * ```typescript + * // Add 1 day to the 'timestamp' field. + * timestampAdd("timestamp", "day", 1); + * ``` + * + * @param fieldName The name of the field representing the timestamp. + * @param unit The unit of time to add (e.g., "day", "hour"). + * @param amount The amount of time to add. + * @return A new {@code Expr} representing the resulting timestamp. + */ +export function timestampAdd( + fieldName: string, + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number +): FunctionExpression; +export function timestampAdd( + timestamp: Expression | string, + unit: + | Expression + | 'microsecond' + | 'millisecond' + | 'second' + | 'minute' + | 'hour' + | 'day', + amount: Expression | number +): FunctionExpression { + const normalizedTimestamp = fieldOrExpression(timestamp); + const normalizedUnit = valueToDefaultExpr(unit); + const normalizedAmount = valueToDefaultExpr(amount); + return normalizedTimestamp.timestampAdd(normalizedUnit, normalizedAmount); +} + +/** + * @beta + * + * Creates an expression that subtracts a specified amount of time from a timestamp. + * + * ```typescript + * // Subtract some duration determined by field 'unit' and 'amount' from the 'timestamp' field. + * timestampSubtract(field("timestamp"), field("unit"), field("amount")); + * ``` + * + * @param timestamp The expression representing the timestamp. + * @param unit The expression evaluates to unit of time, must be one of 'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'. + * @param amount The expression evaluates to amount of the unit. + * @return A new {@code Expr} representing the resulting timestamp. + */ +export function timestampSubtract( + timestamp: Expression, + unit: Expression, + amount: Expression +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that subtracts a specified amount of time from a timestamp. + * + * ```typescript + * // Subtract 1 day from the 'timestamp' field. + * timestampSubtract(field("timestamp"), "day", 1); + * ``` + * + * @param timestamp The expression representing the timestamp. + * @param unit The unit of time to subtract (e.g., "day", "hour"). + * @param amount The amount of time to subtract. + * @return A new {@code Expr} representing the resulting timestamp. + */ +export function timestampSubtract( + timestamp: Expression, + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number +): FunctionExpression; + +/** + * @beta + * + * Creates an expression that subtracts a specified amount of time from a timestamp represented by a field. + * + * ```typescript + * // Subtract 1 day from the 'timestamp' field. + * timestampSubtract("timestamp", "day", 1); + * ``` + * + * @param fieldName The name of the field representing the timestamp. + * @param unit The unit of time to subtract (e.g., "day", "hour"). + * @param amount The amount of time to subtract. + * @return A new {@code Expr} representing the resulting timestamp. + */ +export function timestampSubtract( + fieldName: string, + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number +): FunctionExpression; +export function timestampSubtract( + timestamp: Expression | string, + unit: + | Expression + | 'microsecond' + | 'millisecond' + | 'second' + | 'minute' + | 'hour' + | 'day', + amount: Expression | number +): FunctionExpression { + const normalizedTimestamp = fieldOrExpression(timestamp); + const normalizedUnit = valueToDefaultExpr(unit); + const normalizedAmount = valueToDefaultExpr(amount); + return normalizedTimestamp.timestampSubtract( + normalizedUnit, + normalizedAmount + ); +} + +/** + * @beta + * + * Creates an expression that evaluates to the current server timestamp. + * + * ```typescript + * // Get the current server timestamp + * currentTimestamp() + * ``` + * + * @return A new Expression representing the current server timestamp. + */ +export function currentTimestamp(): FunctionExpression { + return new FunctionExpression('current_timestamp', [], 'currentTimestamp'); +} + +/** + * @beta + * + * Creates an expression that performs a logical 'AND' operation on multiple filter conditions. + * + * ```typescript + * // Check if the 'age' field is greater than 18 AND the 'city' field is "London" AND + * // the 'status' field is "active" + * const condition = and(greaterThan("age", 18), equal("city", "London"), equal("status", "active")); + * ``` + * + * @param first The first filter condition. + * @param second The second filter condition. + * @param more Additional filter conditions to 'AND' together. + * @return A new {@code Expr} representing the logical 'AND' operation. + */ +export function and( + first: BooleanExpression, + second: BooleanExpression, + ...more: BooleanExpression[] +): BooleanExpression { + return new FunctionExpression( + 'and', + [first, second, ...more], + 'and' + ).asBoolean(); +} + +/** + * @beta + * + * Creates an expression that performs a logical 'OR' operation on multiple filter conditions. + * + * ```typescript + * // Check if the 'age' field is greater than 18 OR the 'city' field is "London" OR + * // the 'status' field is "active" + * const condition = or(greaterThan("age", 18), equal("city", "London"), equal("status", "active")); + * ``` + * + * @param first The first filter condition. + * @param second The second filter condition. + * @param more Additional filter conditions to 'OR' together. + * @return A new {@code Expr} representing the logical 'OR' operation. + */ +export function or( + first: BooleanExpression, + second: BooleanExpression, + ...more: BooleanExpression[] +): BooleanExpression { + return new FunctionExpression( + 'or', + [first, second, ...more], + 'xor' + ).asBoolean(); +} + +/** + * @beta + * Creates an expression that returns the value of the base expression raised to the power of the exponent expression. + * + * ```typescript + * // Raise the value of the 'base' field to the power of the 'exponent' field. + * pow(field("base"), field("exponent")); + * ``` + * + * @param base The expression to raise to the power of the exponent. + * @param exponent The expression to raise the base to the power of. + * @return A new `Expr` representing the power operation. + */ +export function pow(base: Expression, exponent: Expression): FunctionExpression; + +/** + * @beta + * Creates an expression that returns the value of the base expression raised to the power of the exponent. + * + * ```typescript + * // Raise the value of the 'base' field to the power of 2. + * pow(field("base"), 2); + * ``` + * + * @param base The expression to raise to the power of the exponent. + * @param exponent The constant value to raise the base to the power of. + * @return A new `Expr` representing the power operation. + */ +export function pow(base: Expression, exponent: number): FunctionExpression; + +/** + * @beta + * Creates an expression that returns the value of the base field raised to the power of the exponent expression. + * + * ```typescript + * // Raise the value of the 'base' field to the power of the 'exponent' field. + * pow("base", field("exponent")); + * ``` + * + * @param base The name of the field to raise to the power of the exponent. + * @param exponent The expression to raise the base to the power of. + * @return A new `Expr` representing the power operation. + */ +export function pow(base: string, exponent: Expression): FunctionExpression; + +/** + * @beta + * Creates an expression that returns the value of the base field raised to the power of the exponent. + * + * ```typescript + * // Raise the value of the 'base' field to the power of 2. + * pow("base", 2); + * ``` + * + * @param base The name of the field to raise to the power of the exponent. + * @param exponent The constant value to raise the base to the power of. + * @return A new `Expr` representing the power operation. + */ +export function pow(base: string, exponent: number): FunctionExpression; +export function pow( + base: Expression | string, + exponent: Expression | number +): FunctionExpression { + return fieldOrExpression(base).pow(exponent as number); +} + +/** + * @beta + * Creates an expression that rounds a numeric value to the nearest whole number. + * + * ```typescript + * // Round the value of the 'price' field. + * round("price"); + * ``` + * + * @param fieldName The name of the field to round. + * @return A new `Expr` representing the rounded value. + */ +export function round(fieldName: string): FunctionExpression; + +/** + * @beta + * Creates an expression that rounds a numeric value to the nearest whole number. + * + * ```typescript + * // Round the value of the 'price' field. + * round(field("price")); + * ``` + * + * @param expression An expression evaluating to a numeric value, which will be rounded. + * @return A new `Expr` representing the rounded value. + */ +export function round(expression: Expression): FunctionExpression; + +/** + * @beta + * Creates an expression that rounds a numeric value to the specified number of decimal places. + * + * ```typescript + * // Round the value of the 'price' field to two decimal places. + * round("price", 2); + * ``` + * + * @param fieldName The name of the field to round. + * @param decimalPlaces A constant or expression specifying the rounding precision in decimal places. + * @return A new `Expr` representing the rounded value. + */ +export function round( + fieldName: string, + decimalPlaces: number | Expression +): FunctionExpression; + +/** + * @beta + * Creates an expression that rounds a numeric value to the specified number of decimal places. + * + * ```typescript + * // Round the value of the 'price' field to two decimal places. + * round(field("price"), constant(2)); + * ``` + * + * @param expression An expression evaluating to a numeric value, which will be rounded. + * @param decimalPlaces A constant or expression specifying the rounding precision in decimal places. + * @return A new `Expr` representing the rounded value. + */ +export function round( + expression: Expression, + decimalPlaces: number | Expression +): FunctionExpression; +export function round( + expr: Expression | string, + decimalPlaces?: number | Expression +): FunctionExpression { + if (decimalPlaces === undefined) { + return fieldOrExpression(expr).round(); + } else { + return fieldOrExpression(expr).round(valueToDefaultExpr(decimalPlaces)); + } +} + +/** + * @beta + * Creates an expression that returns the collection ID from a path. + * + * ```typescript + * // Get the collection ID from a path. + * collectionId("__name__"); + * ``` + * + * @param fieldName The name of the field to get the collection ID from. + * @return A new {@code Expr} representing the collectionId operation. + */ +export function collectionId(fieldName: string): FunctionExpression; + +/** + * @beta + * Creates an expression that returns the collection ID from a path. + * + * ```typescript + * // Get the collection ID from a path. + * collectionId(field("__name__")); + * ``` + * + * @param expression An expression evaluating to a path, which the collection ID will be extracted from. + * @return A new {@code Expr} representing the collectionId operation. + */ +export function collectionId(expression: Expression): FunctionExpression; +export function collectionId(expr: Expression | string): FunctionExpression { + return fieldOrExpression(expr).collectionId(); +} + +/** + * @beta + * Creates an expression that calculates the length of a string, array, map, vector, or bytes. + * + * ```typescript + * // Get the length of the 'name' field. + * length("name"); + * + * // Get the number of items in the 'cart' array. + * length("cart"); + * ``` + * + * @param fieldName The name of the field to calculate the length of. + * @return A new `Expr` representing the length of the string, array, map, vector, or bytes. + */ +export function length(fieldName: string): FunctionExpression; + +/** + * @beta + * Creates an expression that calculates the length of a string, array, map, vector, or bytes. + * + * ```typescript + * // Get the length of the 'name' field. + * length(field("name")); + * + * // Get the number of items in the 'cart' array. + * length(field("cart")); + * ``` + * + * @param expression An expression evaluating to a string, array, map, vector, or bytes, which the length will be calculated for. + * @return A new `Expr` representing the length of the string, array, map, vector, or bytes. + */ +export function length(expression: Expression): FunctionExpression; +export function length(expr: Expression | string): FunctionExpression { + return fieldOrExpression(expr).length(); +} + +/** + * @beta + * Creates an expression that computes the natural logarithm of a numeric value. + * + * ```typescript + * // Compute the natural logarithm of the 'value' field. + * ln("value"); + * ``` + * + * @param fieldName The name of the field to compute the natural logarithm of. + * @return A new `Expr` representing the natural logarithm of the numeric value. + */ +export function ln(fieldName: string): FunctionExpression; + +/** + * @beta + * Creates an expression that computes the natural logarithm of a numeric value. + * + * ```typescript + * // Compute the natural logarithm of the 'value' field. + * ln(field("value")); + * ``` + * + * @param expression An expression evaluating to a numeric value, which the natural logarithm will be computed for. + * @return A new `Expr` representing the natural logarithm of the numeric value. + */ +export function ln(expression: Expression): FunctionExpression; +export function ln(expr: Expression | string): FunctionExpression { + return fieldOrExpression(expr).ln(); +} + +/** + * @beta + * Creates an expression that computes the logarithm of an expression to a given base. + * + * ```typescript + * // Compute the logarithm of the 'value' field with base 10. + * log(field("value"), 10); + * ``` + * + * @param expression An expression evaluating to a numeric value, which the logarithm will be computed for. + * @param base The base of the logarithm. + * @return A new {@code Expr} representing the logarithm of the numeric value. + */ +export function log(expression: Expression, base: number): FunctionExpression; +/** + * @beta + * Creates an expression that computes the logarithm of an expression to a given base. + * + * ```typescript + * // Compute the logarithm of the 'value' field with the base in the 'base' field. + * log(field("value"), field("base")); + * ``` + * + * @param expression An expression evaluating to a numeric value, which the logarithm will be computed for. + * @param base The base of the logarithm. + * @return A new {@code Expr} representing the logarithm of the numeric value. + */ +export function log( + expression: Expression, + base: Expression +): FunctionExpression; +/** + * @beta + * Creates an expression that computes the logarithm of a field to a given base. + * + * ```typescript + * // Compute the logarithm of the 'value' field with base 10. + * log("value", 10); + * ``` + * + * @param fieldName The name of the field to compute the logarithm of. + * @param base The base of the logarithm. + * @return A new {@code Expr} representing the logarithm of the numeric value. + */ +export function log(fieldName: string, base: number): FunctionExpression; +/** + * @beta + * Creates an expression that computes the logarithm of a field to a given base. + * + * ```typescript + * // Compute the logarithm of the 'value' field with the base in the 'base' field. + * log("value", field("base")); + * ``` + * + * @param fieldName The name of the field to compute the logarithm of. + * @param base The base of the logarithm. + * @return A new {@code Expr} representing the logarithm of the numeric value. + */ +export function log(fieldName: string, base: Expression): FunctionExpression; +export function log( + expr: Expression | string, + base: number | Expression +): FunctionExpression { + return new FunctionExpression('log', [ + fieldOrExpression(expr), + valueToDefaultExpr(base) + ]); +} + +/** + * @beta + * Creates an expression that computes the square root of a numeric value. + * + * ```typescript + * // Compute the square root of the 'value' field. + * sqrt(field("value")); + * ``` + * + * @param expression An expression evaluating to a numeric value, which the square root will be computed for. + * @return A new {@code Expr} representing the square root of the numeric value. + */ +export function sqrt(expression: Expression): FunctionExpression; +/** + * @beta + * Creates an expression that computes the square root of a numeric value. + * + * ```typescript + * // Compute the square root of the 'value' field. + * sqrt("value"); + * ``` + * + * @param fieldName The name of the field to compute the square root of. + * @return A new {@code Expr} representing the square root of the numeric value. + */ +export function sqrt(fieldName: string): FunctionExpression; +export function sqrt(expr: Expression | string): FunctionExpression { + return fieldOrExpression(expr).sqrt(); +} + +/** + * @beta + * Creates an expression that reverses a string. + * + * ```typescript + * // Reverse the value of the 'myString' field. + * strReverse(field("myString")); + * ``` + * + * @param stringExpression An expression evaluating to a string value, which will be reversed. + * @return A new {@code Expr} representing the reversed string. + */ +export function stringReverse(stringExpression: Expression): FunctionExpression; + +/** + * @beta + * Creates an expression that reverses a string value in the specified field. + * + * ```typescript + * // Reverse the value of the 'myString' field. + * strReverse("myString"); + * ``` + * + * @param field The name of the field representing the string to reverse. + * @return A new {@code Expr} representing the reversed string. + */ +export function stringReverse(field: string): FunctionExpression; +export function stringReverse(expr: Expression | string): FunctionExpression { + return fieldOrExpression(expr).stringReverse(); +} + +/** + * @beta + * Creates an expression that concatenates strings, arrays, or blobs. Types cannot be mixed. + * + * ```typescript + * // Concatenate the 'firstName' and 'lastName' fields with a space in between. + * concat(field("firstName"), " ", field("lastName")) + * ``` + * + * @param first The first expressions to concatenate. + * @param second The second literal or expression to concatenate. + * @param others Additional literals or expressions to concatenate. + * @return A new `Expression` representing the concatenation. + */ +export function concat( + first: Expression, + second: Expression | unknown, + ...others: Array +): FunctionExpression; + +/** + * @beta + * Creates an expression that concatenates strings, arrays, or blobs. Types cannot be mixed. + * + * ```typescript + * // Concatenate a field with a literal string. + * concat(field("firstName"), "Doe") + * ``` + * + * @param fieldName The name of a field to concatenate. + * @param second The second literal or expression to concatenate. + * @param others Additional literal or expressions to concatenate. + * @return A new `Expression` representing the concatenation. + */ +export function concat( + fieldName: string, + second: Expression | unknown, + ...others: Array +): FunctionExpression; + +export function concat( + fieldNameOrExpression: string | Expression, + second: Expression | unknown, + ...others: Array +): FunctionExpression { + return new FunctionExpression('concat', [ + fieldOrExpression(fieldNameOrExpression), + valueToDefaultExpr(second), + ...others.map(valueToDefaultExpr) + ]); +} + +/** + * @beta + * Creates an expression that computes the absolute value of a numeric value. + * + * @param expr The expression to compute the absolute value of. + * @return A new {@code Expr} representing the absolute value of the numeric value. + */ +export function abs(expr: Expression): FunctionExpression; + +/** + * @beta + * Creates an expression that computes the absolute value of a numeric value. + * + * @param fieldName The field to compute the absolute value of. + * @return A new {@code Expr} representing the absolute value of the numeric value. + */ +export function abs(fieldName: string): FunctionExpression; +export function abs(expr: Expression | string): FunctionExpression { + return fieldOrExpression(expr).abs(); +} + +/** + * @beta + * Creates an expression that returns the `elseExpr` argument if `ifExpr` is absent, else return + * the result of the `ifExpr` argument evaluation. + * + * ```typescript + * // Returns the value of the optional field 'optional_field', or returns 'default_value' + * // if the field is absent. + * ifAbsent(field("optional_field"), constant("default_value")) + * ``` + * + * @param ifExpr The expression to check for absence. + * @param elseExpr The expression that will be evaluated and returned if [ifExpr] is absent. + * @return A new Expression representing the ifAbsent operation. + */ +export function ifAbsent(ifExpr: Expression, elseExpr: Expression): Expression; + +/** + * @beta + * Creates an expression that returns the `elseValue` argument if `ifExpr` is absent, else + * return the result of the `ifExpr` argument evaluation. + * + * ```typescript + * // Returns the value of the optional field 'optional_field', or returns 'default_value' + * // if the field is absent. + * ifAbsent(field("optional_field"), "default_value") + * ``` + * + * @param ifExpr The expression to check for absence. + * @param elseValue The value that will be returned if `ifExpr` evaluates to an absent value. + * @return A new [Expression] representing the ifAbsent operation. + */ +export function ifAbsent(ifExpr: Expression, elseValue: unknown): Expression; + +/** + * @beta + * Creates an expression that returns the `elseExpr` argument if `ifFieldName` is absent, else + * return the value of the field. + * + * ```typescript + * // Returns the value of the optional field 'optional_field', or returns the value of + * // 'default_field' if 'optional_field' is absent. + * ifAbsent("optional_field", field("default_field")) + * ``` + * + * @param ifFieldName The field to check for absence. + * @param elseExpr The expression that will be evaluated and returned if `ifFieldName` is + * absent. + * @return A new Expression representing the ifAbsent operation. + */ +export function ifAbsent(ifFieldName: string, elseExpr: Expression): Expression; + +/** + * @beta + * Creates an expression that returns the `elseValue` argument if `ifFieldName` is absent, else + * return the value of the field. + * + * ```typescript + * // Returns the value of the optional field 'optional_field', or returns 'default_value' + * // if the field is absent. + * ifAbsent("optional_field", "default_value") + * ``` + * + * @param ifFieldName The field to check for absence. + * @param elseValue The value that will be returned if [ifFieldName] is absent. + * @return A new Expression representing the ifAbsent operation. + */ +export function ifAbsent( + ifFieldName: string | Expression, + elseValue: Expression | unknown +): Expression; +export function ifAbsent( + fieldNameOrExpression: string | Expression, + elseValue: Expression | unknown +): Expression { + return fieldOrExpression(fieldNameOrExpression).ifAbsent( + valueToDefaultExpr(elseValue) + ); +} + +/** + * @beta + * Creates an expression that joins the elements of an array into a string. + * + * ```typescript + * // Join the elements of the 'tags' field with a comma and space. + * join("tags", ", ") + * ``` + * + * @param arrayFieldName The name of the field containing the array. + * @param delimiter The string to use as a delimiter. + * @return A new Expression representing the join operation. + */ +export function join(arrayFieldName: string, delimiter: string): Expression; + +/** + * @beta + * Creates an expression that joins the elements of an array into a string. + * + * ```typescript + * // Join an array of string using the delimiter from the 'separator' field. + * join(array(['foo', 'bar']), field("separator")) + * ``` + * + * @param arrayExpression An expression that evaluates to an array. + * @param delimiterExpression The expression that evaluates to the delimiter string. + * @return A new Expression representing the join operation. + */ +export function join( + arrayExpression: Expression, + delimiterExpression: Expression +): Expression; + +/** + * @beta + * Creates an expression that joins the elements of an array into a string. + * + * ```typescript + * // Join the elements of the 'tags' field with a comma and space. + * join(field("tags"), ", ") + * ``` + * + * @param arrayExpression An expression that evaluates to an array. + * @param delimiter The string to use as a delimiter. + * @return A new Expression representing the join operation. + */ +export function join( + arrayExpression: Expression, + delimiter: string +): Expression; + +/** + * @beta + * Creates an expression that joins the elements of an array into a string. + * + * ```typescript + * // Join the elements of the 'tags' field with the delimiter from the 'separator' field. + * join('tags', field("separator")) + * ``` + * + * @param arrayFieldName The name of the field containing the array. + * @param delimiterExpression The expression that evaluates to the delimiter string. + * @return A new Expression representing the join operation. + */ +export function join( + arrayFieldName: string, + delimiterExpression: Expression +): Expression; +export function join( + fieldNameOrExpression: string | Expression, + delimiterValueOrExpression: Expression | string +): Expression { + return fieldOrExpression(fieldNameOrExpression).join( + valueToDefaultExpr(delimiterValueOrExpression) + ); +} + +/** + * @beta + * Creates an expression that computes the base-10 logarithm of a numeric value. + * + * ```typescript + * // Compute the base-10 logarithm of the 'value' field. + * log10("value"); + * ``` + * + * @param fieldName The name of the field to compute the base-10 logarithm of. + * @return A new `Expr` representing the base-10 logarithm of the numeric value. + */ +export function log10(fieldName: string): FunctionExpression; + +/** + * @beta + * Creates an expression that computes the base-10 logarithm of a numeric value. + * + * ```typescript + * // Compute the base-10 logarithm of the 'value' field. + * log10(field("value")); + * ``` + * + * @param expression An expression evaluating to a numeric value, which the base-10 logarithm will be computed for. + * @return A new `Expr` representing the base-10 logarithm of the numeric value. + */ +export function log10(expression: Expression): FunctionExpression; +export function log10(expr: Expression | string): FunctionExpression { + return fieldOrExpression(expr).log10(); +} + +/** + * @beta + * Creates an expression that computes the sum of the elements in an array. + * + * ```typescript + * // Compute the sum of the elements in the 'scores' field. + * arraySum("scores"); + * ``` + * + * @param fieldName The name of the field to compute the sum of. + * @return A new `Expr` representing the sum of the elements in the array. + */ +export function arraySum(fieldName: string): FunctionExpression; + +/** + * @beta + * Creates an expression that computes the sum of the elements in an array. + * + * ```typescript + * // Compute the sum of the elements in the 'scores' field. + * arraySum(field("scores")); + * ``` + * + * @param expression An expression evaluating to a numeric array, which the sum will be computed for. + * @return A new `Expr` representing the sum of the elements in the array. + */ +export function arraySum(expression: Expression): FunctionExpression; +export function arraySum(expr: Expression | string): FunctionExpression { + return fieldOrExpression(expr).arraySum(); +} + +/** + * @beta + * Creates an expression that splits the value of a field on the provided delimiter. + * + * @example + * ```typescript + * // Split the 'scoresCsv' field on delimiter ',' + * split('scoresCsv', ',') + * ``` + * + * @param fieldName Split the value in this field. + * @param delimiter Split on this delimiter. + * + * @return A new {@code Expression} representing the split function. + */ +export function split(fieldName: string, delimiter: string): FunctionExpression; + +/** + * @beta + * Creates an expression that splits the value of a field on the provided delimiter. + * + * @example + * ```typescript + * // Split the 'scores' field on delimiter ',' or ':' depending on the stored format + * split('scores', conditional(field('format').equal('csv'), constant(','), constant(':')) + * ``` + * + * @param fieldName Split the value in this field. + * @param delimiter Split on this delimiter returned by evaluating this expression. + * + * @return A new {@code Expression} representing the split function. + */ +export function split( + fieldName: string, + delimiter: Expression +): FunctionExpression; + +/** + * @beta + * Creates an expression that splits a string into an array of substrings based on the provided delimiter. + * + * @example + * ```typescript + * // Split the 'scoresCsv' field on delimiter ',' + * split(field('scoresCsv'), ',') + * ``` + * + * @param expression Split the result of this expression. + * @param delimiter Split on this delimiter. + * + * @return A new {@code Expression} representing the split function. + */ +export function split( + expression: Expression, + delimiter: string +): FunctionExpression; + +/** + * @beta + * Creates an expression that splits a string into an array of substrings based on the provided delimiter. + * + * @example + * ```typescript + * // Split the 'scores' field on delimiter ',' or ':' depending on the stored format + * split(field('scores'), conditional(field('format').equal('csv'), constant(','), constant(':')) + * ``` + * + * @param expression Split the result of this expression. + * @param delimiter Split on this delimiter returned by evaluating this expression. + * + * @return A new {@code Expression} representing the split function. + */ +export function split( + expression: Expression, + delimiter: Expression +): FunctionExpression; +export function split( + fieldNameOrExpression: string | Expression, + delimiter: string | Expression +): FunctionExpression { + return fieldOrExpression(fieldNameOrExpression).split( + valueToDefaultExpr(delimiter) + ); +} + +/** + * @beta + * Creates an expression that truncates a timestamp to a specified granularity. + * + * @example + * ```typescript + * // Truncate the 'createdAt' timestamp to the beginning of the day. + * field('createdAt').timestampTruncate('day') + * ``` + * + * @param fieldName Truncate the timestamp value contained in this field. + * @param granularity The granularity to truncate to. + * @param timezone The timezone to use for truncation. Valid values are from + * the TZ database (e.g., "America/Los_Angeles") or in the format "Etc/GMT-1". + * @return A new {Expression} representing the truncated timestamp. + */ +export function timestampTruncate( + fieldName: string, + granularity: TimeGranularity, + timezone?: string | Expression +): FunctionExpression; + +/** + * @beta + * Creates an expression that truncates a timestamp to a specified granularity. + * + * @example + * ```typescript + * // Truncate the 'createdAt' timestamp to the granularity specified in the field 'granularity'. + * field('createdAt').timestampTruncate(field('granularity')) + * ``` + * + * @param fieldName Truncate the timestamp value contained in this field. + * @param granularity The granularity to truncate to. + * @param timezone The timezone to use for truncation. Valid values are from + * the TZ database (e.g., "America/Los_Angeles") or in the format "Etc/GMT-1". + * @return A new {Expression} representing the truncated timestamp. + */ +export function timestampTruncate( + fieldName: string, + granularity: Expression, + timezone?: string | Expression +): FunctionExpression; + +/** + * @beta + * Creates an expression that truncates a timestamp to a specified granularity. + * + * @example + * ```typescript + * // Truncate the 'createdAt' timestamp to the beginning of the day. + * field('createdAt').timestampTruncate('day') + * ``` + * + * @param timestampExpression Truncate the timestamp value that is returned by this expression. + * @param granularity The granularity to truncate to. + * @param timezone The timezone to use for truncation. Valid values are from + * the TZ database (e.g., "America/Los_Angeles") or in the format "Etc/GMT-1". + * @return A new {Expression} representing the truncated timestamp. + */ +export function timestampTruncate( + timestampExpression: Expression, + granularity: TimeGranularity, + timezone?: string | Expression +): FunctionExpression; + +/** + * @beta + * Creates an expression that truncates a timestamp to a specified granularity. + * + * @example + * ```typescript + * // Truncate the 'createdAt' timestamp to the granularity specified in the field 'granularity'. + * field('createdAt').timestampTruncate(field('granularity')) + * ``` + * + * @param timestampExpression Truncate the timestamp value that is returned by this expression. + * @param granularity The granularity to truncate to. + * @param timezone The timezone to use for truncation. Valid values are from + * the TZ database (e.g., "America/Los_Angeles") or in the format "Etc/GMT-1". + * @return A new {Expression} representing the truncated timestamp. + */ +export function timestampTruncate( + timestampExpression: Expression, + granularity: Expression, + timezone?: string | Expression +): FunctionExpression; +export function timestampTruncate( + fieldNameOrExpression: string | Expression, + granularity: TimeGranularity | Expression, + timezone?: string | Expression +): FunctionExpression { + const internalGranularity = isString(granularity) + ? valueToDefaultExpr(granularity.toLowerCase()) + : granularity; + return fieldOrExpression(fieldNameOrExpression).timestampTruncate( + internalGranularity, + timezone + ); +} + +/** + * @beta + * Creates an expression that returns the data type of the data in the specified field. + * + * @example + * ```typescript + * // Get the data type of the value in field 'title' + * type('title') + * ``` + * + * @return A new {Expression} representing the data type. + */ +export function type(fieldName: string): FunctionExpression; +/** + * @beta + * Creates an expression that returns the data type of an expression's result. + * + * @example + * ```typescript + * // Get the data type of a conditional expression + * type(conditional(exists('foo'), constant(1), constant(true))) + * ``` + * + * @return A new {Expression} representing the data type. + */ +export function type(expression: Expression): FunctionExpression; +export function type( + fieldNameOrExpression: string | Expression +): FunctionExpression { + return fieldOrExpression(fieldNameOrExpression).type(); +} + +// TODO(new-expression): Add new top-level expression function definitions above this line + +/** + * @beta + * + * Creates an {@link Ordering} that sorts documents in ascending order based on an expression. + * + * ```typescript + * // Sort documents by the 'name' field in lowercase in ascending order + * firestore.pipeline().collection("users") + * .sort(ascending(field("name").toLower())); + * ``` + * + * @param expr The expression to create an ascending ordering for. + * @return A new `Ordering` for ascending sorting. + */ +export function ascending(expr: Expression): Ordering; + +/** + * @beta + * + * Creates an {@link Ordering} that sorts documents in ascending order based on a field. + * + * ```typescript + * // Sort documents by the 'name' field in ascending order + * firestore.pipeline().collection("users") + * .sort(ascending("name")); + * ``` + * + * @param fieldName The field to create an ascending ordering for. + * @return A new `Ordering` for ascending sorting. + */ +export function ascending(fieldName: string): Ordering; +export function ascending(field: Expression | string): Ordering { + return new Ordering(fieldOrExpression(field), 'ascending', 'ascending'); +} + +/** + * @beta + * + * Creates an {@link Ordering} that sorts documents in descending order based on an expression. + * + * ```typescript + * // Sort documents by the 'name' field in lowercase in descending order + * firestore.pipeline().collection("users") + * .sort(descending(field("name").toLower())); + * ``` + * + * @param expr The expression to create a descending ordering for. + * @return A new `Ordering` for descending sorting. + */ +export function descending(expr: Expression): Ordering; + +/** + * @beta + * + * Creates an {@link Ordering} that sorts documents in descending order based on a field. + * + * ```typescript + * // Sort documents by the 'name' field in descending order + * firestore.pipeline().collection("users") + * .sort(descending("name")); + * ``` + * + * @param fieldName The field to create a descending ordering for. + * @return A new `Ordering` for descending sorting. + */ +export function descending(fieldName: string): Ordering; +export function descending(field: Expression | string): Ordering { + return new Ordering(fieldOrExpression(field), 'descending', 'descending'); +} + +/** + * @beta + * + * Represents an ordering criterion for sorting documents in a Firestore pipeline. + * + * You create `Ordering` instances using the `ascending` and `descending` helper functions. + */ +export class Ordering implements ProtoValueSerializable, UserData { + constructor( + public readonly expr: Expression, + public readonly direction: 'ascending' | 'descending', + readonly _methodName: string | undefined + ) {} + + /** + * @private + * @internal + */ + _toProto(serializer: JsonProtoSerializer): ProtoValue { + return { + mapValue: { + fields: { + direction: toStringValue(this.direction), + expression: this.expr._toProto(serializer) + } + } + }; + } + + /** + * @private + * @internal + */ + _readUserData(context: ParseContext): void { + this.expr._readUserData(context); + } + + _protoValueType: 'ProtoValue' = 'ProtoValue'; +} + +export function isSelectable(val: unknown): val is Selectable { + const candidate = val as Selectable; + return ( + candidate.selectable && isString(candidate.alias) && isExpr(candidate.expr) + ); +} + +export function isOrdering(val: unknown): val is Ordering { + const candidate = val as Ordering; + return ( + isExpr(candidate.expr) && + (candidate.direction === 'ascending' || + candidate.direction === 'descending') + ); +} + +export function isAliasedAggregate(val: unknown): val is AliasedAggregate { + const candidate = val as AliasedAggregate; + return ( + isString(candidate.alias) && + candidate.aggregate instanceof AggregateFunction + ); +} + +export function isExpr(val: unknown): val is Expression { + return val instanceof Expression; +} + +export function isBooleanExpr(val: unknown): val is BooleanExpression { + return val instanceof BooleanExpression; +} + +export function isField(val: unknown): val is Field { + return val instanceof Field; +} + +export function toField(value: string | Field): Field { + if (isString(value)) { + const result = field(value); + return result; + } else { + return value as Field; + } +} diff --git a/packages/firestore/src/lite-api/pipeline-result.ts b/packages/firestore/src/lite-api/pipeline-result.ts new file mode 100644 index 0000000000..a853a65299 --- /dev/null +++ b/packages/firestore/src/lite-api/pipeline-result.ts @@ -0,0 +1,278 @@ +/** + * @license + * Copyright 2024 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 { ObjectValue } from '../model/object_value'; +import { firestoreV1ApiClientInterfaces } from '../protos/firestore_proto_api'; +import { isOptionalEqual } from '../util/misc'; + +import { Field, isField } from './expressions'; +import { FieldPath } from './field_path'; +import { Pipeline } from './pipeline'; +import { DocumentData, DocumentReference, refEqual } from './reference'; +import { Timestamp } from './timestamp'; +import { fieldPathFromArgument } from './user_data_reader'; +import { AbstractUserDataWriter } from './user_data_writer'; + +/** + * @beta + * Represents the results of a Firestore pipeline execution. + * + * A `PipelineSnapshot` contains zero or more {@link PipelineResult} objects + * representing the documents returned by a pipeline query. It provides methods + * to iterate over the documents and access metadata about the query results. + * + * @example + * ```typescript + * const snapshot: PipelineSnapshot = await firestore + * .pipeline() + * .collection('myCollection') + * .where(field('value').greaterThan(10)) + * .execute(); + * + * snapshot.results.forEach(doc => { + * console.log(doc.id, '=>', doc.data()); + * }); + * ``` + */ +export class PipelineSnapshot { + private readonly _pipeline: Pipeline; + private readonly _executionTime: Timestamp | undefined; + private readonly _results: PipelineResult[]; + constructor( + pipeline: Pipeline, + results: PipelineResult[], + executionTime?: Timestamp + ) { + this._pipeline = pipeline; + this._executionTime = executionTime; + this._results = results; + } + + /** + * @beta An array of all the results in the `PipelineSnapshot`. + */ + get results(): PipelineResult[] { + return this._results; + } + + /** + * @beta + * The time at which the pipeline producing this result is executed. + * + * @type {Timestamp} + * @readonly + * + */ + get executionTime(): Timestamp { + if (this._executionTime === undefined) { + throw new Error( + "'executionTime' is expected to exist, but it is undefined" + ); + } + return this._executionTime; + } +} + +/** + * @beta + * + * A PipelineResult contains data read from a Firestore Pipeline. The data can be extracted with the + * {@link #data()} or {@link #get(String)} methods. + * + *

If the PipelineResult represents a non-document result, `ref` will return a undefined + * value. + */ +export class PipelineResult { + private readonly _userDataWriter: AbstractUserDataWriter; + + private readonly _createTime: Timestamp | undefined; + private readonly _updateTime: Timestamp | undefined; + + /** + * @internal + * @private + */ + readonly _ref: DocumentReference | undefined; + + /** + * @internal + * @private + */ + readonly _fields: ObjectValue; + + /** + * @private + * @internal + * + * @param userDataWriter The serializer used to encode/decode protobuf. + * @param ref The reference to the document. + * @param fields The fields of the Firestore `Document` Protobuf backing + * this document. + * @param createTime The time when the document was created if the result is a document, undefined otherwise. + * @param updateTime The time when the document was last updated if the result is a document, undefined otherwise. + */ + constructor( + userDataWriter: AbstractUserDataWriter, + fields: ObjectValue, + ref?: DocumentReference, + createTime?: Timestamp, + updateTime?: Timestamp + ) { + this._ref = ref; + this._userDataWriter = userDataWriter; + this._createTime = createTime; + this._updateTime = updateTime; + this._fields = fields; + } + + /** + * @beta + * The reference of the document, if it is a document; otherwise `undefined`. + */ + get ref(): DocumentReference | undefined { + return this._ref; + } + + /** + * @beta + * The ID of the document for which this PipelineResult contains data, if it is a document; otherwise `undefined`. + * + * @type {string} + * @readonly + * + */ + get id(): string | undefined { + return this._ref?.id; + } + + /** + * @beta + * The time the document was created. Undefined if this result is not a document. + * + * @type {Timestamp|undefined} + * @readonly + */ + get createTime(): Timestamp | undefined { + return this._createTime; + } + + /** + * @beta + * The time the document was last updated (at the time the snapshot was + * generated). Undefined if this result is not a document. + * + * @type {Timestamp|undefined} + * @readonly + */ + get updateTime(): Timestamp | undefined { + return this._updateTime; + } + + /** + * @beta + * Retrieves all fields in the result as an object. + * + * @returns {T} An object containing all fields in the document or + * 'undefined' if the document doesn't exist. + * + * @example + * ``` + * let p = firestore.pipeline().collection('col'); + * + * p.execute().then(results => { + * let data = results[0].data(); + * console.log(`Retrieved data: ${JSON.stringify(data)}`); + * }); + * ``` + */ + data(): AppModelType { + return this._userDataWriter.convertValue( + this._fields.value + ) as AppModelType; + } + + /** + * @internal + * @private + * + * Retrieves all fields in the result as a proto value. + * + * @returns An `Object` containing all fields in the result. + */ + _fieldsProto(): { [key: string]: firestoreV1ApiClientInterfaces.Value } { + // Return a cloned value to prevent manipulation of the Snapshot's data + return this._fields.clone().value.mapValue.fields!; + } + + /** + * @beta + * Retrieves the field specified by `field`. + * + * @param {string|FieldPath|Field} 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 p = firestore.pipeline().collection('col'); + * + * p.execute().then(results => { + * let field = results[0].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(fieldPath: string | FieldPath | Field): any { + if (this._fields === undefined) { + return undefined; + } + if (isField(fieldPath)) { + fieldPath = fieldPath.fieldName; + } + + const value = this._fields.field( + fieldPathFromArgument('DocumentSnapshot.get', fieldPath) + ); + if (value !== null) { + return this._userDataWriter.convertValue(value); + } + } +} + +/** + * @beta + * Test equality of two PipelineResults. + * @param left + * @param right + */ +export function pipelineResultEqual( + left: PipelineResult, + right: PipelineResult +): boolean { + if (left === right) { + return true; + } + + return ( + isOptionalEqual(left._ref, right._ref, refEqual) && + isOptionalEqual(left._fields, right._fields, (l, r) => l.isEqual(r)) + ); +} diff --git a/packages/firestore/src/lite-api/pipeline-source.ts b/packages/firestore/src/lite-api/pipeline-source.ts new file mode 100644 index 0000000000..0186ca3526 --- /dev/null +++ b/packages/firestore/src/lite-api/pipeline-source.ts @@ -0,0 +1,278 @@ +/** + * @license + * Copyright 2024 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 { DatabaseId } from '../core/database_info'; +import { toPipeline } from '../core/pipeline-util'; +import { Code, FirestoreError } from '../util/error'; +import { isString } from '../util/types'; + +import { Pipeline } from './pipeline'; +import { + CollectionReference, + DocumentReference, + isCollectionReference, + Query +} from './reference'; +import { + CollectionGroupSource, + CollectionSource, + DatabaseSource, + DocumentsSource, + Stage +} from './stage'; +import { + CollectionGroupStageOptions, + CollectionStageOptions, + DatabaseStageOptions, + DocumentsStageOptions +} from './stage_options'; +import { UserDataReader, UserDataSource } from './user_data_reader'; + +/** + * @beta + * Provides the entry point for defining the data source of a Firestore {@link Pipeline}. + * + * Use the methods of this class (e.g., {@link PipelineSource#collection}, {@link PipelineSource#collectionGroup}, + * {@link PipelineSource#database}, or {@link PipelineSource#documents}) to specify the initial data + * for your pipeline, such as a collection, a collection group, the entire database, or a set of specific documents. + */ +export class PipelineSource { + /** + * @internal + * @private + * @param databaseId + * @param userDataReader + * @param _createPipeline + */ + constructor( + private databaseId: DatabaseId, + private userDataReader: UserDataReader, + /** + * @internal + * @private + */ + public _createPipeline: (stages: Stage[]) => PipelineType + ) {} + + /** + * @beta + * Returns all documents from the entire collection. The collection can be nested. + * @param collection - Name or reference to the collection that will be used as the Pipeline source. + */ + collection(collection: string | CollectionReference): PipelineType; + /** + * @beta + * Returns all documents from the entire collection. The collection can be nested. + * @param options - Options defining how this CollectionStage is evaluated. + */ + collection(options: CollectionStageOptions): PipelineType; + collection( + collectionOrOptions: string | CollectionReference | CollectionStageOptions + ): PipelineType { + // Process argument union(s) from method overloads + const options = + isString(collectionOrOptions) || + isCollectionReference(collectionOrOptions) + ? {} + : collectionOrOptions; + const collectionRefOrString = + isString(collectionOrOptions) || + isCollectionReference(collectionOrOptions) + ? collectionOrOptions + : collectionOrOptions.collection; + + // Validate that a user provided reference is for the same Firestore DB + if (isCollectionReference(collectionRefOrString)) { + this._validateReference(collectionRefOrString); + } + + // Convert user land convenience types to internal types + const normalizedCollection = isString(collectionRefOrString) + ? (collectionRefOrString as string) + : collectionRefOrString.path; + + // Create stage object + const stage = new CollectionSource(normalizedCollection, options); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'collection' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._createPipeline([stage]); + } + + /** + * @beta + * Returns all documents from a collection ID regardless of the parent. + * @param collectionId - ID of the collection group to use as the Pipeline source. + */ + collectionGroup(collectionId: string): PipelineType; + /** + * @beta + * Returns all documents from a collection ID regardless of the parent. + * @param options - Options defining how this CollectionGroupStage is evaluated. + */ + collectionGroup(options: CollectionGroupStageOptions): PipelineType; + collectionGroup( + collectionIdOrOptions: string | CollectionGroupStageOptions + ): PipelineType { + // Process argument union(s) from method overloads + let collectionId: string; + let options: {}; + if (isString(collectionIdOrOptions)) { + collectionId = collectionIdOrOptions; + options = {}; + } else { + ({ collectionId, ...options } = collectionIdOrOptions); + } + + // Create stage object + const stage = new CollectionGroupSource(collectionId, options); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'collectionGroup' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._createPipeline([stage]); + } + + /** + * @beta + * Returns all documents from the entire database. + */ + database(): PipelineType; + /** + * @beta + * Returns all documents from the entire database. + * @param options - Options defining how a DatabaseStage is evaluated. + */ + database(options: DatabaseStageOptions): PipelineType; + database(options?: DatabaseStageOptions): PipelineType { + // Process argument union(s) from method overloads + options = options ?? {}; + + // Create stage object + const stage = new DatabaseSource(options); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'database' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._createPipeline([stage]); + } + + /** + * @beta + * Set the pipeline's source to the documents specified by the given paths and DocumentReferences. + * + * @param docs An array of paths and DocumentReferences specifying the individual documents that will be the source of this pipeline. + * The converters for these DocumentReferences will be ignored and not have an effect on this pipeline. + * + * @throws {@FirestoreError} Thrown if any of the provided DocumentReferences target a different project or database than the pipeline. + */ + documents(docs: Array): PipelineType; + + /** + * @beta + * Set the pipeline's source to the documents specified by the given paths and DocumentReferences. + * + * @param options - Options defining how this DocumentsStage is evaluated. + * + * @throws {@FirestoreError} Thrown if any of the provided DocumentReferences target a different project or database than the pipeline. + */ + documents(options: DocumentsStageOptions): PipelineType; + documents( + docsOrOptions: Array | DocumentsStageOptions + ): PipelineType { + // Process argument union(s) from method overloads + let options: {}; + let docs: Array; + if (Array.isArray(docsOrOptions)) { + docs = docsOrOptions; + options = {}; + } else { + ({ docs, ...options } = docsOrOptions); + } + + // Validate that all user provided references are for the same Firestore DB + docs + .filter(v => v instanceof DocumentReference) + .forEach(dr => this._validateReference(dr as DocumentReference)); + + // Convert user land convenience types to internal types + const normalizedDocs: string[] = docs.map(doc => + isString(doc) ? doc : doc.path + ); + + // Create stage object + const stage = new DocumentsSource(normalizedDocs, options); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'documents' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._createPipeline([stage]); + } + + /** + * @beta + * Convert the given Query into an equivalent Pipeline. + * + * @param query A Query to be converted into a Pipeline. + * + * @throws {@FirestoreError} Thrown if any of the provided DocumentReferences target a different project or database than the pipeline. + */ + createFrom(query: Query): Pipeline { + return toPipeline(query._query, query.firestore); + } + + _validateReference(reference: CollectionReference | DocumentReference): void { + const refDbId = reference.firestore._databaseId; + if (!refDbId.isEqual(this.databaseId)) { + throw new FirestoreError( + Code.INVALID_ARGUMENT, + `Invalid ${ + reference instanceof CollectionReference + ? 'CollectionReference' + : 'DocumentReference' + }. ` + + `The project ID ("${refDbId.projectId}") or the database ("${refDbId.database}") does not match ` + + `the project ID ("${this.databaseId.projectId}") and database ("${this.databaseId.database}") of the target database of this Pipeline.` + ); + } + } +} diff --git a/packages/firestore/src/lite-api/pipeline.ts b/packages/firestore/src/lite-api/pipeline.ts new file mode 100644 index 0000000000..4352e30f4c --- /dev/null +++ b/packages/firestore/src/lite-api/pipeline.ts @@ -0,0 +1,1478 @@ +/** + * @license + * Copyright 2024 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 { + Pipeline as ProtoPipeline, + Stage as ProtoStage +} from '../protos/firestore_proto_api'; +import { JsonProtoSerializer, ProtoSerializable } from '../remote/serializer'; +import { isPlainObject } from '../util/input_validation'; +import { + aliasedAggregateToMap, + fieldOrExpression, + selectablesToMap, + vectorToExpr +} from '../util/pipeline_util'; +import { isNumber, isString } from '../util/types'; + +import { Firestore } from './database'; +import { + _mapValue, + AggregateFunction, + AliasedAggregate, + BooleanExpression, + _constant, + Expression, + Field, + field, + Ordering, + Selectable, + _field, + isSelectable, + isField, + isBooleanExpr, + isAliasedAggregate, + toField, + isOrdering, + isExpr +} from './expressions'; +import { + AddFields, + Aggregate, + Distinct, + FindNearest, + RawStage, + Limit, + Offset, + RemoveFields, + Replace, + Sample, + Select, + Sort, + Stage, + Union, + Unnest, + Where +} from './stage'; +import { + AddFieldsStageOptions, + AggregateStageOptions, + DistinctStageOptions, + FindNearestStageOptions, + LimitStageOptions, + OffsetStageOptions, + RemoveFieldsStageOptions, + ReplaceWithStageOptions, + SampleStageOptions, + SelectStageOptions, + SortStageOptions, + StageOptions, + UnionStageOptions, + UnnestStageOptions, + WhereStageOptions +} from './stage_options'; +import { UserDataReader, UserDataSource } from './user_data_reader'; +import { AbstractUserDataWriter } from './user_data_writer'; + +/** + * @beta + * + * The Pipeline class provides a flexible and expressive framework for building complex data + * transformation and query pipelines for Firestore. + * + * A pipeline takes data sources, such as Firestore collections or collection groups, and applies + * a series of stages that are chained together. Each stage takes the output from the previous stage + * (or the data source) and produces an output for the next stage (or as the final output of the + * pipeline). + * + * Expressions can be used within each stage to filter and transform data through the stage. + * + * NOTE: The chained stages do not prescribe exactly how Firestore will execute the pipeline. + * Instead, Firestore only guarantees that the result is the same as if the chained stages were + * executed in order. + * + * Usage Examples: + * + * ```typescript + * const db: Firestore; // Assumes a valid firestore instance. + * + * // Example 1: Select specific fields and rename 'rating' to 'bookRating' + * const results1 = await execute(db.pipeline() + * .collection("books") + * .select("title", "author", field("rating").as("bookRating"))); + * + * // Example 2: Filter documents where 'genre' is "Science Fiction" and 'published' is after 1950 + * const results2 = await execute(db.pipeline() + * .collection("books") + * .where(and(field("genre").eq("Science Fiction"), field("published").gt(1950)))); + * + * // Example 3: Calculate the average rating of books published after 1980 + * const results3 = await execute(db.pipeline() + * .collection("books") + * .where(field("published").gt(1980)) + * .aggregate(avg(field("rating")).as("averageRating"))); + * ``` + */ +export class Pipeline implements ProtoSerializable { + /** + * @internal + * @private + * @param _db + * @param userDataReader + * @param _userDataWriter + * @param stages + */ + constructor( + /** + * @internal + * @private + */ + public _db: Firestore, + /** + * @internal + * @private + */ + private userDataReader: UserDataReader, + /** + * @internal + * @private + */ + public _userDataWriter: AbstractUserDataWriter, + /** + * @internal + * @private + */ + private stages: Stage[] + ) {} + + /** + * @beta + * Adds new fields to outputs from previous stages. + * + * This stage allows you to compute values on-the-fly based on existing data from previous + * stages or constants. You can use this to create new fields or overwrite existing ones (if there + * is name overlaps). + * + * The added fields are defined using {@link Selectable}s, which can be: + * + * - {@link Field}: References an existing document field. + * - {@link Expression}: Either a literal value (see {@link Constant}) or a computed value + * (see {@FunctionExpr}) with an assigned alias using {@link Expression#as}. + * + * Example: + * + * ```typescript + * firestore.pipeline().collection("books") + * .addFields( + * field("rating").as("bookRating"), // Rename 'rating' to 'bookRating' + * add(5, field("quantity")).as("totalCost") // Calculate 'totalCost' + * ); + * ``` + * + * @param field The first field to add to the documents, specified as a {@link Selectable}. + * @param additionalFields Optional additional fields to add to the documents, specified as {@link Selectable}s. + * @return A new Pipeline object with this stage appended to the stage list. + */ + addFields(field: Selectable, ...additionalFields: Selectable[]): Pipeline; + /** + * @beta + * Adds new fields to outputs from previous stages. + * + * This stage allows you to compute values on-the-fly based on existing data from previous + * stages or constants. You can use this to create new fields or overwrite existing ones (if there + * is name overlaps). + * + * The added fields are defined using {@link Selectable}s, which can be: + * + * - {@link Field}: References an existing document field. + * - {@link Expression}: Either a literal value (see {@link Constant}) or a computed value + * (see {@FunctionExpr}) with an assigned alias using {@link Expression#as}. + * + * Example: + * + * ```typescript + * firestore.pipeline().collection("books") + * .addFields( + * field("rating").as("bookRating"), // Rename 'rating' to 'bookRating' + * add(5, field("quantity")).as("totalCost") // Calculate 'totalCost' + * ); + * ``` + * + * @param options - An object that specifies required and optional parameters for the stage. + * @return A new Pipeline object with this stage appended to the stage list. + */ + addFields(options: AddFieldsStageOptions): Pipeline; + addFields( + fieldOrOptions: Selectable | AddFieldsStageOptions, + ...additionalFields: Selectable[] + ): Pipeline { + // Process argument union(s) from method overloads + let fields: Selectable[]; + let options: {}; + if (isSelectable(fieldOrOptions)) { + fields = [fieldOrOptions, ...additionalFields]; + options = {}; + } else { + ({ fields, ...options } = fieldOrOptions); + } + + // Convert user land convenience types to internal types + const normalizedFields: Map = selectablesToMap(fields); + + // Create stage object + const stage = new AddFields(normalizedFields, options); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'addFields' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._addStage(stage); + } + + /** + * @beta + * Remove fields from outputs of previous stages. + * + * Example: + * + * ```typescript + * firestore.pipeline().collection('books') + * // removes field 'rating' and 'cost' from the previous stage outputs. + * .removeFields( + * field('rating'), + * 'cost' + * ); + * ``` + * + * @param fieldValue The first field to remove. + * @param additionalFields Optional additional fields to remove. + * @return A new Pipeline object with this stage appended to the stage list. + */ + removeFields( + fieldValue: Field | string, + ...additionalFields: Array + ): Pipeline; + /** + * @beta + * Remove fields from outputs of previous stages. + * + * Example: + * + * ```typescript + * firestore.pipeline().collection('books') + * // removes field 'rating' and 'cost' from the previous stage outputs. + * .removeFields( + * field('rating'), + * 'cost' + * ); + * ``` + * + * @param options - An object that specifies required and optional parameters for the stage. + * @return A new Pipeline object with this stage appended to the stage list. + */ + removeFields(options: RemoveFieldsStageOptions): Pipeline; + removeFields( + fieldValueOrOptions: Field | string | RemoveFieldsStageOptions, + ...additionalFields: Array + ): Pipeline { + // Process argument union(s) from method overloads + const options = + isField(fieldValueOrOptions) || isString(fieldValueOrOptions) + ? {} + : fieldValueOrOptions; + const fields: Array = + isField(fieldValueOrOptions) || isString(fieldValueOrOptions) + ? [fieldValueOrOptions, ...additionalFields] + : fieldValueOrOptions.fields; + + // Convert user land convenience types to internal types + const convertedFields: Field[] = fields.map(f => + isString(f) ? field(f) : (f as Field) + ); + + // Create stage object + const stage = new RemoveFields(convertedFields, options); + + // User data must be read in the context of the API method to + // provide contextual errors + stage._readUserData( + this.userDataReader.createContext(UserDataSource.Argument, 'removeFields') + ); + + // Add stage to the pipeline + return this._addStage(stage); + } + + /** + * @beta + * Selects or creates a set of fields from the outputs of previous stages. + * + *

The selected fields are defined using {@link Selectable} expressions, which can be: + * + *

    + *
  • {@code string}: Name of an existing field
  • + *
  • {@link Field}: References an existing field.
  • + *
  • {@link Function}: Represents the result of a function with an assigned alias name using + * {@link Expression#as}
  • + *
+ * + *

If no selections are provided, the output of this stage is empty. Use {@link + * Pipeline#addFields} instead if only additions are + * desired. + * + *

Example: + * + * ```typescript + * db.pipeline().collection("books") + * .select( + * "firstName", + * field("lastName"), + * field("address").toUppercase().as("upperAddress"), + * ); + * ``` + * + * @param selection The first field to include in the output documents, specified as {@link + * Selectable} expression or string value representing the field name. + * @param additionalSelections Optional additional fields to include in the output documents, specified as {@link + * Selectable} expressions or {@code string} values representing field names. + * @return A new Pipeline object with this stage appended to the stage list. + */ + select( + selection: Selectable | string, + ...additionalSelections: Array + ): Pipeline; + /** + * @beta + * Selects or creates a set of fields from the outputs of previous stages. + * + *

The selected fields are defined using {@link Selectable} expressions, which can be: + * + *

    + *
  • {@code string}: Name of an existing field
  • + *
  • {@link Field}: References an existing field.
  • + *
  • {@link Function}: Represents the result of a function with an assigned alias name using + * {@link Expression#as}
  • + *
+ * + *

If no selections are provided, the output of this stage is empty. Use {@link + * Pipeline#addFields} instead if only additions are + * desired. + * + *

Example: + * + * ```typescript + * db.pipeline().collection("books") + * .select( + * "firstName", + * field("lastName"), + * field("address").toUppercase().as("upperAddress"), + * ); + * ``` + * + * @param options - An object that specifies required and optional parameters for the stage. + * @return A new Pipeline object with this stage appended to the stage list. + */ + select(options: SelectStageOptions): Pipeline; + select( + selectionOrOptions: Selectable | string | SelectStageOptions, + ...additionalSelections: Array + ): Pipeline { + // Process argument union(s) from method overloads + const options = + isSelectable(selectionOrOptions) || isString(selectionOrOptions) + ? {} + : selectionOrOptions; + + const selections: Array = + isSelectable(selectionOrOptions) || isString(selectionOrOptions) + ? [selectionOrOptions, ...additionalSelections] + : selectionOrOptions.selections; + + // Convert user land convenience types to internal types + const normalizedSelections: Map = + selectablesToMap(selections); + + // Create stage object + const stage = new Select(normalizedSelections, options); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'select' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._addStage(stage); + } + + /** + * @beta + * Filters the documents from previous stages to only include those matching the specified {@link + * BooleanExpression}. + * + *

This stage allows you to apply conditions to the data, similar to a "WHERE" clause in SQL. + * You can filter documents based on their field values, using implementations of {@link + * BooleanExpression}, typically including but not limited to: + * + *

    + *
  • field comparators: {@link Function#eq}, {@link Function#lt} (less than), {@link + * Function#gt} (greater than), etc.
  • + *
  • logical operators: {@link Function#and}, {@link Function#or}, {@link Function#not}, etc.
  • + *
  • advanced functions: {@link Function#regexMatch}, {@link + * Function#arrayContains}, etc.
  • + *
+ * + *

Example: + * + * ```typescript + * firestore.pipeline().collection("books") + * .where( + * and( + * gt(field("rating"), 4.0), // Filter for ratings greater than 4.0 + * field("genre").eq("Science Fiction") // Equivalent to gt("genre", "Science Fiction") + * ) + * ); + * ``` + * + * @param condition The {@link BooleanExpression} to apply. + * @return A new Pipeline object with this stage appended to the stage list. + */ + where(condition: BooleanExpression): Pipeline; + /** + * @beta + * Filters the documents from previous stages to only include those matching the specified {@link + * BooleanExpression}. + * + *

This stage allows you to apply conditions to the data, similar to a "WHERE" clause in SQL. + * You can filter documents based on their field values, using implementations of {@link + * BooleanExpression}, typically including but not limited to: + * + *

    + *
  • field comparators: {@link Function#eq}, {@link Function#lt} (less than), {@link + * Function#gt} (greater than), etc.
  • + *
  • logical operators: {@link Function#and}, {@link Function#or}, {@link Function#not}, etc.
  • + *
  • advanced functions: {@link Function#regexMatch}, {@link + * Function#arrayContains}, etc.
  • + *
+ * + *

Example: + * + * ```typescript + * firestore.pipeline().collection("books") + * .where( + * and( + * gt(field("rating"), 4.0), // Filter for ratings greater than 4.0 + * field("genre").eq("Science Fiction") // Equivalent to gt("genre", "Science Fiction") + * ) + * ); + * ``` + * + * @param options - An object that specifies required and optional parameters for the stage. + * @return A new Pipeline object with this stage appended to the stage list. + */ + where(options: WhereStageOptions): Pipeline; + where(conditionOrOptions: BooleanExpression | WhereStageOptions): Pipeline { + // Process argument union(s) from method overloads + const options = isBooleanExpr(conditionOrOptions) ? {} : conditionOrOptions; + const condition: BooleanExpression = isBooleanExpr(conditionOrOptions) + ? conditionOrOptions + : conditionOrOptions.condition; + + // Create stage object + const stage = new Where(condition, options); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'where' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._addStage(stage); + } + + /** + * @beta + * Skips the first `offset` number of documents from the results of previous stages. + * + *

This stage is useful for implementing pagination in your pipelines, allowing you to retrieve + * results in chunks. It is typically used in conjunction with {@link #limit} to control the + * size of each page. + * + *

Example: + * + * ```typescript + * // Retrieve the second page of 20 results + * firestore.pipeline().collection('books') + * .sort(field('published').descending()) + * .offset(20) // Skip the first 20 results + * .limit(20); // Take the next 20 results + * ``` + * + * @param offset The number of documents to skip. + * @return A new Pipeline object with this stage appended to the stage list. + */ + offset(offset: number): Pipeline; + /** + * @beta + * Skips the first `offset` number of documents from the results of previous stages. + * + *

This stage is useful for implementing pagination in your pipelines, allowing you to retrieve + * results in chunks. It is typically used in conjunction with {@link #limit} to control the + * size of each page. + * + *

Example: + * + * ```typescript + * // Retrieve the second page of 20 results + * firestore.pipeline().collection('books') + * .sort(field('published').descending()) + * .offset(20) // Skip the first 20 results + * .limit(20); // Take the next 20 results + * ``` + * + * @param options - An object that specifies required and optional parameters for the stage. + * @return A new Pipeline object with this stage appended to the stage list. + */ + offset(options: OffsetStageOptions): Pipeline; + offset(offsetOrOptions: number | OffsetStageOptions): Pipeline { + // Process argument union(s) from method overloads + let options: {}; + let offset: number; + if (isNumber(offsetOrOptions)) { + options = {}; + offset = offsetOrOptions; + } else { + options = offsetOrOptions; + offset = offsetOrOptions.offset; + } + + // Create stage object + const stage = new Offset(offset, options); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'offset' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._addStage(stage); + } + + /** + * @beta + * Limits the maximum number of documents returned by previous stages to `limit`. + * + *

This stage is particularly useful when you want to retrieve a controlled subset of data from + * a potentially large result set. It's often used for: + * + *

    + *
  • **Pagination:** In combination with {@link #offset} to retrieve specific pages of + * results.
  • + *
  • **Limiting Data Retrieval:** To prevent excessive data transfer and improve performance, + * especially when dealing with large collections.
  • + *
+ * + *

Example: + * + * ```typescript + * // Limit the results to the top 10 highest-rated books + * firestore.pipeline().collection('books') + * .sort(field('rating').descending()) + * .limit(10); + * ``` + * + * @param limit The maximum number of documents to return. + * @return A new Pipeline object with this stage appended to the stage list. + */ + limit(limit: number): Pipeline; + /** + * @beta + * Limits the maximum number of documents returned by previous stages to `limit`. + * + *

This stage is particularly useful when you want to retrieve a controlled subset of data from + * a potentially large result set. It's often used for: + * + *

    + *
  • **Pagination:** In combination with {@link #offset} to retrieve specific pages of + * results.
  • + *
  • **Limiting Data Retrieval:** To prevent excessive data transfer and improve performance, + * especially when dealing with large collections.
  • + *
+ * + *

Example: + * + * ```typescript + * // Limit the results to the top 10 highest-rated books + * firestore.pipeline().collection('books') + * .sort(field('rating').descending()) + * .limit(10); + * ``` + * + * @param options - An object that specifies required and optional parameters for the stage. + * @return A new Pipeline object with this stage appended to the stage list. + */ + limit(options: LimitStageOptions): Pipeline; + limit(limitOrOptions: number | LimitStageOptions): Pipeline { + // Process argument union(s) from method overloads + const options = isNumber(limitOrOptions) ? {} : limitOrOptions; + const limit: number = isNumber(limitOrOptions) + ? limitOrOptions + : limitOrOptions.limit; + + // Create stage object + const stage = new Limit(limit, options); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'limit' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._addStage(stage); + } + + /** + * @beta + * Returns a set of distinct values from the inputs to this stage. + * + * This stage runs through the results from previous stages to include only results with + * unique combinations of {@link Expression} values ({@link Field}, {@link Function}, etc). + * + * The parameters to this stage are defined using {@link Selectable} expressions or strings: + * + * - {@code string}: Name of an existing field + * - {@link Field}: References an existing document field. + * - {@link AliasedExpr}: Represents the result of a function with an assigned alias name + * using {@link Expression#as}. + * + * Example: + * + * ```typescript + * // Get a list of unique author names in uppercase and genre combinations. + * firestore.pipeline().collection("books") + * .distinct(toUppercase(field("author")).as("authorName"), field("genre"), "publishedAt") + * .select("authorName"); + * ``` + * + * @param group The {@link Selectable} expression or field name to consider when determining + * distinct value combinations. + * @param additionalGroups Optional additional {@link Selectable} expressions to consider when determining distinct + * value combinations or strings representing field names. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + distinct( + group: string | Selectable, + ...additionalGroups: Array + ): Pipeline; + /** + * @beta + * Returns a set of distinct values from the inputs to this stage. + * + * This stage runs through the results from previous stages to include only results with + * unique combinations of {@link Expression} values ({@link Field}, {@link Function}, etc). + * + * The parameters to this stage are defined using {@link Selectable} expressions or strings: + * + * - {@code string}: Name of an existing field + * - {@link Field}: References an existing document field. + * - {@link AliasedExpr}: Represents the result of a function with an assigned alias name + * using {@link Expression#as}. + * + * Example: + * + * ```typescript + * // Get a list of unique author names in uppercase and genre combinations. + * firestore.pipeline().collection("books") + * .distinct(toUppercase(field("author")).as("authorName"), field("genre"), "publishedAt") + * .select("authorName"); + * ``` + * + * @param options - An object that specifies required and optional parameters for the stage. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + distinct(options: DistinctStageOptions): Pipeline; + distinct( + groupOrOptions: string | Selectable | DistinctStageOptions, + ...additionalGroups: Array + ): Pipeline { + // Process argument union(s) from method overloads + const options = + isString(groupOrOptions) || isSelectable(groupOrOptions) + ? {} + : groupOrOptions; + const groups: Array = + isString(groupOrOptions) || isSelectable(groupOrOptions) + ? [groupOrOptions, ...additionalGroups] + : groupOrOptions.groups; + + // Convert user land convenience types to internal types + const convertedGroups: Map = selectablesToMap(groups); + + // Create stage object + const stage = new Distinct(convertedGroups, options); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'distinct' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._addStage(stage); + } + + /** + * @beta + * Performs aggregation operations on the documents from previous stages. + * + *

This stage allows you to calculate aggregate values over a set of documents. You define the + * aggregations to perform using {@link AliasedAggregate} expressions which are typically results of + * calling {@link Expression#as} on {@link AggregateFunction} instances. + * + *

Example: + * + * ```typescript + * // Calculate the average rating and the total number of books + * firestore.pipeline().collection("books") + * .aggregate( + * field("rating").avg().as("averageRating"), + * countAll().as("totalBooks") + * ); + * ``` + * + * @param accumulator The first {@link AliasedAggregate}, wrapping an {@link AggregateFunction} + * and providing a name for the accumulated results. + * @param additionalAccumulators Optional additional {@link AliasedAggregate}, each wrapping an {@link AggregateFunction} + * and providing a name for the accumulated results. + * @return A new Pipeline object with this stage appended to the stage list. + */ + aggregate( + accumulator: AliasedAggregate, + ...additionalAccumulators: AliasedAggregate[] + ): Pipeline; + /** + * @beta + * Performs optionally grouped aggregation operations on the documents from previous stages. + * + *

This stage allows you to calculate aggregate values over a set of documents, optionally + * grouped by one or more fields or functions. You can specify: + * + *

    + *
  • **Grouping Fields or Functions:** One or more fields or functions to group the documents + * by. For each distinct combination of values in these fields, a separate group is created. + * If no grouping fields are provided, a single group containing all documents is used. Not + * specifying groups is the same as putting the entire inputs into one group.
  • + *
  • **Accumulators:** One or more accumulation operations to perform within each group. These + * are defined using {@link AliasedAggregate} expressions, which are typically created by + * calling {@link Expression#as} on {@link AggregateFunction} instances. Each aggregation + * calculates a value (e.g., sum, average, count) based on the documents within its group.
  • + *
+ * + *

Example: + * + * ```typescript + * // Calculate the average rating for each genre. + * firestore.pipeline().collection("books") + * .aggregate({ + * accumulators: [avg(field("rating")).as("avg_rating")] + * groups: ["genre"] + * }); + * ``` + * + * @param options - An object that specifies required and optional parameters for the stage. + * @return A new {@code Pipeline} object with this stage appended to the stage + * list. + */ + aggregate(options: AggregateStageOptions): Pipeline; + aggregate( + targetOrOptions: AliasedAggregate | AggregateStageOptions, + ...rest: AliasedAggregate[] + ): Pipeline { + // Process argument union(s) from method overloads + const options = isAliasedAggregate(targetOrOptions) ? {} : targetOrOptions; + const accumulators: AliasedAggregate[] = isAliasedAggregate(targetOrOptions) + ? [targetOrOptions, ...rest] + : targetOrOptions.accumulators; + const groups: Array = isAliasedAggregate( + targetOrOptions + ) + ? [] + : targetOrOptions.groups ?? []; + + // Convert user land convenience types to internal types + const convertedAccumulators: Map = + aliasedAggregateToMap(accumulators); + const convertedGroups: Map = selectablesToMap(groups); + + // Create stage object + const stage = new Aggregate( + convertedGroups, + convertedAccumulators, + options + ); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'aggregate' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._addStage(stage); + } + + /** + * @beta + * Performs a vector proximity search on the documents from the previous stage, returning the + * K-nearest documents based on the specified query `vectorValue` and `distanceMeasure`. The + * returned documents will be sorted in order from nearest to furthest from the query `vectorValue`. + * + *

Example: + * + * ```typescript + * // Find the 10 most similar books based on the book description. + * const bookDescription = "Lorem ipsum..."; + * const queryVector: number[] = ...; // compute embedding of `bookDescription` + * + * firestore.pipeline().collection("books") + * .findNearest({ + * field: 'embedding', + * vectorValue: queryVector, + * distanceMeasure: 'euclidean', + * limit: 10, // optional + * distanceField: 'computedDistance' // optional + * }); + * ``` + * + * @param options - An object that specifies required and optional parameters for the stage. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + findNearest(options: FindNearestStageOptions): Pipeline { + // Convert user land convenience types to internal types + const field = toField(options.field); + const vectorValue = vectorToExpr(options.vectorValue); + const distanceField = options.distanceField + ? toField(options.distanceField) + : undefined; + const internalOptions = { + distanceField, + limit: options.limit, + rawOptions: options.rawOptions + }; + + // Create stage object + const stage = new FindNearest( + vectorValue, + field, + options.distanceMeasure, + internalOptions + ); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'addFields' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._addStage(stage); + } + + /** + * @beta + * Sorts the documents from previous stages based on one or more {@link Ordering} criteria. + * + *

This stage allows you to order the results of your pipeline. You can specify multiple {@link + * Ordering} instances to sort by multiple fields in ascending or descending order. If documents + * have the same value for a field used for sorting, the next specified ordering will be used. If + * all orderings result in equal comparison, the documents are considered equal and the order is + * unspecified. + * + *

Example: + * + * ```typescript + * // Sort books by rating in descending order, and then by title in ascending order for books + * // with the same rating + * firestore.pipeline().collection("books") + * .sort( + * Ordering.of(field("rating")).descending(), + * Ordering.of(field("title")) // Ascending order is the default + * ); + * ``` + * + * @param ordering The first {@link Ordering} instance specifying the sorting criteria. + * @param additionalOrderings Optional additional {@link Ordering} instances specifying the additional sorting criteria. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + sort(ordering: Ordering, ...additionalOrderings: Ordering[]): Pipeline; + /** + * @beta + * Sorts the documents from previous stages based on one or more {@link Ordering} criteria. + * + *

This stage allows you to order the results of your pipeline. You can specify multiple {@link + * Ordering} instances to sort by multiple fields in ascending or descending order. If documents + * have the same value for a field used for sorting, the next specified ordering will be used. If + * all orderings result in equal comparison, the documents are considered equal and the order is + * unspecified. + * + *

Example: + * + * ```typescript + * // Sort books by rating in descending order, and then by title in ascending order for books + * // with the same rating + * firestore.pipeline().collection("books") + * .sort( + * Ordering.of(field("rating")).descending(), + * Ordering.of(field("title")) // Ascending order is the default + * ); + * ``` + * + * @param options - An object that specifies required and optional parameters for the stage. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + sort(options: SortStageOptions): Pipeline; + sort( + orderingOrOptions: Ordering | SortStageOptions, + ...additionalOrderings: Ordering[] + ): Pipeline { + // Process argument union(s) from method overloads + const options = isOrdering(orderingOrOptions) ? {} : orderingOrOptions; + const orderings: Ordering[] = isOrdering(orderingOrOptions) + ? [orderingOrOptions, ...additionalOrderings] + : orderingOrOptions.orderings; + + // Create stage object + const stage = new Sort(orderings, options); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'sort' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._addStage(stage); + } + + /** + * @beta + * Fully overwrites all fields in a document with those coming from a nested map. + * + *

This stage allows you to emit a map value as a document. Each key of the map becomes a field + * on the document that contains the corresponding value. + * + *

Example: + * + * ```typescript + * // Input. + * // { + * // 'name': 'John Doe Jr.', + * // 'parents': { + * // 'father': 'John Doe Sr.', + * // 'mother': 'Jane Doe' + * // } + * // } + * + * // Emit parents as document. + * firestore.pipeline().collection('people').replaceWith('parents'); + * + * // Output + * // { + * // 'father': 'John Doe Sr.', + * // 'mother': 'Jane Doe' + * // } + * ``` + * + * @param fieldName The {@link Field} field containing the nested map. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + replaceWith(fieldName: string): Pipeline; + /** + * @beta + * Fully overwrites all fields in a document with those coming from a map. + * + *

This stage allows you to emit a map value as a document. Each key of the map becomes a field + * on the document that contains the corresponding value. + * + *

Example: + * + * ```typescript + * // Input. + * // { + * // 'name': 'John Doe Jr.', + * // 'parents': { + * // 'father': 'John Doe Sr.', + * // 'mother': 'Jane Doe' + * // } + * // } + * + * // Emit parents as document. + * firestore.pipeline().collection('people').replaceWith(map({ + * foo: 'bar', + * info: { + * name: field('name') + * } + * })); + * + * // Output + * // { + * // 'father': 'John Doe Sr.', + * // 'mother': 'Jane Doe' + * // } + * ``` + * + * @param expr An {@link Expression} that when returned evaluates to a map. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + replaceWith(expr: Expression): Pipeline; + /** + * @beta + * Fully overwrites all fields in a document with those coming from a map. + * + *

This stage allows you to emit a map value as a document. Each key of the map becomes a field + * on the document that contains the corresponding value. + * + *

Example: + * + * ```typescript + * // Input. + * // { + * // 'name': 'John Doe Jr.', + * // 'parents': { + * // 'father': 'John Doe Sr.', + * // 'mother': 'Jane Doe' + * // } + * // } + * + * // Emit parents as document. + * firestore.pipeline().collection('people').replaceWith(map({ + * foo: 'bar', + * info: { + * name: field('name') + * } + * })); + * + * // Output + * // { + * // 'father': 'John Doe Sr.', + * // 'mother': 'Jane Doe' + * // } + * ``` + * + * @param options - An object that specifies required and optional parameters for the stage. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + replaceWith(options: ReplaceWithStageOptions): Pipeline; + replaceWith( + valueOrOptions: Expression | string | ReplaceWithStageOptions + ): Pipeline { + // Process argument union(s) from method overloads + const options = + isString(valueOrOptions) || isExpr(valueOrOptions) ? {} : valueOrOptions; + const fieldNameOrExpr: string | Expression = + isString(valueOrOptions) || isExpr(valueOrOptions) + ? valueOrOptions + : valueOrOptions.map; + + // Convert user land convenience types to internal types + const mapExpr = fieldOrExpression(fieldNameOrExpr); + + // Create stage object + const stage = new Replace(mapExpr, options); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'replaceWith' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._addStage(stage); + } + + /** + * @beta + * Performs a pseudo-random sampling of the documents from the previous stage. + * + *

This stage will filter documents pseudo-randomly. The parameter specifies how number of + * documents to be returned. + * + *

Examples: + * + * ```typescript + * // Sample 25 books, if available. + * firestore.pipeline().collection('books') + * .sample(25); + * ``` + * + * @param documents The number of documents to sample. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + sample(documents: number): Pipeline; + + /** + * @beta + * Performs a pseudo-random sampling of the documents from the previous stage. + * + *

This stage will filter documents pseudo-randomly. The 'options' parameter specifies how + * sampling will be performed. See {@code SampleOptions} for more information. + * + *

Examples: + * + * // Sample 10 books, if available. + * firestore.pipeline().collection("books") + * .sample({ documents: 10 }); + * + * // Sample 50% of books. + * firestore.pipeline().collection("books") + * .sample({ percentage: 0.5 }); + * + * @param options - An object that specifies required and optional parameters for the stage. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + sample(options: SampleStageOptions): Pipeline; + sample(documentsOrOptions: number | SampleStageOptions): Pipeline { + // Process argument union(s) from method overloads + const options = isNumber(documentsOrOptions) ? {} : documentsOrOptions; + let rate: number; + let mode: 'documents' | 'percent'; + if (isNumber(documentsOrOptions)) { + rate = documentsOrOptions; + mode = 'documents'; + } else if (isNumber(documentsOrOptions.documents)) { + rate = documentsOrOptions.documents; + mode = 'documents'; + } else { + rate = documentsOrOptions.percentage!; + mode = 'percent'; + } + + // Create stage object + const stage = new Sample(rate, mode, options); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'sample' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._addStage(stage); + } + + /** + * @beta + * Performs union of all documents from two pipelines, including duplicates. + * + *

This stage will pass through documents from previous stage, and also pass through documents + * from previous stage of the `other` {@code Pipeline} given in parameter. The order of documents + * emitted from this stage is undefined. + * + *

Example: + * + * ```typescript + * // Emit documents from books collection and magazines collection. + * firestore.pipeline().collection('books') + * .union(firestore.pipeline().collection('magazines')); + * ``` + * + * @param other The other {@code Pipeline} that is part of union. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + union(other: Pipeline): Pipeline; + /** + * @beta + * Performs union of all documents from two pipelines, including duplicates. + * + *

This stage will pass through documents from previous stage, and also pass through documents + * from previous stage of the `other` {@code Pipeline} given in parameter. The order of documents + * emitted from this stage is undefined. + * + *

Example: + * + * ```typescript + * // Emit documents from books collection and magazines collection. + * firestore.pipeline().collection('books') + * .union(firestore.pipeline().collection('magazines')); + * ``` + * + * @param options - An object that specifies required and optional parameters for the stage. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + union(options: UnionStageOptions): Pipeline; + union(otherOrOptions: Pipeline | UnionStageOptions): Pipeline { + // Process argument union(s) from method overloads + let options: {}; + let otherPipeline: Pipeline; + if (isPipeline(otherOrOptions)) { + options = {}; + otherPipeline = otherOrOptions; + } else { + ({ other: otherPipeline, ...options } = otherOrOptions); + } + + // Create stage object + const stage = new Union(otherPipeline, options); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'union' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._addStage(stage); + } + + /** + * @beta + * Produces a document for each element in an input array. + * + * For each previous stage document, this stage will emit zero or more augmented documents. The + * input array specified by the `selectable` parameter, will emit an augmented document for each input array element. The input array element will + * augment the previous stage document by setting the `alias` field with the array element value. + * + * When `selectable` evaluates to a non-array value (ex: number, null, absent), then the stage becomes a no-op for + * the current input document, returning it as is with the `alias` field absent. + * + * No documents are emitted when `selectable` evaluates to an empty array. + * + * Example: + * + * ```typescript + * // Input: + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tags": [ "comedy", "space", "adventure" ], ... } + * + * // Emit a book document for each tag of the book. + * firestore.pipeline().collection("books") + * .unnest(field("tags").as('tag'), 'tagIndex'); + * + * // Output: + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tag": "comedy", "tagIndex": 0, ... } + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tag": "space", "tagIndex": 1, ... } + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tag": "adventure", "tagIndex": 2, ... } + * ``` + * + * @param selectable A selectable expression defining the field to unnest and the alias to use for each un-nested element in the output documents. + * @param indexField An optional string value specifying the field path to write the offset (starting at zero) into the array the un-nested element is from + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + unnest(selectable: Selectable, indexField?: string): Pipeline; + /** + * @beta + * Produces a document for each element in an input array. + * + * For each previous stage document, this stage will emit zero or more augmented documents. The + * input array specified by the `selectable` parameter, will emit an augmented document for each input array element. The input array element will + * augment the previous stage document by setting the `alias` field with the array element value. + * + * When `selectable` evaluates to a non-array value (ex: number, null, absent), then the stage becomes a no-op for + * the current input document, returning it as is with the `alias` field absent. + * + * No documents are emitted when `selectable` evaluates to an empty array. + * + * Example: + * + * ```typescript + * // Input: + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tags": [ "comedy", "space", "adventure" ], ... } + * + * // Emit a book document for each tag of the book. + * firestore.pipeline().collection("books") + * .unnest(field("tags").as('tag'), 'tagIndex'); + * + * // Output: + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tag": "comedy", "tagIndex": 0, ... } + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tag": "space", "tagIndex": 1, ... } + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tag": "adventure", "tagIndex": 2, ... } + * ``` + * + * @param options - An object that specifies required and optional parameters for the stage. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + unnest(options: UnnestStageOptions): Pipeline; + unnest( + selectableOrOptions: Selectable | UnnestStageOptions, + indexField?: string + ): Pipeline { + // Process argument union(s) from method overloads + let options: { indexField?: Field } & StageOptions; + let selectable: Selectable; + let indexFieldName: string | undefined; + if (isSelectable(selectableOrOptions)) { + options = {}; + selectable = selectableOrOptions; + indexFieldName = indexField; + } else { + ({ + selectable, + indexField: indexFieldName, + ...options + } = selectableOrOptions); + } + + // Convert user land convenience types to internal types + const alias = selectable.alias; + const expr = selectable.expr as Expression; + if (isString(indexFieldName)) { + options.indexField = _field(indexFieldName, 'unnest'); + } + + // Create stage object + const stage = new Unnest(alias, expr, options); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'unnest' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._addStage(stage); + } + + /** + * @beta + * Adds a raw stage to the pipeline. + * + *

This method provides a flexible way to extend the pipeline's functionality by adding custom + * stages. Each raw stage is defined by a unique `name` and a set of `params` that control its + * behavior. + * + *

Example (Assuming there is no 'where' stage available in SDK): + * + * ```typescript + * // Assume we don't have a built-in 'where' stage + * firestore.pipeline().collection('books') + * .rawStage('where', [field('published').lt(1900)]) // Custom 'where' stage + * .select('title', 'author'); + * ``` + * + * @param name - The unique name of the raw stage to add. + * @param params - A list of parameters to configure the raw stage's behavior. + * @param options - An object of key value pairs that specifies optional parameters for the stage. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + rawStage( + name: string, + params: unknown[], + options?: { [key: string]: Expression | unknown } + ): Pipeline { + // Convert user land convenience types to internal types + const expressionParams = params.map((value: unknown) => { + if (value instanceof Expression) { + return value; + } else if (value instanceof AggregateFunction) { + return value; + } else if (isPlainObject(value)) { + return _mapValue(value as Record); + } else { + return _constant(value, 'rawStage'); + } + }); + + // Create stage object + const stage = new RawStage(name, expressionParams, options ?? {}); + + // User data must be read in the context of the API method to + // provide contextual errors + const parseContext = this.userDataReader.createContext( + UserDataSource.Argument, + 'rawStage' + ); + stage._readUserData(parseContext); + + // Add stage to the pipeline + return this._addStage(stage); + } + + /** + * @internal + * @private + */ + _toProto(jsonProtoSerializer: JsonProtoSerializer): ProtoPipeline { + const stages: ProtoStage[] = this.stages.map(stage => + stage._toProto(jsonProtoSerializer) + ); + return { stages }; + } + + private _addStage(stage: Stage): Pipeline { + const copy = this.stages.map(s => s); + copy.push(stage); + return this.newPipeline( + this._db, + this.userDataReader, + this._userDataWriter, + copy + ); + } + + /** + * @internal + * @private + * @param db + * @param userDataReader + * @param userDataWriter + * @param stages + * @protected + */ + protected newPipeline( + db: Firestore, + userDataReader: UserDataReader, + userDataWriter: AbstractUserDataWriter, + stages: Stage[] + ): Pipeline { + return new Pipeline(db, userDataReader, userDataWriter, stages); + } +} + +export function isPipeline(val: unknown): val is Pipeline { + return val instanceof Pipeline; +} diff --git a/packages/firestore/src/lite-api/pipeline_impl.ts b/packages/firestore/src/lite-api/pipeline_impl.ts new file mode 100644 index 0000000000..dbfe96a099 --- /dev/null +++ b/packages/firestore/src/lite-api/pipeline_impl.ts @@ -0,0 +1,152 @@ +/** + * @license + * Copyright 2024 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 { + StructuredPipeline, + StructuredPipelineOptions +} from '../core/structured_pipeline'; +import { invokeExecutePipeline } from '../remote/datastore'; + +import { getDatastore } from './components'; +import { Firestore } from './database'; +import { Pipeline } from './pipeline'; +import { PipelineResult, PipelineSnapshot } from './pipeline-result'; +import { PipelineSource } from './pipeline-source'; +import { DocumentReference } from './reference'; +import { LiteUserDataWriter } from './reference_impl'; +import { Stage } from './stage'; +import { + newUserDataReader, + UserDataReader, + UserDataSource +} from './user_data_reader'; + +declare module './database' { + interface Firestore { + /** + * @beta + * Creates and returns a new PipelineSource, which allows specifying the source stage of a {@link Pipeline}. + * + * @example + * ``` + * let myPipeline: Pipeline = firestore.pipeline().collection('books'); + * ``` + */ + pipeline(): PipelineSource; + } +} + +/** + * @beta + * Executes this pipeline and returns a Promise to represent the asynchronous operation. + * + * The returned Promise can be used to track the progress of the pipeline execution + * and retrieve the results (or handle any errors) asynchronously. + * + * The pipeline results are returned as a {@link PipelineSnapshot} that contains + * a list of {@link PipelineResult} objects. Each {@link PipelineResult} typically + * represents a single key/value map that has passed through all the + * stages of the pipeline, however this might differ depending on the stages involved in the + * pipeline. For example: + * + *

    + *
  • If there are no stages or only transformation stages, each {@link PipelineResult} + * represents a single document.
  • + *
  • If there is an aggregation, only a single {@link PipelineResult} is returned, + * representing the aggregated results over the entire dataset .
  • + *
  • If there is an aggregation stage with grouping, each {@link PipelineResult} represents a + * distinct group and its associated aggregated values.
  • + *
+ * + *

Example: + * + * ```typescript + * const snapshot: PipelineSnapshot = await execute(firestore.pipeline().collection("books") + * .where(gt(field("rating"), 4.5)) + * .select("title", "author", "rating")); + * + * const results: PipelineResults = snapshot.results; + * ``` + * + * @param pipeline The pipeline to execute. + * @return A Promise representing the asynchronous pipeline execution. + */ +export function execute(pipeline: Pipeline): Promise { + const datastore = getDatastore(pipeline._db); + + const udr = new UserDataReader( + pipeline._db._databaseId, + /* ignoreUndefinedProperties */ true + ); + const context = udr.createContext(UserDataSource.Argument, 'execute'); + + const structuredPipelineOptions = new StructuredPipelineOptions({}, {}); + structuredPipelineOptions._readUserData(context); + + const structuredPipeline: StructuredPipeline = new StructuredPipeline( + pipeline, + structuredPipelineOptions + ); + + return invokeExecutePipeline(datastore, structuredPipeline).then(result => { + // Get the execution time from the first result. + // firestoreClientExecutePipeline returns at least one PipelineStreamElement + // even if the returned document set is empty. + const executionTime = + result.length > 0 ? result[0].executionTime?.toTimestamp() : undefined; + + const docs = result + // Currently ignore any response from ExecutePipeline that does + // not contain any document data in the `fields` property. + .filter(element => !!element.fields) + .map( + element => + new PipelineResult( + pipeline._userDataWriter, + element.fields!, + element.key?.path + ? new DocumentReference(pipeline._db, null, element.key) + : undefined, + element.createTime?.toTimestamp(), + element.updateTime?.toTimestamp() + ) + ); + + return new PipelineSnapshot(pipeline, docs, executionTime); + }); +} + +/** + * @beta + * Creates and returns a new PipelineSource, which allows specifying the source stage of a {@link Pipeline}. + * + * @example + * ``` + * let myPipeline: Pipeline = firestore.pipeline().collection('books'); + * ``` + */ +Firestore.prototype.pipeline = function (): PipelineSource { + const userDataWriter = new LiteUserDataWriter(this); + const userDataReader = newUserDataReader(this); + return new PipelineSource( + this._databaseId, + userDataReader, + (stages: Stage[]) => { + return new Pipeline(this, userDataReader, userDataWriter, stages); + } + ); +}; diff --git a/packages/firestore/src/lite-api/pipeline_options.ts b/packages/firestore/src/lite-api/pipeline_options.ts new file mode 100644 index 0000000000..c46f42c856 --- /dev/null +++ b/packages/firestore/src/lite-api/pipeline_options.ts @@ -0,0 +1,83 @@ +/** + * @beta + * @license + * Copyright 2025 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 type { Pipeline } from './pipeline'; + +/** + * @beta + * Options defining Pipeline execution. + */ +export interface PipelineExecuteOptions { + /** + * @beta + * Pipeline to be evaluated. + */ + pipeline: Pipeline; + + /** + * @beta + * Specify the index mode. + */ + indexMode?: 'recommended'; + + /** + * @beta + * An escape hatch to set options not known at SDK build time. These values + * will be passed directly to the Firestore backend and not used by the SDK. + * + * The option name will be used as provided. And must match the name + * format used by the backend (hint: use a snake_case_name). + * + * Custom option values can be any type supported + * by Firestore (for example: string, boolean, number, map, …). Value types + * not known to the SDK will be rejected. + * + * Values specified in rawOptions will take precedence over any options + * with the same name set by the SDK. + * + * Override the `example_option`: + * ``` + * execute({ + * pipeline: myPipeline, + * rawOptions: { + * // Override `example_option`. This will not + * // merge with the existing `example_option` object. + * "example_option": { + * foo: "bar" + * } + * } + * } + * ``` + * + * `rawOptions` supports dot notation, if you want to override + * a nested option. + * ``` + * execute({ + * pipeline: myPipeline, + * rawOptions: { + * // Override `example_option.foo` and do not override + * // any other properties of `example_option`. + * "example_option.foo": "bar" + * } + * } + * ``` + */ + rawOptions?: { + [name: string]: unknown; + }; +} diff --git a/packages/firestore/src/lite-api/query.ts b/packages/firestore/src/lite-api/query.ts index f0a357b828..f019f0d093 100644 --- a/packages/firestore/src/lite-api/query.ts +++ b/packages/firestore/src/lite-api/query.ts @@ -52,8 +52,9 @@ import { import { FieldPath } from './field_path'; import { DocumentData, DocumentReference, Query } from './reference'; -import { DocumentSnapshot, fieldPathFromArgument } from './snapshot'; +import { DocumentSnapshot } from './snapshot'; import { + fieldPathFromArgument, newUserDataReader, parseQueryValue, UserDataReader diff --git a/packages/firestore/src/lite-api/reference.ts b/packages/firestore/src/lite-api/reference.ts index e6c5fd7b05..43eedecf8b 100644 --- a/packages/firestore/src/lite-api/reference.ts +++ b/packages/firestore/src/lite-api/reference.ts @@ -440,6 +440,12 @@ export class CollectionReference< } } +export function isCollectionReference( + val: unknown +): val is CollectionReference { + return val instanceof CollectionReference; +} + /** * Gets a `CollectionReference` instance that refers to the collection at * the specified absolute path. diff --git a/packages/firestore/src/lite-api/snapshot.ts b/packages/firestore/src/lite-api/snapshot.ts index 3024e2e9db..99474bda5f 100644 --- a/packages/firestore/src/lite-api/snapshot.ts +++ b/packages/firestore/src/lite-api/snapshot.ts @@ -15,11 +15,11 @@ * limitations under the License. */ -import { Compat, getModularInstance } from '@firebase/util'; +import { getModularInstance } from '@firebase/util'; import { Document } from '../model/document'; import { DocumentKey } from '../model/document_key'; -import { FieldPath as InternalFieldPath } from '../model/path'; +import { firestoreV1ApiClientInterfaces } from '../protos/firestore_proto_api'; import { arrayEquals } from '../util/misc'; import { Firestore } from './database'; @@ -34,7 +34,7 @@ import { WithFieldValue } from './reference'; import { - fieldPathFromDotSeparatedString, + fieldPathFromArgument, UntypedFirestoreDataConverter } from './user_data_reader'; import { AbstractUserDataWriter } from './user_data_writer'; @@ -366,6 +366,23 @@ export class DocumentSnapshot< } } + /** + * @internal + * @private + * + * Retrieves all fields in the document as a proto Value. Returns `undefined` if + * the document doesn't exist. + * + * @returns An `Object` containing all fields in the document or `undefined` + * if the document doesn't exist. + */ + _fieldsProto(): + | { [key: string]: firestoreV1ApiClientInterfaces.Value } + | undefined { + // Return a cloned value to prevent manipulation of the Snapshot's data + return this._document?.data.clone().value.mapValue.fields ?? undefined; + } + /** * Retrieves the field specified by `fieldPath`. Returns `undefined` if the * document or field doesn't exist. @@ -509,19 +526,3 @@ export function snapshotEqual( return false; } - -/** - * Helper that calls `fromDotSeparatedString()` but wraps any error thrown. - */ -export function fieldPathFromArgument( - methodName: string, - arg: string | FieldPath | Compat -): InternalFieldPath { - if (typeof arg === 'string') { - return fieldPathFromDotSeparatedString(methodName, arg); - } else if (arg instanceof FieldPath) { - return arg._internalPath; - } else { - return arg._delegate._internalPath; - } -} diff --git a/packages/firestore/src/lite-api/stage.ts b/packages/firestore/src/lite-api/stage.ts new file mode 100644 index 0000000000..5dd30eedba --- /dev/null +++ b/packages/firestore/src/lite-api/stage.ts @@ -0,0 +1,764 @@ +/** + * @license + * Copyright 2024 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 { ParseContext } from '../api/parse_context'; +import { OptionsUtil } from '../core/options_util'; +import { + ApiClientObjectMap, + firestoreV1ApiClientInterfaces, + Stage as ProtoStage +} from '../protos/firestore_proto_api'; +import { toNumber } from '../remote/number_serializer'; +import { + JsonProtoSerializer, + ProtoSerializable, + toMapValue, + toPipelineValue, + toStringValue +} from '../remote/serializer'; +import { hardAssert } from '../util/assert'; + +import { + AggregateFunction, + BooleanExpression, + Expression, + Field, + field, + Ordering +} from './expressions'; +import { Pipeline } from './pipeline'; +import { StageOptions } from './stage_options'; +import { isUserData, UserData } from './user_data_reader'; + +/** + * @beta + */ +export abstract class Stage implements ProtoSerializable, UserData { + /** + * Store optionsProto parsed by _readUserData. + * @private + * @internal + * @protected + */ + protected optionsProto: + | ApiClientObjectMap + | undefined = undefined; + protected knownOptions: Record; + protected rawOptions?: Record; + + constructor(options: StageOptions) { + ({ rawOptions: this.rawOptions, ...this.knownOptions } = options); + } + + _readUserData(context: ParseContext): void { + this.optionsProto = this._optionsUtil.getOptionsProto( + context, + this.knownOptions, + this.rawOptions + ); + } + + _toProto(_: JsonProtoSerializer): ProtoStage { + return { + name: this._name, + options: this.optionsProto + }; + } + + abstract get _optionsUtil(): OptionsUtil; + abstract get _name(): string; +} + +/** + * @beta + */ +export class AddFields extends Stage { + get _name(): string { + return 'add_fields'; + } + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({}); + } + + constructor(private fields: Map, options: StageOptions) { + super(options); + } + + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: [toMapValue(serializer, this.fields)] + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + readUserDataHelper(this.fields, context); + } +} + +/** + * @beta + */ +export class RemoveFields extends Stage { + get _name(): string { + return 'remove_fields'; + } + + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({}); + } + + constructor(private fields: Field[], options: StageOptions) { + super(options); + } + + /** + * @internal + * @private + */ + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: this.fields.map(f => f._toProto(serializer)) + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + readUserDataHelper(this.fields, context); + } +} + +/** + * @beta + */ +export class Aggregate extends Stage { + get _name(): string { + return 'aggregate'; + } + + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({}); + } + + constructor( + private groups: Map, + private accumulators: Map, + options: StageOptions + ) { + super(options); + } + + /** + * @internal + * @private + */ + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: [ + toMapValue(serializer, this.accumulators), + toMapValue(serializer, this.groups) + ] + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + readUserDataHelper(this.groups, context); + readUserDataHelper(this.accumulators, context); + } +} + +/** + * @beta + */ +export class Distinct extends Stage { + get _name(): string { + return 'distinct'; + } + + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({}); + } + + constructor(private groups: Map, options: StageOptions) { + super(options); + } + + /** + * @internal + * @private + */ + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: [toMapValue(serializer, this.groups)] + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + readUserDataHelper(this.groups, context); + } +} + +/** + * @beta + */ +export class CollectionSource extends Stage { + get _name(): string { + return 'collection'; + } + + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({ + forceIndex: { + serverName: 'force_index' + } + }); + } + + private formattedCollectionPath: string; + + constructor(collection: string, options: StageOptions) { + super(options); + + // prepend slash to collection string + this.formattedCollectionPath = collection.startsWith('/') + ? collection + : '/' + collection; + } + + /** + * @internal + * @private + */ + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: [{ referenceValue: this.formattedCollectionPath }] + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + } +} + +/** + * @beta + */ +export class CollectionGroupSource extends Stage { + get _name(): string { + return 'collection_group'; + } + + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({ + forceIndex: { + serverName: 'force_index' + } + }); + } + + constructor(private collectionId: string, options: StageOptions) { + super(options); + } + + /** + * @internal + * @private + */ + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: [{ referenceValue: '' }, { stringValue: this.collectionId }] + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + } +} + +/** + * @beta + */ +export class DatabaseSource extends Stage { + get _name(): string { + return 'database'; + } + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({}); + } + + /** + * @internal + * @private + */ + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer) + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + } +} + +/** + * @beta + */ +export class DocumentsSource extends Stage { + get _name(): string { + return 'documents'; + } + + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({}); + } + + private formattedPaths: string[]; + + constructor(docPaths: string[], options: StageOptions) { + super(options); + this.formattedPaths = docPaths.map(path => + path.startsWith('/') ? path : '/' + path + ); + } + + /** + * @internal + * @private + */ + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: this.formattedPaths.map(p => { + return { referenceValue: p }; + }) + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + } +} + +/** + * @beta + */ +export class Where extends Stage { + get _name(): string { + return 'where'; + } + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({}); + } + + constructor(private condition: BooleanExpression, options: StageOptions) { + super(options); + } + + /** + * @internal + * @private + */ + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: [this.condition._toProto(serializer)] + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + readUserDataHelper(this.condition, context); + } +} + +/** + * @beta + */ +export class FindNearest extends Stage { + get _name(): string { + return 'find_nearest'; + } + + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({ + limit: { + serverName: 'limit' + }, + distanceField: { + serverName: 'distance_field' + } + }); + } + + constructor( + private vectorValue: Expression, + private field: Field, + private distanceMeasure: 'euclidean' | 'cosine' | 'dot_product', + options: StageOptions + ) { + super(options); + } + + /** + * @private + * @internal + */ + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: [ + this.field._toProto(serializer), + this.vectorValue._toProto(serializer), + toStringValue(this.distanceMeasure) + ] + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + readUserDataHelper(this.vectorValue, context); + readUserDataHelper(this.field, context); + } +} + +/** + * @beta + */ +export class Limit extends Stage { + get _name(): string { + return 'limit'; + } + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({}); + } + + constructor(private limit: number, options: StageOptions) { + hardAssert( + !isNaN(limit) && limit !== Infinity && limit !== -Infinity, + 0x882c, + 'Invalid limit value' + ); + super(options); + } + + /** + * @internal + * @private + */ + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: [toNumber(serializer, this.limit)] + }; + } +} + +/** + * @beta + */ +export class Offset extends Stage { + get _name(): string { + return 'offset'; + } + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({}); + } + + constructor(private offset: number, options: StageOptions) { + super(options); + } + + /** + * @internal + * @private + */ + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: [toNumber(serializer, this.offset)] + }; + } +} + +/** + * @beta + */ +export class Select extends Stage { + get _name(): string { + return 'select'; + } + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({}); + } + + constructor( + private selections: Map, + options: StageOptions + ) { + super(options); + } + + /** + * @internal + * @private + */ + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: [toMapValue(serializer, this.selections)] + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + readUserDataHelper(this.selections, context); + } +} + +/** + * @beta + */ +export class Sort extends Stage { + get _name(): string { + return 'sort'; + } + + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({}); + } + + constructor(private orderings: Ordering[], options: StageOptions) { + super(options); + } + + /** + * @internal + * @private + */ + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: this.orderings.map(o => o._toProto(serializer)) + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + readUserDataHelper(this.orderings, context); + } +} + +/** + * @beta + */ +export class Sample extends Stage { + get _name(): string { + return 'sample'; + } + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({}); + } + + constructor( + private rate: number, + private mode: 'percent' | 'documents', + options: StageOptions + ) { + super(options); + } + + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: [toNumber(serializer, this.rate)!, toStringValue(this.mode)!] + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + } +} + +/** + * @beta + */ +export class Union extends Stage { + get _name(): string { + return 'union'; + } + + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({}); + } + + constructor(private other: Pipeline, options: StageOptions) { + super(options); + } + + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: [toPipelineValue(this.other._toProto(serializer))] + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + } +} + +/** + * @beta + */ +export class Unnest extends Stage { + get _name(): string { + return 'unnest'; + } + + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({ + indexField: { + serverName: 'index_field' + } + }); + } + + constructor( + private alias: string, + private expr: Expression, + options: StageOptions + ) { + super(options); + } + + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: [ + this.expr._toProto(serializer), + field(this.alias)._toProto(serializer) + ] + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + readUserDataHelper(this.expr, context); + } +} + +/** + * @beta + */ +export class Replace extends Stage { + static readonly MODE = 'full_replace'; + + get _name(): string { + return 'replace_with'; + } + + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({}); + } + + constructor(private map: Expression, options: StageOptions) { + super(options); + } + + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + ...super._toProto(serializer), + args: [this.map._toProto(serializer), toStringValue(Replace.MODE)] + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + readUserDataHelper(this.map, context); + } +} + +/** + * @beta + */ +export class RawStage extends Stage { + /** + * @private + * @internal + */ + constructor( + private name: string, + private params: Array, + rawOptions: Record + ) { + super({ rawOptions }); + } + + /** + * @internal + * @private + */ + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + name: this.name, + args: this.params.map(o => o._toProto(serializer)), + options: this.optionsProto + }; + } + + _readUserData(context: ParseContext): void { + super._readUserData(context); + readUserDataHelper(this.params, context); + } + + get _name(): string { + return this.name; + } + + get _optionsUtil(): OptionsUtil { + return new OptionsUtil({}); + } +} + +/** + * Helper to read user data across a number of different formats. + * @param name Name of the calling function. Used for error messages when invalid user data is encountered. + * @param expressionMap + * @return the expressionMap argument. + * @private + */ +function readUserDataHelper< + T extends Map | UserData[] | UserData +>(expressionMap: T, context: ParseContext): T { + if (isUserData(expressionMap)) { + expressionMap._readUserData(context); + } else if (Array.isArray(expressionMap)) { + expressionMap.forEach(readableData => readableData._readUserData(context)); + } else { + expressionMap.forEach(expr => expr._readUserData(context)); + } + return expressionMap; +} diff --git a/packages/firestore/src/lite-api/stage_options.ts b/packages/firestore/src/lite-api/stage_options.ts new file mode 100644 index 0000000000..ea9ff08823 --- /dev/null +++ b/packages/firestore/src/lite-api/stage_options.ts @@ -0,0 +1,345 @@ +/** + * @beta + * @license + * Copyright 2025 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 { OneOf } from '../util/types'; + +import { + AliasedAggregate, + BooleanExpression, + Expression, + Field, + Ordering, + Selectable +} from './expressions'; +import { Pipeline } from './pipeline'; +import { CollectionReference, DocumentReference } from './reference'; +import { VectorValue } from './vector_value'; + +/** + * @beta + * Options defining how a Stage is evaluated. + */ +export interface StageOptions { + /** + * @beta + * An escape hatch to set options not known at SDK build time. These values + * will be passed directly to the Firestore backend and not used by the SDK. + * + * The option name will be used as provided. And must match the name + * format used by the backend (hint: use a snake_case_name). + * + * Raw option values can be any type supported + * by Firestore (for example: string, boolean, number, map, …). Value types + * not known to the SDK will be rejected. + * + * Values specified in rawOptions will take precedence over any options + * with the same name set by the SDK. + * + * `rawOptions` supports dot notation, if you want to override + * a nested option. + */ + rawOptions?: { + [name: string]: unknown; + }; +} +/** + * @beta + * Options defining how a CollectionStage is evaluated. See {@link PipelineSource.collection}. + */ +export type CollectionStageOptions = StageOptions & { + /** + * @beta + * Name or reference to the collection that will be used as the Pipeline source. + */ + collection: string | CollectionReference; + + /** + * @beta + * Specifies the name of an index to be used for a query, overriding the query optimizer's default choice. + * This can be useful for performance tuning in specific scenarios where the default index selection + * does not yield optimal performance. + * + * @remarks This property is optional. When provided, it should be the exact name of the index to force. + */ + forceIndex?: string; +}; + +/** + * @beta + * Defines the configuration options for a {@link CollectionGroupStage} within a pipeline. + * This type extends {@link StageOptions} and provides specific settings for how a collection group + * is identified and processed during pipeline execution. + * + * @see {@link PipelineSource.collectionGroup} to create a collection group stage. + */ +export type CollectionGroupStageOptions = StageOptions & { + /** + * @beta + * ID of the collection group to use as the Pipeline source. + */ + collectionId: string; + + /** + * @beta + * Specifies the name of an index to be used for a query, overriding the query optimizer's default choice. + * This can be useful for performance tuning in specific scenarios where the default index selection + * does not yield optimal performance. + * + * @remarks This property is optional. When provided, it should be the exact name of the index to force. + */ + forceIndex?: string; +}; +/** + * @beta + * Options defining how a DatabaseStage is evaluated. See {@link PipelineSource.database}. + */ +export type DatabaseStageOptions = StageOptions & {}; +/** + * @beta + * Options defining how a DocumentsStage is evaluated. See {@link PipelineSource.documents}. + */ +export type DocumentsStageOptions = StageOptions & { + /** + * @beta + * An array of paths and DocumentReferences specifying the individual documents that will be the source of this pipeline. + * The converters for these DocumentReferences will be ignored and not have an effect on this pipeline. + * There must be at least one document specified in the array. + */ + docs: Array; +}; +/** + * @beta + * Options defining how an AddFieldsStage is evaluated. See {@link Pipeline.addFields}. + */ +export type AddFieldsStageOptions = StageOptions & { + /** + * @beta + * The fields to add to each document, specified as a {@link Selectable}. + * At least one field is required. + */ + fields: Selectable[]; +}; +/** + * @beta + * Options defining how a RemoveFieldsStage is evaluated. See {@link Pipeline.removeFields}. + */ +export type RemoveFieldsStageOptions = StageOptions & { + /** + * @beta + * The fields to remove from each document. + */ + fields: Array; +}; +/** + * @beta + * Options defining how a SelectStage is evaluated. See {@link Pipeline.select}. + */ +export type SelectStageOptions = StageOptions & { + /** + * @beta + * The fields to include in the output documents, specified as {@link Selectable} expression + * or as a string value indicating the field name. + */ + selections: Array; +}; +/** + * @beta + * Options defining how a WhereStage is evaluated. See {@link Pipeline.where}. + */ +export type WhereStageOptions = StageOptions & { + /** + * @beta + * The {@link BooleanExpression} to apply as a filter for each input document to this stage. + */ + condition: BooleanExpression; +}; +/** + * @beta + * Options defining how an OffsetStage is evaluated. See {@link Pipeline.offset}. + */ +export type OffsetStageOptions = StageOptions & { + /** + * @beta + * The number of documents to skip. + */ + offset: number; +}; +/** + * @beta + * Options defining how a LimitStage is evaluated. See {@link Pipeline.limit}. + */ +export type LimitStageOptions = StageOptions & { + /** + * @beta + * The maximum number of documents to return. + */ + limit: number; +}; +/** + * @beta + * Options defining how a DistinctStage is evaluated. See {@link Pipeline.distinct}. + */ +export type DistinctStageOptions = StageOptions & { + /** + * @beta + * The {@link Selectable} expressions or field names to consider when determining + * distinct value combinations (groups). + */ + groups: Array; +}; + +/** + * @beta + * Options defining how an AggregateStage is evaluated. See {@link Pipeline.aggregate}. + */ +export type AggregateStageOptions = StageOptions & { + /** + * @beta + * The {@link AliasedAggregate} values specifying aggregate operations to + * perform on the input documents. + */ + accumulators: AliasedAggregate[]; + /** + * @beta + * The {@link Selectable} expressions or field names to consider when determining + * distinct value combinations (groups), which will be aggregated over. + */ + groups?: Array; +}; +/** + * @beta + * Options defining how a FindNearestStage is evaluated. See {@link Pipeline.findNearest}. + */ +export type FindNearestStageOptions = StageOptions & { + /** + * @beta + * Specifies the field to be used. This can be a string representing the field path + * (e.g., 'fieldName', 'nested.fieldName') or an object of type {@link Field} + * representing a more complex field expression. + */ + field: Field | string; + /** + * @beta + * Specifies the query vector value, to which the vector distance will be computed. + */ + vectorValue: VectorValue | number[]; + /** + * @beta + * Specifies the method used to compute the distance between vectors. + * + * Possible values are: + * - `'euclidean'`: Euclidean distance. + * - `'cosine'`: Cosine similarity. + * - `'dot_product'`: Dot product. + */ + distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; + /** + * @beta + * The maximum number of documents to return from the FindNearest stage. + */ + limit?: number; + /** + * @beta + * If set, specifies the field on the output documents that will contain + * the computed vector distance for the document. If not set, the computed + * vector distance will not be returned. + */ + distanceField?: string; +}; +/** + * @beta + * Options defining how a ReplaceWithStage is evaluated. See {@link Pipeline.replaceWith}. + */ +export type ReplaceWithStageOptions = StageOptions & { + /** + * @beta + * The name of a field that contains a map or an {@link Expression} that + * evaluates to a map. + */ + map: Expression | string; +}; +/** + * @beta + * Defines the options for evaluating a sample stage within a pipeline. + * This type combines common {@link StageOptions} with a specific configuration + * where only one of the defined sampling methods can be applied. + * + * See {@link Pipeline.sample} to create a sample stage.. + */ +export type SampleStageOptions = StageOptions & + OneOf<{ + /** + * @beta + * If set, specifies the sample rate as a percentage of the + * input documents. + * + * Cannot be set when `documents: number` is set. + */ + percentage: number; + /** + * @beta + * If set, specifies the sample rate as a total number of + * documents to sample from the input documents. + * + * Cannot be set when `percentage: number` is set. + */ + documents: number; + }>; +/** + * @beta + * Options defining how a UnionStage is evaluated. See {@link Pipeline.union}. + */ +export type UnionStageOptions = StageOptions & { + /** + * @beta + * Specifies the other Pipeline to union with. + */ + other: Pipeline; +}; + +/** + * @beta + * Represents the specific options available for configuring an `UnnestStage` within a pipeline. + */ +export type UnnestStageOptions = StageOptions & { + /** + * @beta + * A `Selectable` object that defines an array expression to be un-nested + * and the alias for the un-nested field. + */ + selectable: Selectable; + /** + * @beta + * If set, specifies the field on the output documents that will contain the + * offset (starting at zero) that the element is from the original array. + */ + indexField?: string; +}; +/** + * @beta + * @beta + * Options defining how a SortStage is evaluated. See {@link Pipeline.sort}. + */ +export type SortStageOptions = StageOptions & { + /** + * @beta + * Orderings specify how the input documents are sorted. + * One or more ordering are required. + */ + orderings: Ordering[]; +}; diff --git a/packages/firestore/src/lite-api/user_data_reader.ts b/packages/firestore/src/lite-api/user_data_reader.ts index a3022be627..8ea2728b3a 100644 --- a/packages/firestore/src/lite-api/user_data_reader.ts +++ b/packages/firestore/src/lite-api/user_data_reader.ts @@ -22,7 +22,7 @@ import { } from '@firebase/firestore-types'; import { Compat, deepEqual, getModularInstance } from '@firebase/util'; -import { ParseContext } from '../api/parse_context'; +import { ContextSettings, ParseContext } from '../api/parse_context'; import { DatabaseId } from '../core/database_info'; import { DocumentKey } from '../model/document_key'; import { FieldMask } from '../model/field_mask'; @@ -56,7 +56,8 @@ import { JsonProtoSerializer, toBytes, toResourceName, - toTimestamp + toTimestamp, + isProtoValueSerializable } from '../remote/serializer'; import { debugAssert, fail } from '../util/assert'; import { Code, FirestoreError } from '../util/error'; @@ -181,33 +182,6 @@ function isWrite(dataSource: UserDataSource): boolean { } } -/** Contains the settings that are mutated as we parse user data. */ -interface ContextSettings { - /** Indicates what kind of API method this data came from. */ - readonly dataSource: UserDataSource; - /** The name of the method the user called to create the ParseContext. */ - readonly methodName: string; - /** The document the user is attempting to modify, if that applies. */ - readonly targetDoc?: DocumentKey; - /** - * A path within the object being parsed. This could be an empty path (in - * which case the context represents the root of the data being parsed), or a - * nonempty path (indicating the context represents a nested location within - * the data). - */ - readonly path?: InternalFieldPath; - /** - * Whether or not this context corresponds to an element of an array. - * If not set, elements are treated as if they were outside of arrays. - */ - readonly arrayElement?: boolean; - /** - * Whether or not a converter was specified in this context. If true, error - * messages will reference the converter when invalid data is provided. - */ - readonly hasConverter?: boolean; -} - /** A "context" object passed around while parsing user data. */ class ParseContextImpl implements ParseContext { readonly fieldTransforms: FieldTransform[]; @@ -731,7 +705,7 @@ export function parseQueryValue( */ export function parseData( input: unknown, - context: ParseContextImpl + context: ParseContext ): ProtoValue | null { // Unwrap the API type from the Compat SDK. This will return the API type // from firestore-exp. @@ -782,7 +756,7 @@ export function parseData( export function parseObject( obj: Dict, - context: ParseContextImpl + context: ParseContext ): { mapValue: ProtoMapValue } { const fields: Dict = {}; @@ -804,7 +778,7 @@ export function parseObject( return { mapValue: { fields } }; } -function parseArray(array: unknown[], context: ParseContextImpl): ProtoValue { +function parseArray(array: unknown[], context: ParseContext): ProtoValue { const values: ProtoValue[] = []; let entryIndex = 0; for (const entry of array) { @@ -829,7 +803,7 @@ function parseArray(array: unknown[], context: ParseContextImpl): ProtoValue { */ function parseSentinelFieldValue( value: FieldValue, - context: ParseContextImpl + context: ParseContext ): void { // Sentinels are only supported with writes, and not within arrays. if (!isWrite(context.dataSource)) { @@ -854,9 +828,9 @@ function parseSentinelFieldValue( * * @returns The parsed value */ -function parseScalarValue( +export function parseScalarValue( value: unknown, - context: ParseContextImpl + context: ParseContext ): ProtoValue | null { value = getModularInstance(value); @@ -911,6 +885,8 @@ function parseScalarValue( }; } else if (value instanceof VectorValue) { return parseVectorValue(value, context); + } else if (isProtoValueSerializable(value)) { + return value._toProto(context.serializer); } else { throw context.createError( `Unsupported field value: ${valueDescription(value)}` @@ -922,9 +898,10 @@ function parseScalarValue( * Creates a new VectorValue proto value (using the internal format). */ export function parseVectorValue( - value: VectorValue, - context: ParseContextImpl -): ProtoValue { + value: VectorValue | number[], + context: ParseContext +): { mapValue: ProtoMapValue } { + const values = value instanceof VectorValue ? value.toArray() : value; const mapValue: ProtoMapValue = { fields: { [TYPE_KEY]: { @@ -932,7 +909,7 @@ export function parseVectorValue( }, [VECTOR_MAP_VECTORS_KEY]: { arrayValue: { - values: value.toArray().map(value => { + values: values.map(value => { if (typeof value !== 'number') { throw context.createError( 'VectorValues must only contain numeric values.' @@ -956,7 +933,7 @@ export function parseVectorValue( * GeoPoints, etc. are not considered to look like JSON objects since they map * to specific FieldValue types other than ObjectValue. */ -function looksLikeJsonObject(input: unknown): boolean { +export function looksLikeJsonObject(input: unknown): boolean { return ( typeof input === 'object' && input !== null && @@ -967,13 +944,14 @@ function looksLikeJsonObject(input: unknown): boolean { !(input instanceof Bytes) && !(input instanceof DocumentReference) && !(input instanceof FieldValue) && - !(input instanceof VectorValue) + !(input instanceof VectorValue) && + !isProtoValueSerializable(input) ); } function validatePlainObject( message: string, - context: ParseContextImpl, + context: ParseContext, input: unknown ): asserts input is Dict { if (!looksLikeJsonObject(input) || !isPlainObject(input)) { @@ -1101,3 +1079,11 @@ function fieldMaskContains( ): boolean { return haystack.some(v => v.isEqual(needle)); } + +export interface UserData { + _readUserData(context: ParseContext): void; +} + +export function isUserData(value: unknown): value is UserData { + return typeof (value as UserData)._readUserData === 'function'; +} diff --git a/packages/firestore/src/model/aggregate_result_value.ts b/packages/firestore/src/model/aggregate_result_value.ts new file mode 100644 index 0000000000..042dc29d34 --- /dev/null +++ b/packages/firestore/src/model/aggregate_result_value.ts @@ -0,0 +1,42 @@ +/** + * @license + * Copyright 2024 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 { + MapValue as ProtoMapValue, + Value as ProtoValue +} from '../protos/firestore_proto_api'; + +import { valueEquals } from './values'; + +/** + * An AggregateResultValue represents a MapValue in the Firestore Proto. + */ +export class AggregateResultValue { + constructor(readonly value: { mapValue: ProtoMapValue }) {} + + static empty(): AggregateResultValue { + return new AggregateResultValue({ mapValue: {} }); + } + + aggregate(alias: string): ProtoValue | null { + return this.value.mapValue.fields?.[alias] ?? null; + } + + isEqual(other: AggregateResultValue): boolean { + return valueEquals(this.value, other.value); + } +} diff --git a/packages/firestore/src/model/pipeline_stream_element.ts b/packages/firestore/src/model/pipeline_stream_element.ts new file mode 100644 index 0000000000..efa27e2cc4 --- /dev/null +++ b/packages/firestore/src/model/pipeline_stream_element.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2024 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 { SnapshotVersion } from '../core/snapshot_version'; + +import { DocumentKey } from './document_key'; +import { ObjectValue } from './object_value'; + +export interface PipelineStreamElement { + transaction?: string; + key?: DocumentKey; + executionTime?: SnapshotVersion; + createTime?: SnapshotVersion; + updateTime?: SnapshotVersion; + fields?: ObjectValue; +} diff --git a/packages/firestore/src/platform/browser/webchannel_connection.ts b/packages/firestore/src/platform/browser/webchannel_connection.ts index 56f57aa959..b3fdd53340 100644 --- a/packages/firestore/src/platform/browser/webchannel_connection.ts +++ b/packages/firestore/src/platform/browser/webchannel_connection.ts @@ -16,7 +16,7 @@ */ import { - createWebChannelTransport, + createWebChannelTransport as internalCreateWebChannelTransport, ErrorCode, EventType, WebChannel, @@ -27,7 +27,8 @@ import { EventTarget, StatEvent, Event, - Stat + Stat, + WebChannelTransport } from '@firebase/webchannel-wrapper/webchannel-blob'; import { Token } from '../../api/credentials'; @@ -53,6 +54,28 @@ const RPC_STREAM_SERVICE = 'google.firestore.v1.Firestore'; const XHR_TIMEOUT_SECS = 15; +// Closure events are guarded and exceptions are swallowed, so catch any +// exception and rethrow using a setTimeout so they become visible again. +// Note that eventually this function could go away if we are confident +// enough the code is exception free. +const unguardedEventListen = ( + target: EventTarget, + type: string | number, + fn: (param: T) => void +): void => { + // TODO(dimond): closure typing seems broken because WebChannel does + // not implement goog.events.Listenable + target.listen(type, (param: unknown) => { + try { + fn(param as T); + } catch (e) { + setTimeout(() => { + throw e; + }, 0); + } + }); +}; + export class WebChannelConnection extends RestConnection { private readonly forceLongPolling: boolean; private readonly autoDetectLongPolling: boolean; @@ -70,6 +93,29 @@ export class WebChannelConnection extends RestConnection { this.longPollingOptions = info.longPollingOptions; } + /** + * Track if the STAT_EVENT listener has been initialized. + */ + static statEventListenerInitialized: boolean = false; + + /** + * Initialize STAT_EVENT listener once. Subsequent calls are a no-op. + * getStatEventTarget() returns the same target every time. + */ + static ensureStatEventListenerInitialized(): void { + if (!WebChannelConnection.statEventListenerInitialized) { + const requestStats = getStatEventTarget(); + unguardedEventListen(requestStats, Event.STAT_EVENT, event => { + if (event.stat === Stat.PROXY) { + logDebug(LOG_TAG, `STAT_EVENT: detected buffering proxy`); + } else if (event.stat === Stat.NOPROXY) { + logDebug(LOG_TAG, `STAT_EVENT: detected no buffering proxy`); + } + }); + WebChannelConnection.statEventListenerInitialized = true; + } + } + protected performRPCRequest( rpcName: string, url: string, @@ -181,8 +227,7 @@ export class WebChannelConnection extends RestConnection { rpcName, '/channel' ]; - const webchannelTransport = createWebChannelTransport(); - const requestStats = getStatEventTarget(); + const webchannelTransport = this.createWebChannelTransport(); const request: WebChannelOptions = { // Required for backend stickiness, routing behavior is based on this // parameter. @@ -285,28 +330,6 @@ export class WebChannelConnection extends RestConnection { closeFn: () => channel.close() }); - // Closure events are guarded and exceptions are swallowed, so catch any - // exception and rethrow using a setTimeout so they become visible again. - // Note that eventually this function could go away if we are confident - // enough the code is exception free. - const unguardedEventListen = ( - target: EventTarget, - type: string | number, - fn: (param: T) => void - ): void => { - // TODO(dimond): closure typing seems broken because WebChannel does - // not implement goog.events.Listenable - target.listen(type, (param: unknown) => { - try { - fn(param as T); - } catch (e) { - setTimeout(() => { - throw e; - }, 0); - } - }); - }; - unguardedEventListen(channel, WebChannel.EventType.OPEN, () => { if (!closed) { logDebug( @@ -409,19 +432,8 @@ export class WebChannelConnection extends RestConnection { } ); - unguardedEventListen(requestStats, Event.STAT_EVENT, event => { - if (event.stat === Stat.PROXY) { - logDebug( - LOG_TAG, - `RPC '${rpcName}' stream ${streamId} detected buffering proxy` - ); - } else if (event.stat === Stat.NOPROXY) { - logDebug( - LOG_TAG, - `RPC '${rpcName}' stream ${streamId} detected no buffering proxy` - ); - } - }); + // Ensure that event listeners are configured for STAT_EVENTs. + WebChannelConnection.ensureStatEventListenerInitialized(); setTimeout(() => { // Technically we could/should wait for the WebChannel opened event, @@ -460,4 +472,29 @@ export class WebChannelConnection extends RestConnection { instance => instance === webChannel ); } + + /** + * Modifies the headers for a request, adding the api key if present, + * and then calling super.modifyHeadersForRequest + */ + protected modifyHeadersForRequest( + headers: StringMap, + authToken: Token | null, + appCheckToken: Token | null + ): void { + super.modifyHeadersForRequest(headers, authToken, appCheckToken); + + // For web channel streams, we want to send the api key in the headers. + if (this.databaseInfo.apiKey) { + headers['x-goog-api-key'] = this.databaseInfo.apiKey; + } + } + + /** + * Wrapped for mocking. + * @protected + */ + protected createWebChannelTransport(): WebChannelTransport { + return internalCreateWebChannelTransport(); + } } diff --git a/packages/firestore/src/platform/node/grpc_connection.ts b/packages/firestore/src/platform/node/grpc_connection.ts index d50a314941..79ccb3ddf3 100644 --- a/packages/firestore/src/platform/node/grpc_connection.ts +++ b/packages/firestore/src/platform/node/grpc_connection.ts @@ -44,7 +44,8 @@ function createMetadata( databasePath: string, authToken: Token | null, appCheckToken: Token | null, - appId: string + appId: string, + apiKey: string | undefined ): grpc.Metadata { hardAssert( authToken === null || authToken.type === 'OAuth', @@ -69,6 +70,9 @@ function createMetadata( // 11 from Google3. metadata.set('Google-Cloud-Resource-Prefix', databasePath); metadata.set('x-goog-request-params', databasePath); + if (apiKey) { + metadata.set('X-Goog-Api-Key', apiKey); + } return metadata; } @@ -100,7 +104,8 @@ export class GrpcConnection implements Connection { this.databasePath = `projects/${databaseInfo.databaseId.projectId}/databases/${databaseInfo.databaseId.database}`; } - private ensureActiveStub(): GeneratedGrpcStub { + /** made protected for testing */ + protected ensureActiveStub(): GeneratedGrpcStub { if (!this.cachedStub) { logDebug(LOG_TAG, 'Creating Firestore stub.'); const credentials = this.databaseInfo.ssl @@ -127,7 +132,8 @@ export class GrpcConnection implements Connection { this.databasePath, authToken, appCheckToken, - this.databaseInfo.appId + this.databaseInfo.appId, + this.databaseInfo.apiKey ); const jsonRequest = { database: this.databasePath, ...request }; @@ -187,7 +193,8 @@ export class GrpcConnection implements Connection { this.databasePath, authToken, appCheckToken, - this.databaseInfo.appId + this.databaseInfo.appId, + this.databaseInfo.apiKey ); const jsonRequest = { ...request, database: this.databasePath }; const stream = stub[rpcName](jsonRequest, metadata); @@ -239,7 +246,8 @@ export class GrpcConnection implements Connection { this.databasePath, authToken, appCheckToken, - this.databaseInfo.appId + this.databaseInfo.appId, + this.databaseInfo.apiKey ); const grpcStream = stub[rpcName](metadata); diff --git a/packages/firestore/src/platform/rn_lite/snapshot_to_json.ts b/packages/firestore/src/platform/rn_lite/snapshot_to_json.ts index 709509c8a4..0ccebd779f 100644 --- a/packages/firestore/src/platform/rn_lite/snapshot_to_json.ts +++ b/packages/firestore/src/platform/rn_lite/snapshot_to_json.ts @@ -16,3 +16,10 @@ */ export { toByteStreamReader } from '../browser/byte_stream_reader'; + +// This is not included in the RN lite-bundle, but the rollup build +// will fail if these exports are not defined. +export { + buildDocumentSnapshotJsonBundle, + buildQuerySnapshotJsonBundle +} from '../browser/snapshot_to_json'; diff --git a/packages/firestore/src/protos/compile.sh b/packages/firestore/src/protos/compile.sh index 26c46d1a40..9f9fb4b321 100755 --- a/packages/firestore/src/protos/compile.sh +++ b/packages/firestore/src/protos/compile.sh @@ -18,10 +18,17 @@ set -euo pipefail # Variables PROTOS_DIR="." -PBJS="$(npm bin)/pbjs" +PBJS="../../node_modules/.bin/pbjs" +PBTS="../../node_modules/.bin/pbts" -"${PBJS}" --proto_path=. --target=json -o protos.json \ - -r firestore_v1 \ - "${PROTOS_DIR}/google/firestore/v1/*.proto" \ +"${PBJS}" --path=. --target=json -o protos.json \ + -r firestore/v1 "${PROTOS_DIR}/google/firestore/v1/*.proto" \ "${PROTOS_DIR}/google/protobuf/*.proto" "${PROTOS_DIR}/google/type/*.proto" \ "${PROTOS_DIR}/google/rpc/*.proto" "${PROTOS_DIR}/google/api/*.proto" + +"${PBJS}" --path="${PROTOS_DIR}" --target=static -o temp.js \ + -r firestore/v1 "${PROTOS_DIR}/google/firestore/v1/*.proto" \ + "${PROTOS_DIR}/google/protobuf/*.proto" "${PROTOS_DIR}/google/type/*.proto" \ + "${PROTOS_DIR}/google/rpc/*.proto" "${PROTOS_DIR}/google/api/*.proto" + +"${PBTS}" -o temp.d.ts --no-comments temp.js diff --git a/packages/firestore/src/protos/firestore_proto_api.ts b/packages/firestore/src/protos/firestore_proto_api.ts index 9618d71b86..cc1c57259f 100644 --- a/packages/firestore/src/protos/firestore_proto_api.ts +++ b/packages/firestore/src/protos/firestore_proto_api.ts @@ -145,9 +145,21 @@ export interface IValueNullValueEnum { } export declare const ValueNullValueEnum: IValueNullValueEnum; export declare namespace firestoreV1ApiClientInterfaces { + interface Aggregation { + count?: Count; + sum?: Sum; + avg?: Avg; + alias?: string; + } + interface AggregationResult { + aggregateFields?: ApiClientObjectMap; + } interface ArrayValue { values?: Value[]; } + interface Avg { + field?: FieldReference; + } interface BatchGetDocumentsRequest { database?: string; documents?: string[]; @@ -168,6 +180,14 @@ export declare namespace firestoreV1ApiClientInterfaces { interface BeginTransactionResponse { transaction?: string; } + interface BitSequence { + bitmap?: string | Uint8Array; + padding?: number; + } + interface BloomFilter { + bits?: BitSequence; + hashCount?: number; + } interface CollectionSelector { collectionId?: string; allDescendants?: boolean; @@ -185,6 +205,9 @@ export declare namespace firestoreV1ApiClientInterfaces { op?: CompositeFilterOp; filters?: Filter[]; } + interface Count { + upTo?: number; + } interface Cursor { values?: Value[]; before?: boolean; @@ -221,19 +244,23 @@ export declare namespace firestoreV1ApiClientInterfaces { documents?: string[]; } interface Empty {} + interface ExecutePipelineRequest { + database?: string; + structuredPipeline?: StructuredPipeline; + transaction?: string; + newTransaction?: TransactionOptions; + readTime?: string; + } + interface ExecutePipelineResponse { + transaction?: string; + results?: Document[]; + executionTime?: string; + } interface ExistenceFilter { targetId?: number; count?: number; unchangedNames?: BloomFilter; } - interface BloomFilter { - bits?: BitSequence; - hashCount?: number; - } - interface BitSequence { - bitmap?: string | Uint8Array; - padding?: number; - } interface FieldFilter { field?: FieldReference; op?: FieldFilterOp; @@ -254,6 +281,11 @@ export declare namespace firestoreV1ApiClientInterfaces { fieldFilter?: FieldFilter; unaryFilter?: UnaryFilter; } + interface Function { + name?: string; + args?: Value[]; + options?: ApiClientObjectMap; + } interface Index { name?: string; collectionId?: string; @@ -310,6 +342,9 @@ export declare namespace firestoreV1ApiClientInterfaces { field?: FieldReference; direction?: OrderDirection; } + interface Pipeline { + stages?: Stage[]; + } interface Precondition { exists?: boolean; updateTime?: Timestamp; @@ -355,33 +390,24 @@ export declare namespace firestoreV1ApiClientInterfaces { transaction?: string; readTime?: string; } - interface AggregationResult { - aggregateFields?: ApiClientObjectMap; - } interface StructuredAggregationQuery { structuredQuery?: StructuredQuery; aggregations?: Aggregation[]; } - interface Aggregation { - count?: Count; - sum?: Sum; - avg?: Avg; - alias?: string; - } - interface Count { - upTo?: number; - } - interface Sum { - field?: FieldReference; - } - interface Avg { - field?: FieldReference; + interface Stage { + name?: string; + args?: Value[]; + options?: ApiClientObjectMap; } interface Status { code?: number; message?: string; details?: Array>; } + interface StructuredPipeline { + pipeline?: Pipeline; + options?: ApiClientObjectMap; + } interface StructuredQuery { select?: Projection; from?: CollectionSelector[]; @@ -392,6 +418,9 @@ export declare namespace firestoreV1ApiClientInterfaces { offset?: number; limit?: number | { value: number }; } + interface Sum { + field?: FieldReference; + } interface Target { query?: QueryTarget; documents?: DocumentsTarget; @@ -428,6 +457,10 @@ export declare namespace firestoreV1ApiClientInterfaces { geoPointValue?: LatLng; arrayValue?: ArrayValue; mapValue?: MapValue; + fieldReferenceValue?: string; + // eslint-disable-next-line @typescript-eslint/ban-types + functionValue?: Function; + pipelineValue?: Pipeline; } interface Write { update?: Document; @@ -489,12 +522,17 @@ export declare type DocumentsTarget = export declare type Empty = firestoreV1ApiClientInterfaces.Empty; export declare type ExistenceFilter = firestoreV1ApiClientInterfaces.ExistenceFilter; +export declare type ExecutePipelineRequest = + firestoreV1ApiClientInterfaces.ExecutePipelineRequest; +export declare type ExecutePipelineResponse = + firestoreV1ApiClientInterfaces.ExecutePipelineResponse; export declare type FieldFilter = firestoreV1ApiClientInterfaces.FieldFilter; export declare type FieldReference = firestoreV1ApiClientInterfaces.FieldReference; export declare type FieldTransform = firestoreV1ApiClientInterfaces.FieldTransform; export declare type Filter = firestoreV1ApiClientInterfaces.Filter; +export declare type Function = firestoreV1ApiClientInterfaces.Function; export declare type Index = firestoreV1ApiClientInterfaces.Index; export declare type IndexField = firestoreV1ApiClientInterfaces.IndexField; export declare type LatLng = firestoreV1ApiClientInterfaces.LatLng; @@ -513,6 +551,7 @@ export declare type ListenResponse = export declare type MapValue = firestoreV1ApiClientInterfaces.MapValue; export declare type Operation = firestoreV1ApiClientInterfaces.Operation; export declare type Order = firestoreV1ApiClientInterfaces.Order; +export declare type Pipeline = firestoreV1ApiClientInterfaces.Pipeline; export declare type Precondition = firestoreV1ApiClientInterfaces.Precondition; export declare type Projection = firestoreV1ApiClientInterfaces.Projection; export declare type QueryTarget = firestoreV1ApiClientInterfaces.QueryTarget; @@ -529,9 +568,12 @@ export declare type RunAggregationQueryRequest = export declare type Aggregation = firestoreV1ApiClientInterfaces.Aggregation; export declare type RunAggregationQueryResponse = firestoreV1ApiClientInterfaces.RunAggregationQueryResponse; +export declare type Stage = firestoreV1ApiClientInterfaces.Stage; export declare type Status = firestoreV1ApiClientInterfaces.Status; export declare type StructuredQuery = firestoreV1ApiClientInterfaces.StructuredQuery; +export declare type StructuredPipeline = + firestoreV1ApiClientInterfaces.StructuredPipeline; export declare type Target = firestoreV1ApiClientInterfaces.Target; export declare type TargetChange = firestoreV1ApiClientInterfaces.TargetChange; export declare type TransactionOptions = diff --git a/packages/firestore/src/protos/google/api/launch_stage.proto b/packages/firestore/src/protos/google/api/launch_stage.proto new file mode 100644 index 0000000000..9863fc23d4 --- /dev/null +++ b/packages/firestore/src/protos/google/api/launch_stage.proto @@ -0,0 +1,72 @@ +// Copyright 2024 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. + +syntax = "proto3"; + +package google.api; + +option go_package = "google.golang.org/genproto/googleapis/api;api"; +option java_multiple_files = true; +option java_outer_classname = "LaunchStageProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// The launch stage as defined by [Google Cloud Platform +// Launch Stages](https://cloud.google.com/terms/launch-stages). +enum LaunchStage { + // Do not use this default value. + LAUNCH_STAGE_UNSPECIFIED = 0; + + // The feature is not yet implemented. Users can not use it. + UNIMPLEMENTED = 6; + + // Prelaunch features are hidden from users and are only visible internally. + PRELAUNCH = 7; + + // Early Access features are limited to a closed group of testers. To use + // these features, you must sign up in advance and sign a Trusted Tester + // agreement (which includes confidentiality provisions). These features may + // be unstable, changed in backward-incompatible ways, and are not + // guaranteed to be released. + EARLY_ACCESS = 1; + + // Alpha is a limited availability test for releases before they are cleared + // for widespread use. By Alpha, all significant design issues are resolved + // and we are in the process of verifying functionality. Alpha customers + // need to apply for access, agree to applicable terms, and have their + // projects allowlisted. Alpha releases don't have to be feature complete, + // no SLAs are provided, and there are no technical support obligations, but + // they will be far enough along that customers can actually use them in + // test environments or for limited-use tests -- just like they would in + // normal production cases. + ALPHA = 2; + + // Beta is the point at which we are ready to open a release for any + // customer to use. There are no SLA or technical support obligations in a + // Beta release. Products will be complete from a feature perspective, but + // may have some open outstanding issues. Beta releases are suitable for + // limited production use cases. + BETA = 3; + + // GA features are open to all developers and are considered stable and + // fully qualified for production use. + GA = 4; + + // Deprecated features are scheduled to be shut down and removed. For more + // information, see the "Deprecation Policy" section of our [Terms of + // Service](https://cloud.google.com/terms/) + // and the [Google Cloud Platform Subject to the Deprecation + // Policy](https://cloud.google.com/terms/deprecation) documentation. + DEPRECATED = 5; +} diff --git a/packages/firestore/src/protos/google/firestore/v1/document.proto b/packages/firestore/src/protos/google/firestore/v1/document.proto index 5238a943ce..f760575050 100644 --- a/packages/firestore/src/protos/google/firestore/v1/document.proto +++ b/packages/firestore/src/protos/google/firestore/v1/document.proto @@ -129,6 +129,48 @@ message Value { // A map value. MapValue map_value = 6; + // Value which references a field. + // + // This is considered relative (vs absolute) since it only refers to a field + // and not a field within a particular document. + // + // **Requires:** + // + // * Must follow [field reference][FieldReference.field_path] limitations. + // + // * Not allowed to be used when writing documents. + // + // (-- NOTE(batchik): long term, there is no reason this type should not be + // allowed to be used on the write path. --) + string field_reference_value = 19; + + // A value that represents an unevaluated expression. + // + // **Requires:** + // + // * Not allowed to be used when writing documents. + // + // (-- NOTE(batchik): similar to above, there is no reason to not allow + // storing expressions into the database, just no plan to support in + // the near term. + // + // This would actually be an interesting way to represent user-defined + // functions or more expressive rules-based systems. --) + Function function_value = 20; + + // A value that represents an unevaluated pipeline. + // + // **Requires:** + // + // * Not allowed to be used when writing documents. + // + // (-- NOTE(batchik): similar to above, there is no reason to not allow + // storing expressions into the database, just no plan to support in + // the near term. + // + // This would actually be an interesting way to represent user-defined + // functions or more expressive rules-based systems. --) + Pipeline pipeline_value = 21; } } @@ -148,3 +190,73 @@ message MapValue { // not exceed 1,500 bytes and cannot be empty. map fields = 1; } + + +// Represents an unevaluated scalar expression. +// +// For example, the expression `like(user_name, "%alice%")` is represented as: +// +// ``` +// name: "like" +// args { field_reference: "user_name" } +// args { string_value: "%alice%" } +// ``` +// +// (-- api-linter: core::0123::resource-annotation=disabled +// aip.dev/not-precedent: this is not a One Platform API resource. --) +message Function { + // The name of the function to evaluate. + // + // **Requires:** + // + // * must be in snake case (lower case with underscore separator). + // + string name = 1; + + // Ordered list of arguments the given function expects. + repeated Value args = 2; + + // Optional named arguments that certain functions may support. + map options = 3; +} + +// A Firestore query represented as an ordered list of operations / stages. +message Pipeline { + // A single operation within a pipeline. + // + // A stage is made up of a unique name, and a list of arguments. The exact + // number of arguments & types is dependent on the stage type. + // + // To give an example, the stage `filter(state = "MD")` would be encoded as: + // + // ``` + // name: "filter" + // args { + // function_value { + // name: "eq" + // args { field_reference_value: "state" } + // args { string_value: "MD" } + // } + // } + // ``` + // + // See public documentation for the full list. + message Stage { + // The name of the stage to evaluate. + // + // **Requires:** + // + // * must be in snake case (lower case with underscore separator). + // + string name = 1; + + // Ordered list of arguments the given stage expects. + repeated Value args = 2; + + // Optional named arguments that certain functions may support. + map options = 3; + } + + // Ordered list of stages to evaluate. + repeated Stage stages = 1; +} diff --git a/packages/firestore/src/protos/google/firestore/v1/firestore.proto b/packages/firestore/src/protos/google/firestore/v1/firestore.proto index a8fc0d54b5..3e7b62e060 100644 --- a/packages/firestore/src/protos/google/firestore/v1/firestore.proto +++ b/packages/firestore/src/protos/google/firestore/v1/firestore.proto @@ -22,6 +22,7 @@ import "google/api/field_behavior.proto"; import "google/firestore/v1/aggregation_result.proto"; import "google/firestore/v1/common.proto"; import "google/firestore/v1/document.proto"; +import "google/firestore/v1/pipeline.proto"; import "google/firestore/v1/query.proto"; import "google/firestore/v1/write.proto"; import "google/protobuf/empty.proto"; @@ -135,6 +136,15 @@ service Firestore { }; } + // Executes a pipeline query. + rpc ExecutePipeline(ExecutePipelineRequest) + returns (stream ExecutePipelineResponse) { + option (google.api.http) = { + post: "/v1/{database=projects/*/databases/*}/documents:executePipeline" + body: "*" + }; + } + // Runs an aggregation query. // // Rather than producing [Document][google.firestore.v1.Document] results like [Firestore.RunQuery][google.firestore.v1.Firestore.RunQuery], @@ -157,7 +167,7 @@ service Firestore { } }; } - + // Partitions a query by returning partition cursors that can be used to run // the query in parallel. The returned partition cursors are split points that // can be used by RunQuery as starting/end points for the query results. @@ -547,6 +557,82 @@ message RunQueryResponse { int32 skipped_results = 4; } +// The request for [Firestore.ExecutePipeline][]. +message ExecutePipelineRequest { + // Database identifier, in the form `projects/{project}/databases/{database}`. + string database = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + + oneof pipeline_type { + // A pipelined operation. + StructuredPipeline structured_pipeline = 2; + } + + // Optional consistency arguments, defaults to strong consistency. + oneof consistency_selector { + // Run the query within an already active transaction. + // + // The value here is the opaque transaction ID to execute the query in. + bytes transaction = 5; + + // Execute the pipeline in a new transaction. + // + // The identifier of the newly created transaction will be returned in the + // first response on the stream. This defaults to a read-only transaction. + TransactionOptions new_transaction = 6; + + // Execute the pipeline in a snapshot transaction at the given time. + // + // This must be a microsecond precision timestamp within the past one hour, + // or if Point-in-Time Recovery is enabled, can additionally be a whole + // minute timestamp within the past 7 days. + google.protobuf.Timestamp read_time = 7; + } + + // Explain / analyze options for the pipeline. + // ExplainOptions explain_options = 8 [(google.api.field_behavior) = OPTIONAL]; +} + +// The response for [Firestore.Execute][]. +message ExecutePipelineResponse { + // Newly created transaction identifier. + // + // This field is only specified on the first response from the server when + // the request specified [ExecuteRequest.new_transaction][]. + bytes transaction = 1; + + // An ordered batch of results returned executing a pipeline. + // + // The batch size is variable, and can even be zero for when only a partial + // progress message is returned. + // + // The fields present in the returned documents are only those that were + // explicitly requested in the pipeline, this include those like + // [`__name__`][Document.name] & [`__update_time__`][Document.update_time]. + // This is explicitly a divergence from `Firestore.RunQuery` / + // `Firestore.GetDocument` RPCs which always return such fields even when they + // are not specified in the [`mask`][DocumentMask]. + repeated Document results = 2; + + // The time at which the document(s) were read. + // + // This may be monotonically increasing; in this case, the previous documents + // in the result stream are guaranteed not to have changed between their + // `execution_time` and this one. + // + // If the query returns no results, a response with `execution_time` and no + // `results` will be sent, and this represents the time at which the operation + // was run. + google.protobuf.Timestamp execution_time = 3; + + // Query explain metrics. + // + // Set on the last response when [ExecutePipelineRequest.explain_options][] + // was specified on the request. + // ExplainMetrics explain_metrics = 4; +} + // The request for [Firestore.RunAggregationQuery][google.firestore.v1.Firestore.RunAggregationQuery]. message RunAggregationQueryRequest { // Required. The parent resource name. In the format: diff --git a/packages/firestore/src/protos/google/firestore/v1/pipeline.proto b/packages/firestore/src/protos/google/firestore/v1/pipeline.proto new file mode 100644 index 0000000000..ea5b230933 --- /dev/null +++ b/packages/firestore/src/protos/google/firestore/v1/pipeline.proto @@ -0,0 +1,41 @@ +/*! + * Copyright 2024 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. + */ + +syntax = "proto3"; +package google.firestore.v1; +import "google/firestore/v1/document.proto"; +option csharp_namespace = "Google.Cloud.Firestore.V1"; +option php_namespace = "Google\\Cloud\\Firestore\\V1"; +option ruby_package = "Google::Cloud::Firestore::V1"; +option java_multiple_files = true; +option java_package = "com.google.firestore.v1"; +option java_outer_classname = "PipelineProto"; +option objc_class_prefix = "GCFS"; +// A Firestore query represented as an ordered list of operations / stages. +// +// This is considered the top-level function which plans & executes a query. +// It is logically equivalent to `query(stages, options)`, but prevents the +// client from having to build a function wrapper. +message StructuredPipeline { + // The pipeline query to execute. + Pipeline pipeline = 1; + // Optional query-level arguments. + // + // (-- Think query statement hints. --) + // + // (-- TODO(batchik): define the api contract of using an unsupported hint --) + map options = 2; +} diff --git a/packages/firestore/src/protos/google/firestore/v1/query_profile.proto b/packages/firestore/src/protos/google/firestore/v1/query_profile.proto new file mode 100644 index 0000000000..de27144a03 --- /dev/null +++ b/packages/firestore/src/protos/google/firestore/v1/query_profile.proto @@ -0,0 +1,92 @@ +// Copyright 2024 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. + +syntax = "proto3"; + +package google.firestore.v1; + +import "google/api/field_behavior.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; + +option csharp_namespace = "Google.Cloud.Firestore.V1"; +option go_package = "cloud.google.com/go/firestore/apiv1/firestorepb;firestorepb"; +option java_multiple_files = true; +option java_outer_classname = "QueryProfileProto"; +option java_package = "com.google.firestore.v1"; +option objc_class_prefix = "GCFS"; +option php_namespace = "Google\\Cloud\\Firestore\\V1"; +option ruby_package = "Google::Cloud::Firestore::V1"; + +// Specification of the Firestore Query Profile fields. + +// Explain options for the query. +message ExplainOptions { + // Optional. Whether to execute this query. + // + // When false (the default), the query will be planned, returning only + // metrics from the planning stages. + // + // When true, the query will be planned and executed, returning the full + // query results along with both planning and execution stage metrics. + bool analyze = 1 [(google.api.field_behavior) = OPTIONAL]; +} + +// Explain metrics for the query. +message ExplainMetrics { + // Planning phase information for the query. + PlanSummary plan_summary = 1; + + // Aggregated stats from the execution of the query. Only present when + // [ExplainOptions.analyze][google.firestore.v1.ExplainOptions.analyze] is set + // to true. + ExecutionStats execution_stats = 2; +} + +// Planning phase information for the query. +message PlanSummary { + // The indexes selected for the query. For example: + // [ + // {"query_scope": "Collection", "properties": "(foo ASC, __name__ ASC)"}, + // {"query_scope": "Collection", "properties": "(bar ASC, __name__ ASC)"} + // ] + repeated google.protobuf.Struct indexes_used = 1; +} + +// Execution statistics for the query. +message ExecutionStats { + // Total number of results returned, including documents, projections, + // aggregation results, keys. + int64 results_returned = 1; + + // Total time to execute the query in the backend. + google.protobuf.Duration execution_duration = 3; + + // Total billable read operations. + int64 read_operations = 4; + + // Debugging statistics from the execution of the query. Note that the + // debugging stats are subject to change as Firestore evolves. It could + // include: + // { + // "indexes_entries_scanned": "1000", + // "documents_scanned": "20", + // "billing_details" : { + // "documents_billable": "20", + // "index_entries_billable": "1000", + // "min_query_cost": "0" + // } + // } + google.protobuf.Struct debug_stats = 5; +} diff --git a/packages/firestore/src/protos/protos.json b/packages/firestore/src/protos/protos.json index b2c50ccaee..5b73c4647f 100644 --- a/packages/firestore/src/protos/protos.json +++ b/packages/firestore/src/protos/protos.json @@ -4,16 +4,78 @@ "nested": { "protobuf": { "options": { - "csharp_namespace": "Google.Protobuf.WellKnownTypes", - "go_package": "github.com/golang/protobuf/ptypes/wrappers", + "go_package": "github.com/golang/protobuf/protoc-gen-go/descriptor;descriptor", "java_package": "com.google.protobuf", - "java_outer_classname": "WrappersProto", - "java_multiple_files": true, + "java_outer_classname": "DescriptorProtos", + "csharp_namespace": "Google.Protobuf.Reflection", "objc_class_prefix": "GPB", "cc_enable_arenas": true, "optimize_for": "SPEED" }, "nested": { + "Struct": { + "fields": { + "fields": { + "keyType": "string", + "type": "Value", + "id": 1 + } + } + }, + "Value": { + "oneofs": { + "kind": { + "oneof": [ + "nullValue", + "numberValue", + "stringValue", + "boolValue", + "structValue", + "listValue" + ] + } + }, + "fields": { + "nullValue": { + "type": "NullValue", + "id": 1 + }, + "numberValue": { + "type": "double", + "id": 2 + }, + "stringValue": { + "type": "string", + "id": 3 + }, + "boolValue": { + "type": "bool", + "id": 4 + }, + "structValue": { + "type": "Struct", + "id": 5 + }, + "listValue": { + "type": "ListValue", + "id": 6 + } + } + }, + "NullValue": { + "values": { + "NULL_VALUE": 0 + } + }, + "ListValue": { + "fields": { + "values": { + "rule": "repeated", + "type": "Value", + "id": 1 + } + } + }, "Timestamp": { "fields": { "seconds": { @@ -161,6 +223,10 @@ "end": { "type": "int32", "id": 2 + }, + "options": { + "type": "ExtensionRangeOptions", + "id": 3 } } }, @@ -178,6 +244,21 @@ } } }, + "ExtensionRangeOptions": { + "fields": { + "uninterpretedOption": { + "rule": "repeated", + "type": "UninterpretedOption", + "id": 999 + } + }, + "extensions": [ + [ + 1000, + 536870911 + ] + ] + }, "FieldDescriptorProto": { "fields": { "name": { @@ -279,6 +360,30 @@ "options": { "type": "EnumOptions", "id": 3 + }, + "reservedRange": { + "rule": "repeated", + "type": "EnumReservedRange", + "id": 4 + }, + "reservedName": { + "rule": "repeated", + "type": "string", + "id": 5 + } + }, + "nested": { + "EnumReservedRange": { + "fields": { + "start": { + "type": "int32", + "id": 1 + }, + "end": { + "type": "int32", + "id": 2 + } + } } } }, @@ -335,11 +440,17 @@ }, "clientStreaming": { "type": "bool", - "id": 5 + "id": 5, + "options": { + "default": false + } }, "serverStreaming": { "type": "bool", - "id": 6 + "id": 6, + "options": { + "default": false + } } } }, @@ -355,7 +466,10 @@ }, "javaMultipleFiles": { "type": "bool", - "id": 10 + "id": 10, + "options": { + "default": false + } }, "javaGenerateEqualsAndHash": { "type": "bool", @@ -366,7 +480,10 @@ }, "javaStringCheckUtf8": { "type": "bool", - "id": 27 + "id": 27, + "options": { + "default": false + } }, "optimizeFor": { "type": "OptimizeMode", @@ -381,23 +498,45 @@ }, "ccGenericServices": { "type": "bool", - "id": 16 + "id": 16, + "options": { + "default": false + } }, "javaGenericServices": { "type": "bool", - "id": 17 + "id": 17, + "options": { + "default": false + } }, "pyGenericServices": { "type": "bool", - "id": 18 + "id": 18, + "options": { + "default": false + } + }, + "phpGenericServices": { + "type": "bool", + "id": 42, + "options": { + "default": false + } }, "deprecated": { "type": "bool", - "id": 23 + "id": 23, + "options": { + "default": false + } }, "ccEnableArenas": { "type": "bool", - "id": 31 + "id": 31, + "options": { + "default": false + } }, "objcClassPrefix": { "type": "string", @@ -407,6 +546,26 @@ "type": "string", "id": 37 }, + "swiftPrefix": { + "type": "string", + "id": 39 + }, + "phpClassPrefix": { + "type": "string", + "id": 40 + }, + "phpNamespace": { + "type": "string", + "id": 41 + }, + "phpMetadataNamespace": { + "type": "string", + "id": 44 + }, + "rubyPackage": { + "type": "string", + "id": 45 + }, "uninterpretedOption": { "rule": "repeated", "type": "UninterpretedOption", @@ -439,15 +598,24 @@ "fields": { "messageSetWireFormat": { "type": "bool", - "id": 1 + "id": 1, + "options": { + "default": false + } }, "noStandardDescriptorAccessor": { "type": "bool", - "id": 2 + "id": 2, + "options": { + "default": false + } }, "deprecated": { "type": "bool", - "id": 3 + "id": 3, + "options": { + "default": false + } }, "mapEntry": { "type": "bool", @@ -469,6 +637,10 @@ [ 8, 8 + ], + [ + 9, + 9 ] ] }, @@ -494,15 +666,24 @@ }, "lazy": { "type": "bool", - "id": 5 + "id": 5, + "options": { + "default": false + } }, "deprecated": { "type": "bool", - "id": 3 + "id": 3, + "options": { + "default": false + } }, "weak": { "type": "bool", - "id": 10 + "id": 10, + "options": { + "default": false + } }, "uninterpretedOption": { "rule": "repeated", @@ -562,7 +743,10 @@ }, "deprecated": { "type": "bool", - "id": 3 + "id": 3, + "options": { + "default": false + } }, "uninterpretedOption": { "rule": "repeated", @@ -575,13 +759,22 @@ 1000, 536870911 ] + ], + "reserved": [ + [ + 5, + 5 + ] ] }, "EnumValueOptions": { "fields": { "deprecated": { "type": "bool", - "id": 1 + "id": 1, + "options": { + "default": false + } }, "uninterpretedOption": { "rule": "repeated", @@ -600,7 +793,10 @@ "fields": { "deprecated": { "type": "bool", - "id": 33 + "id": 33, + "options": { + "default": false + } }, "uninterpretedOption": { "rule": "repeated", @@ -619,7 +815,17 @@ "fields": { "deprecated": { "type": "bool", - "id": 33 + "id": 33, + "options": { + "default": false + } + }, + "idempotencyLevel": { + "type": "IdempotencyLevel", + "id": 34, + "options": { + "default": "IDEMPOTENCY_UNKNOWN" + } }, "uninterpretedOption": { "rule": "repeated", @@ -632,7 +838,16 @@ 1000, 536870911 ] - ] + ], + "nested": { + "IdempotencyLevel": { + "values": { + "IDEMPOTENCY_UNKNOWN": 0, + "NO_SIDE_EFFECTS": 1, + "IDEMPOTENT": 2 + } + } + } }, "UninterpretedOption": { "fields": { @@ -753,72 +968,6 @@ } } }, - "Struct": { - "fields": { - "fields": { - "keyType": "string", - "type": "Value", - "id": 1 - } - } - }, - "Value": { - "oneofs": { - "kind": { - "oneof": [ - "nullValue", - "numberValue", - "stringValue", - "boolValue", - "structValue", - "listValue" - ] - } - }, - "fields": { - "nullValue": { - "type": "NullValue", - "id": 1 - }, - "numberValue": { - "type": "double", - "id": 2 - }, - "stringValue": { - "type": "string", - "id": 3 - }, - "boolValue": { - "type": "bool", - "id": 4 - }, - "structValue": { - "type": "Struct", - "id": 5 - }, - "listValue": { - "type": "ListValue", - "id": 6 - } - } - }, - "NullValue": { - "values": { - "NULL_VALUE": 0 - } - }, - "ListValue": { - "fields": { - "values": { - "rule": "repeated", - "type": "Value", - "id": 1 - } - } - }, - "Empty": { - "fields": {} - }, "DoubleValue": { "fields": { "value": { @@ -891,9 +1040,12 @@ } } }, + "Empty": { + "fields": {} + }, "Any": { "fields": { - "typeUrl": { + "type_url": { "type": "string", "id": 1 }, @@ -902,6 +1054,18 @@ "id": 2 } } + }, + "Duration": { + "fields": { + "seconds": { + "type": "int64", + "id": 1 + }, + "nanos": { + "type": "int32", + "id": 2 + } + } } } }, @@ -910,9 +1074,9 @@ "v1": { "options": { "csharp_namespace": "Google.Cloud.Firestore.V1", - "go_package": "google.golang.org/genproto/googleapis/firestore/v1;firestore", + "go_package": "cloud.google.com/go/firestore/apiv1/firestorepb;firestorepb", "java_multiple_files": true, - "java_outer_classname": "WriteProto", + "java_outer_classname": "QueryProfileProto", "java_package": "com.google.firestore.v1", "objc_class_prefix": "GCFS", "php_namespace": "Google\\Cloud\\Firestore\\V1", @@ -928,104 +1092,6 @@ } } }, - "BitSequence": { - "fields": { - "bitmap": { - "type": "bytes", - "id": 1 - }, - "padding": { - "type": "int32", - "id": 2 - } - } - }, - "BloomFilter": { - "fields": { - "bits": { - "type": "BitSequence", - "id": 1 - }, - "hashCount": { - "type": "int32", - "id": 2 - } - } - }, - "DocumentMask": { - "fields": { - "fieldPaths": { - "rule": "repeated", - "type": "string", - "id": 1 - } - } - }, - "Precondition": { - "oneofs": { - "conditionType": { - "oneof": [ - "exists", - "updateTime" - ] - } - }, - "fields": { - "exists": { - "type": "bool", - "id": 1 - }, - "updateTime": { - "type": "google.protobuf.Timestamp", - "id": 2 - } - } - }, - "TransactionOptions": { - "oneofs": { - "mode": { - "oneof": [ - "readOnly", - "readWrite" - ] - } - }, - "fields": { - "readOnly": { - "type": "ReadOnly", - "id": 2 - }, - "readWrite": { - "type": "ReadWrite", - "id": 3 - } - }, - "nested": { - "ReadWrite": { - "fields": { - "retryTransaction": { - "type": "bytes", - "id": 1 - } - } - }, - "ReadOnly": { - "oneofs": { - "consistencySelector": { - "oneof": [ - "readTime" - ] - } - }, - "fields": { - "readTime": { - "type": "google.protobuf.Timestamp", - "id": 2 - } - } - } - } - }, "Document": { "fields": { "name": { @@ -1061,7 +1127,10 @@ "referenceValue", "geoPointValue", "arrayValue", - "mapValue" + "mapValue", + "fieldReferenceValue", + "functionValue", + "pipelineValue" ] } }, @@ -1106,27 +1175,184 @@ "type": "ArrayValue", "id": 9 }, - "mapValue": { - "type": "MapValue", - "id": 6 + "mapValue": { + "type": "MapValue", + "id": 6 + }, + "fieldReferenceValue": { + "type": "string", + "id": 19 + }, + "functionValue": { + "type": "Function", + "id": 20 + }, + "pipelineValue": { + "type": "Pipeline", + "id": 21 + } + } + }, + "ArrayValue": { + "fields": { + "values": { + "rule": "repeated", + "type": "Value", + "id": 1 + } + } + }, + "MapValue": { + "fields": { + "fields": { + "keyType": "string", + "type": "Value", + "id": 1 + } + } + }, + "Function": { + "fields": { + "name": { + "type": "string", + "id": 1 + }, + "args": { + "rule": "repeated", + "type": "Value", + "id": 2 + }, + "options": { + "keyType": "string", + "type": "Value", + "id": 3 + } + } + }, + "Pipeline": { + "fields": { + "stages": { + "rule": "repeated", + "type": "Stage", + "id": 1 + } + }, + "nested": { + "Stage": { + "fields": { + "name": { + "type": "string", + "id": 1 + }, + "args": { + "rule": "repeated", + "type": "Value", + "id": 2 + }, + "options": { + "keyType": "string", + "type": "Value", + "id": 3 + } + } + } + } + }, + "BitSequence": { + "fields": { + "bitmap": { + "type": "bytes", + "id": 1 + }, + "padding": { + "type": "int32", + "id": 2 + } + } + }, + "BloomFilter": { + "fields": { + "bits": { + "type": "BitSequence", + "id": 1 + }, + "hashCount": { + "type": "int32", + "id": 2 } } }, - "ArrayValue": { + "DocumentMask": { "fields": { - "values": { + "fieldPaths": { "rule": "repeated", - "type": "Value", + "type": "string", "id": 1 } } }, - "MapValue": { + "Precondition": { + "oneofs": { + "conditionType": { + "oneof": [ + "exists", + "updateTime" + ] + } + }, "fields": { - "fields": { - "keyType": "string", - "type": "Value", + "exists": { + "type": "bool", "id": 1 + }, + "updateTime": { + "type": "google.protobuf.Timestamp", + "id": 2 + } + } + }, + "TransactionOptions": { + "oneofs": { + "mode": { + "oneof": [ + "readOnly", + "readWrite" + ] + } + }, + "fields": { + "readOnly": { + "type": "ReadOnly", + "id": 2 + }, + "readWrite": { + "type": "ReadWrite", + "id": 3 + } + }, + "nested": { + "ReadWrite": { + "fields": { + "retryTransaction": { + "type": "bytes", + "id": 1 + } + } + }, + "ReadOnly": { + "oneofs": { + "consistencySelector": { + "oneof": [ + "readTime" + ] + } + }, + "fields": { + "readTime": { + "type": "google.protobuf.Timestamp", + "id": 2 + } + } } } }, @@ -1302,6 +1528,23 @@ } ] }, + "ExecutePipeline": { + "requestType": "ExecutePipelineRequest", + "responseType": "ExecutePipelineResponse", + "responseStream": true, + "options": { + "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:executePipeline", + "(google.api.http).body": "*" + }, + "parsedOptions": [ + { + "(google.api.http)": { + "post": "/v1/{database=projects/*/databases/*}/documents:executePipeline", + "body": "*" + } + } + ] + }, "RunAggregationQuery": { "requestType": "RunAggregationQueryRequest", "responseType": "RunAggregationQueryResponse", @@ -1816,6 +2059,64 @@ } } }, + "ExecutePipelineRequest": { + "oneofs": { + "pipelineType": { + "oneof": [ + "structuredPipeline" + ] + }, + "consistencySelector": { + "oneof": [ + "transaction", + "newTransaction", + "readTime" + ] + } + }, + "fields": { + "database": { + "type": "string", + "id": 1, + "options": { + "(google.api.field_behavior)": "REQUIRED" + } + }, + "structuredPipeline": { + "type": "StructuredPipeline", + "id": 2 + }, + "transaction": { + "type": "bytes", + "id": 5 + }, + "newTransaction": { + "type": "TransactionOptions", + "id": 6 + }, + "readTime": { + "type": "google.protobuf.Timestamp", + "id": 7 + } + } + }, + "ExecutePipelineResponse": { + "fields": { + "transaction": { + "type": "bytes", + "id": 1 + }, + "results": { + "rule": "repeated", + "type": "Document", + "id": 2 + }, + "executionTime": { + "type": "google.protobuf.Timestamp", + "id": 3 + } + } + }, "RunAggregationQueryRequest": { "oneofs": { "queryType": { @@ -2216,6 +2517,19 @@ } } }, + "StructuredPipeline": { + "fields": { + "pipeline": { + "type": "Pipeline", + "id": 1 + }, + "options": { + "keyType": "string", + "type": "Value", + "id": 2 + } + } + }, "StructuredQuery": { "fields": { "select": { @@ -2474,7 +2788,7 @@ "Sum": { "fields": { "field": { - "type": "FieldReference", + "type": "StructuredQuery.FieldReference", "id": 1 } } @@ -2482,7 +2796,7 @@ "Avg": { "fields": { "field": { - "type": "FieldReference", + "type": "StructuredQuery.FieldReference", "id": 1 } } @@ -2694,6 +3008,82 @@ "id": 3 } } + }, + "ExplainOptions": { + "fields": { + "analyze": { + "type": "bool", + "id": 1, + "options": { + "(google.api.field_behavior)": "OPTIONAL" + } + } + } + }, + "ExplainMetrics": { + "fields": { + "planSummary": { + "type": "PlanSummary", + "id": 1 + }, + "executionStats": { + "type": "ExecutionStats", + "id": 2 + } + } + }, + "PlanSummary": { + "fields": { + "indexesUsed": { + "rule": "repeated", + "type": "google.protobuf.Struct", + "id": 1 + } + } + }, + "ExecutionStats": { + "fields": { + "resultsReturned": { + "type": "int64", + "id": 1 + }, + "executionDuration": { + "type": "google.protobuf.Duration", + "id": 3 + }, + "readOperations": { + "type": "int64", + "id": 4 + }, + "debugStats": { + "type": "google.protobuf.Struct", + "id": 5 + } + } + } + } + } + } + }, + "type": { + "options": { + "cc_enable_arenas": true, + "go_package": "google.golang.org/genproto/googleapis/type/latlng;latlng", + "java_multiple_files": true, + "java_outer_classname": "LatLngProto", + "java_package": "com.google.type", + "objc_class_prefix": "GTP" + }, + "nested": { + "LatLng": { + "fields": { + "latitude": { + "type": "double", + "id": 1 + }, + "longitude": { + "type": "double", + "id": 2 } } } @@ -2701,9 +3091,9 @@ }, "api": { "options": { - "go_package": "google.golang.org/genproto/googleapis/api/annotations;annotations", + "go_package": "google.golang.org/genproto/googleapis/api;api", "java_multiple_files": true, - "java_outer_classname": "HttpProto", + "java_outer_classname": "LaunchStageProto", "java_package": "com.google.api", "objc_class_prefix": "GAPI", "cc_enable_arenas": true @@ -2720,6 +3110,10 @@ "rule": "repeated", "type": "HttpRule", "id": 1 + }, + "fullyDecodeReservedExpansion": { + "type": "bool", + "id": 2 } } }, @@ -2737,6 +3131,10 @@ } }, "fields": { + "selector": { + "type": "string", + "id": 1 + }, "get": { "type": "string", "id": 2 @@ -2761,14 +3159,14 @@ "type": "CustomHttpPattern", "id": 8 }, - "selector": { - "type": "string", - "id": 1 - }, "body": { "type": "string", "id": 7 }, + "responseBody": { + "type": "string", + "id": 12 + }, "additionalBindings": { "rule": "repeated", "type": "HttpRule", @@ -2821,29 +3219,17 @@ "UNORDERED_LIST": 6, "NON_EMPTY_DEFAULT": 7 } - } - } - }, - "type": { - "options": { - "cc_enable_arenas": true, - "go_package": "google.golang.org/genproto/googleapis/type/latlng;latlng", - "java_multiple_files": true, - "java_outer_classname": "LatLngProto", - "java_package": "com.google.type", - "objc_class_prefix": "GTP" - }, - "nested": { - "LatLng": { - "fields": { - "latitude": { - "type": "double", - "id": 1 - }, - "longitude": { - "type": "double", - "id": 2 - } + }, + "LaunchStage": { + "values": { + "LAUNCH_STAGE_UNSPECIFIED": 0, + "UNIMPLEMENTED": 6, + "PRELAUNCH": 7, + "EARLY_ACCESS": 1, + "ALPHA": 2, + "BETA": 3, + "GA": 4, + "DEPRECATED": 5 } } } @@ -2880,4 +3266,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts index f790ede0d5..4c84fca96e 100644 --- a/packages/firestore/src/remote/datastore.ts +++ b/packages/firestore/src/remote/datastore.ts @@ -20,10 +20,12 @@ import { User } from '../auth/user'; import { Aggregate } from '../core/aggregate'; import { DatabaseId } from '../core/database_info'; import { queryToAggregateTarget, Query, queryToTarget } from '../core/query'; +import { StructuredPipeline } from '../core/structured_pipeline'; import { Document } from '../model/document'; import { DocumentKey } from '../model/document_key'; import { Mutation } from '../model/mutation'; import { ResourcePath } from '../model/path'; +import { PipelineStreamElement } from '../model/pipeline_stream_element'; import { ApiClientObjectMap, BatchGetDocumentsRequest as ProtoBatchGetDocumentsRequest, @@ -32,6 +34,8 @@ import { RunAggregationQueryResponse as ProtoRunAggregationQueryResponse, RunQueryRequest as ProtoRunQueryRequest, RunQueryResponse as ProtoRunQueryResponse, + ExecutePipelineRequest as ProtoExecutePipelineRequest, + ExecutePipelineResponse as ProtoExecutePipelineResponse, Value } from '../protos/firestore_proto_api'; import { debugAssert, debugCast, hardAssert } from '../util/assert'; @@ -54,7 +58,9 @@ import { toName, toQueryTarget, toResourcePath, - toRunAggregationQueryRequest + toRunAggregationQueryRequest, + fromPipelineResponse, + getEncodedDatabaseId } from './serializer'; /** @@ -236,6 +242,42 @@ export async function invokeBatchGetDocumentsRpc( return result; } +export async function invokeExecutePipeline( + datastore: Datastore, + structuredPipeline: StructuredPipeline +): Promise { + const datastoreImpl = debugCast(datastore, DatastoreImpl); + const executePipelineRequest: ProtoExecutePipelineRequest = { + database: getEncodedDatabaseId(datastoreImpl.serializer), + structuredPipeline: structuredPipeline._toProto(datastoreImpl.serializer) + }; + + const response = await datastoreImpl.invokeStreamingRPC< + ProtoExecutePipelineRequest, + ProtoExecutePipelineResponse + >( + 'ExecutePipeline', + datastoreImpl.serializer.databaseId, + ResourcePath.emptyPath(), + executePipelineRequest + ); + + const result: PipelineStreamElement[] = []; + response.forEach(proto => { + if (!proto.results || proto.results!.length === 0) { + result.push(fromPipelineResponse(datastoreImpl.serializer, proto)); + } else { + return proto.results!.forEach(document => + result.push( + fromPipelineResponse(datastoreImpl.serializer, proto, document) + ) + ); + } + }); + + return result; +} + export async function invokeRunQueryRpc( datastore: Datastore, query: Query diff --git a/packages/firestore/src/remote/internal_serializer.ts b/packages/firestore/src/remote/internal_serializer.ts index 8f27824758..1e759dab81 100644 --- a/packages/firestore/src/remote/internal_serializer.ts +++ b/packages/firestore/src/remote/internal_serializer.ts @@ -18,12 +18,23 @@ import { ensureFirestoreConfigured, Firestore } from '../api/database'; import { AggregateImpl } from '../core/aggregate'; import { queryToAggregateTarget, queryToTarget } from '../core/query'; +import { + StructuredPipeline, + StructuredPipelineOptions +} from '../core/structured_pipeline'; import { AggregateSpec } from '../lite-api/aggregate_types'; +import { getDatastore } from '../lite-api/components'; +import { Pipeline } from '../lite-api/pipeline'; import { Query } from '../lite-api/reference'; +import { ExecutePipelineRequest as ProtoExecutePipelineRequest } from '../protos/firestore_proto_api'; import { cast } from '../util/input_validation'; import { mapToArray } from '../util/obj'; -import { toQueryTarget, toRunAggregationQueryRequest } from './serializer'; +import { + getEncodedDatabaseId, + toQueryTarget, + toRunAggregationQueryRequest +} from './serializer'; /** * @internal @@ -87,3 +98,38 @@ export function _internalAggregationQueryToProtoRunAggregationQueryRequest< /* skipAliasing= */ true ).request; } + +/** + * @internal + * @private + * + * This function is for internal use only. + * + * Returns the `ExecutePipelineRequest` representation of the given query. + * Returns `null` if the Firestore client associated with the given query has + * not been initialized or has been terminated. + * + * @param pipeline - The Pipeline to convert to proto representation. + */ +export function _internalPipelineToExecutePipelineRequestProto( + pipeline: Pipeline + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): any { + const firestore = cast(pipeline._db, Firestore); + const datastore = getDatastore(firestore); + const serializer = datastore.serializer; + if (serializer === undefined) { + return null; + } + + const structuredPipeline = new StructuredPipeline( + pipeline, + new StructuredPipelineOptions() + ); + const executePipelineRequest: ProtoExecutePipelineRequest = { + database: getEncodedDatabaseId(serializer), + structuredPipeline: structuredPipeline._toProto(serializer) + }; + + return executePipelineRequest; +} diff --git a/packages/firestore/src/remote/rest_connection.ts b/packages/firestore/src/remote/rest_connection.ts index 2d6889dac3..83b0c572f9 100644 --- a/packages/firestore/src/remote/rest_connection.ts +++ b/packages/firestore/src/remote/rest_connection.ts @@ -46,6 +46,7 @@ RPC_NAME_URL_MAPPING['BatchGetDocuments'] = 'batchGet'; RPC_NAME_URL_MAPPING['Commit'] = 'commit'; RPC_NAME_URL_MAPPING['RunQuery'] = 'runQuery'; RPC_NAME_URL_MAPPING['RunAggregationQuery'] = 'runAggregationQuery'; +RPC_NAME_URL_MAPPING['ExecutePipeline'] = 'executePipeline'; const RPC_URL_VERSION = 'v1'; @@ -70,7 +71,7 @@ export abstract class RestConnection implements Connection { return false; } - constructor(private readonly databaseInfo: DatabaseInfo) { + constructor(protected readonly databaseInfo: DatabaseInfo) { this.databaseId = databaseInfo.databaseId; const proto = databaseInfo.ssl ? 'https' : 'http'; const projectId = encodeURIComponent(this.databaseId.projectId); @@ -193,13 +194,17 @@ export abstract class RestConnection implements Connection { _forwardCredentials: boolean ): Promise; - private makeUrl(rpcName: string, path: string): string { + protected makeUrl(rpcName: string, path: string): string { const urlRpcName = RPC_NAME_URL_MAPPING[rpcName]; debugAssert( urlRpcName !== undefined, 'Unknown REST mapping for: ' + rpcName ); - return `${this.baseUrl}/${RPC_URL_VERSION}/${path}:${urlRpcName}`; + let url = `${this.baseUrl}/${RPC_URL_VERSION}/${path}:${urlRpcName}`; + if (this.databaseInfo.apiKey) { + url = `${url}?key=${encodeURIComponent(this.databaseInfo.apiKey)}`; + } + return url; } /** diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index 830875f5e1..f11781ac33 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -37,6 +37,8 @@ import { import { SnapshotVersion } from '../core/snapshot_version'; import { targetIsDocumentTarget, Target } from '../core/target'; import { TargetId } from '../core/types'; +import { Bytes } from '../lite-api/bytes'; +import { GeoPoint } from '../lite-api/geo_point'; import { Timestamp } from '../lite-api/timestamp'; import { TargetData, TargetPurpose } from '../local/target_data'; import { MutableDocument } from '../model/document'; @@ -55,6 +57,7 @@ import { import { normalizeTimestamp } from '../model/normalize'; import { ObjectValue } from '../model/object_value'; import { FieldPath, ResourcePath } from '../model/path'; +import { PipelineStreamElement } from '../model/pipeline_stream_element'; import { ArrayRemoveTransformOperation, ArrayUnionTransformOperation, @@ -87,7 +90,11 @@ import { TargetChangeTargetChangeType as ProtoTargetChangeTargetChangeType, Timestamp as ProtoTimestamp, Write as ProtoWrite, - WriteResult as ProtoWriteResult + WriteResult as ProtoWriteResult, + Value as ProtoValue, + MapValue as ProtoMapValue, + ExecutePipelineResponse as ProtoExecutePipelineResponse, + Pipeline } from '../protos/firestore_proto_api'; import { debugAssert, fail, hardAssert } from '../util/assert'; import { ByteString } from '../util/byte_string'; @@ -173,7 +180,7 @@ function fromRpcStatus(status: ProtoStatus): FirestoreError { * our generated proto interfaces say Int32Value must be. But GRPC actually * expects a { value: } struct. */ -function toInt32Proto( +export function toInt32Proto( serializer: JsonProtoSerializer, val: number | null ): number | { value: number } | null { @@ -431,6 +438,37 @@ export function toDocument( }; } +export function fromPipelineResponse( + serializer: JsonProtoSerializer, + proto: ProtoExecutePipelineResponse, + document?: ProtoDocument +): PipelineStreamElement { + const output: PipelineStreamElement = {}; + if (proto.transaction?.length) { + output.transaction = proto.transaction; + } + const executionTime = proto.executionTime + ? fromVersion(proto.executionTime) + : undefined; + output.executionTime = executionTime; + + if (!!document) { + output.key = document.name + ? fromName(serializer, document.name) + : undefined; + + output.fields = new ObjectValue({ mapValue: { fields: document.fields } }); + + output.createTime = document.createTime + ? fromVersion(document.createTime!) + : undefined; + output.updateTime = document.updateTime + ? fromVersion(document.updateTime!) + : undefined; + } + return output; +} + export function fromDocument( serializer: JsonProtoSerializer, document: ProtoDocument, @@ -1414,3 +1452,98 @@ export function isValidResourceName(path: ResourcePath): boolean { path.get(2) === 'databases' ); } + +export interface ProtoSerializable { + _toProto(serializer: JsonProtoSerializer): ProtoType; +} + +export interface ProtoValueSerializable extends ProtoSerializable { + // Supports runtime identification of the ProtoSerializable type. + _protoValueType: 'ProtoValue'; +} + +export function isProtoValueSerializable( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: any +): value is ProtoValueSerializable { + return ( + !!value && + typeof value._toProto === 'function' && + value._protoValueType === 'ProtoValue' + ); +} + +export function toMapValue( + serializer: JsonProtoSerializer, + input: Map> +): ProtoValue { + const map: ProtoMapValue = { fields: {} }; + input.forEach((exp: ProtoSerializable, key: string) => { + if (typeof key !== 'string') { + throw new Error(`Cannot encode map with non-string key: ${key}`); + } + + map.fields![key] = exp._toProto(serializer)!; + }); + return { + mapValue: map + }; +} + +export function toNullValue(value: null): ProtoValue { + return { nullValue: 'NULL_VALUE' }; +} + +export function toBooleanValue(value: boolean): ProtoValue { + return { booleanValue: value }; +} + +export function toStringValue(value: string): ProtoValue { + return { stringValue: value }; +} + +export function toPipelineValue(value: Pipeline): ProtoValue { + return { pipelineValue: value }; +} + +export function dateToTimestampValue( + serializer: JsonProtoSerializer, + value: Date +): ProtoValue { + const timestamp = Timestamp.fromDate(value); + return { + timestampValue: toTimestamp(serializer, timestamp) + }; +} + +export function timestampToTimestampValue( + serializer: JsonProtoSerializer, + value: Timestamp +): ProtoValue { + // Firestore backend truncates precision down to microseconds. To ensure + // offline mode works the same in regards to truncation, perform the + // truncation immediately without waiting for the backend to do that. + const timestamp = new Timestamp( + value.seconds, + Math.floor(value.nanoseconds / 1000) * 1000 + ); + return { + timestampValue: toTimestamp(serializer, timestamp) + }; +} + +export function toGeoPointValue(value: GeoPoint): ProtoValue { + return { + geoPointValue: { + latitude: value.latitude, + longitude: value.longitude + } + }; +} + +export function toBytesValue( + serializer: JsonProtoSerializer, + value: Bytes +): ProtoValue { + return { bytesValue: toBytes(serializer, value._byteString) }; +} diff --git a/packages/firestore/src/util/input_validation.ts b/packages/firestore/src/util/input_validation.ts index 7fd9967b5a..f3b5dda698 100644 --- a/packages/firestore/src/util/input_validation.ts +++ b/packages/firestore/src/util/input_validation.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { DocumentData } from '../lite-api/reference'; import { DocumentKey } from '../model/document_key'; import { ResourcePath } from '../model/path'; @@ -92,7 +93,7 @@ export function validateCollectionPath(path: ResourcePath): void { * Returns true if it's a non-null object without a custom prototype * (i.e. excludes Array, Date, etc.). */ -export function isPlainObject(input: unknown): boolean { +export function isPlainObject(input: unknown): input is DocumentData { return ( typeof input === 'object' && input !== null && diff --git a/packages/firestore/src/util/misc.ts b/packages/firestore/src/util/misc.ts index f2fa04d1b4..e2dc643783 100644 --- a/packages/firestore/src/util/misc.ts +++ b/packages/firestore/src/util/misc.ts @@ -153,6 +153,26 @@ export function arrayEquals( } return left.every((value, index) => comparator(value, right[index])); } + +/** + * Verifies equality for an optional value. + */ +export function isOptionalEqual( + left: T | undefined, + right: T | undefined, + equalityTest: (left: T, right: T) => boolean +): boolean { + if (left === undefined && right === undefined) { + return true; + } + + if (left === undefined || right === undefined) { + return false; + } + + return equalityTest(left, right); +} + /** * Returns the immediate lexicographically-following string. This is useful to * construct an inclusive range for indexeddb iterators. diff --git a/packages/firestore/src/util/obj.ts b/packages/firestore/src/util/obj.ts index c40bc86bc5..2b61da9447 100644 --- a/packages/firestore/src/util/obj.ts +++ b/packages/firestore/src/util/obj.ts @@ -32,7 +32,7 @@ export function objectSize(obj: object): number { } export function forEach( - obj: Dict | undefined, + obj: Record | undefined, fn: (key: string, val: V) => void ): void { for (const key in obj) { diff --git a/packages/firestore/src/util/pipeline_util.ts b/packages/firestore/src/util/pipeline_util.ts new file mode 100644 index 0000000000..a7400828fd --- /dev/null +++ b/packages/firestore/src/util/pipeline_util.ts @@ -0,0 +1,156 @@ +/** + * @license + * Copyright 2025 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 { FirestoreError, vector } from '../api'; +import { + _constant, + AggregateFunction, + AliasedAggregate, + array, + constant, + Expression, + AliasedExpression, + field, + Field, + map, + Selectable +} from '../lite-api/expressions'; +import { VectorValue } from '../lite-api/vector_value'; + +import { fail } from './assert'; +import { isPlainObject } from './input_validation'; +import { isFirestoreValue } from './proto'; +import { isString } from './types'; + +export function selectablesToMap( + selectables: Array +): Map { + const result = new Map(); + for (const selectable of selectables) { + let alias: string; + let expression: Expression; + if (typeof selectable === 'string') { + alias = selectable as string; + expression = field(selectable); + } else if (selectable instanceof Field) { + alias = selectable.alias; + expression = selectable.expr; + } else if (selectable instanceof AliasedExpression) { + alias = selectable.alias; + expression = selectable.expr; + } else { + fail(0x5319, '`selectable` has an unsupported type', { selectable }); + } + + if (result.get(alias) !== undefined) { + throw new FirestoreError( + 'invalid-argument', + `Duplicate alias or field '${alias}'` + ); + } + + result.set(alias, expression); + } + return result; +} + +export function aliasedAggregateToMap( + aliasedAggregatees: AliasedAggregate[] +): Map { + return aliasedAggregatees.reduce( + (map: Map, selectable: AliasedAggregate) => { + if (map.get(selectable.alias) !== undefined) { + throw new FirestoreError( + 'invalid-argument', + `Duplicate alias or field '${selectable.alias}'` + ); + } + + map.set(selectable.alias, selectable.aggregate as AggregateFunction); + return map; + }, + new Map() as Map + ); +} + +/** + * Converts a value to an Expr, Returning either a Constant, MapFunction, + * ArrayFunction, or the input itself (if it's already an expression). + * + * @private + * @internal + * @param value + */ +export function vectorToExpr( + value: VectorValue | number[] | Expression +): Expression { + if (value instanceof Expression) { + return value; + } else if (value instanceof VectorValue) { + const result = constant(value); + return result; + } else if (Array.isArray(value)) { + const result = constant(vector(value)); + return result; + } else { + throw new Error('Unsupported value: ' + typeof value); + } +} + +/** + * Converts a value to an Expr, Returning either a Constant, MapFunction, + * ArrayFunction, or the input itself (if it's already an expression). + * If the input is a string, it is assumed to be a field name, and a + * field(value) is returned. + * + * @private + * @internal + * @param value + */ +export function fieldOrExpression(value: unknown): Expression { + if (isString(value)) { + const result = field(value); + return result; + } else { + return valueToDefaultExpr(value); + } +} +/** + * Converts a value to an Expr, Returning either a Constant, MapFunction, + * ArrayFunction, or the input itself (if it's already an expression). + * + * @private + * @internal + * @param value + */ +export function valueToDefaultExpr(value: unknown): Expression { + let result: Expression | undefined; + if (isFirestoreValue(value)) { + return constant(value); + } + if (value instanceof Expression) { + return value; + } else if (isPlainObject(value)) { + result = map(value as Record); + } else if (value instanceof Array) { + result = array(value); + } else { + result = _constant(value, undefined); + } + + return result; +} diff --git a/packages/firestore/src/util/proto.ts b/packages/firestore/src/util/proto.ts new file mode 100644 index 0000000000..a99f73cfa9 --- /dev/null +++ b/packages/firestore/src/util/proto.ts @@ -0,0 +1,155 @@ +/** + * @license + * Copyright 2024 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 { + ArrayValue as ProtoArrayValue, + Function as ProtoFunction, + LatLng as ProtoLatLng, + MapValue as ProtoMapValue, + Pipeline as ProtoPipeline, + Timestamp as ProtoTimestamp, + Value as ProtoValue +} from '../protos/firestore_proto_api'; + +import { isPlainObject } from './input_validation'; + +/* eslint @typescript-eslint/no-explicit-any: 0 */ + +function isITimestamp(obj: any): obj is ProtoTimestamp { + if (typeof obj !== 'object' || obj === null) { + return false; // Must be a non-null object + } + if ( + 'seconds' in obj && + (obj.seconds === null || + typeof obj.seconds === 'number' || + typeof obj.seconds === 'string') && + 'nanos' in obj && + (obj.nanos === null || typeof obj.nanos === 'number') + ) { + return true; + } + + return false; +} +function isILatLng(obj: any): obj is ProtoLatLng { + if (typeof obj !== 'object' || obj === null) { + return false; // Must be a non-null object + } + if ( + 'latitude' in obj && + (obj.latitude === null || typeof obj.latitude === 'number') && + 'longitude' in obj && + (obj.longitude === null || typeof obj.longitude === 'number') + ) { + return true; + } + + return false; +} +function isIArrayValue(obj: any): obj is ProtoArrayValue { + if (typeof obj !== 'object' || obj === null) { + return false; // Must be a non-null object + } + if ('values' in obj && (obj.values === null || Array.isArray(obj.values))) { + return true; + } + + return false; +} +function isIMapValue(obj: any): obj is ProtoMapValue { + if (typeof obj !== 'object' || obj === null) { + return false; // Must be a non-null object + } + if ('fields' in obj && (obj.fields === null || isPlainObject(obj.fields))) { + return true; + } + + return false; +} +function isIFunction(obj: any): obj is ProtoFunction { + if (typeof obj !== 'object' || obj === null) { + return false; // Must be a non-null object + } + if ( + 'name' in obj && + (obj.name === null || typeof obj.name === 'string') && + 'args' in obj && + (obj.args === null || Array.isArray(obj.args)) + ) { + return true; + } + + return false; +} + +function isIPipeline(obj: any): obj is ProtoPipeline { + if (typeof obj !== 'object' || obj === null) { + return false; // Must be a non-null object + } + if ('stages' in obj && (obj.stages === null || Array.isArray(obj.stages))) { + return true; + } + + return false; +} + +export function isFirestoreValue(obj: any): obj is ProtoValue { + if (typeof obj !== 'object' || obj === null) { + return false; // Must be a non-null object + } + + // Check optional properties and their types + if ( + ('nullValue' in obj && + (obj.nullValue === null || obj.nullValue === 'NULL_VALUE')) || + ('booleanValue' in obj && + (obj.booleanValue === null || typeof obj.booleanValue === 'boolean')) || + ('integerValue' in obj && + (obj.integerValue === null || + typeof obj.integerValue === 'number' || + typeof obj.integerValue === 'string')) || + ('doubleValue' in obj && + (obj.doubleValue === null || typeof obj.doubleValue === 'number')) || + ('timestampValue' in obj && + (obj.timestampValue === null || isITimestamp(obj.timestampValue))) || + ('stringValue' in obj && + (obj.stringValue === null || typeof obj.stringValue === 'string')) || + ('bytesValue' in obj && + (obj.bytesValue === null || obj.bytesValue instanceof Uint8Array)) || + ('referenceValue' in obj && + (obj.referenceValue === null || + typeof obj.referenceValue === 'string')) || + ('geoPointValue' in obj && + (obj.geoPointValue === null || isILatLng(obj.geoPointValue))) || + ('arrayValue' in obj && + (obj.arrayValue === null || isIArrayValue(obj.arrayValue))) || + ('mapValue' in obj && + (obj.mapValue === null || isIMapValue(obj.mapValue))) || + ('fieldReferenceValue' in obj && + (obj.fieldReferenceValue === null || + typeof obj.fieldReferenceValue === 'string')) || + ('functionValue' in obj && + (obj.functionValue === null || isIFunction(obj.functionValue))) || + ('pipelineValue' in obj && + (obj.pipelineValue === null || isIPipeline(obj.pipelineValue))) + ) { + return true; + } + + return false; +} diff --git a/packages/firestore/src/util/types.ts b/packages/firestore/src/util/types.ts index c298bfe213..ad272991ff 100644 --- a/packages/firestore/src/util/types.ts +++ b/packages/firestore/src/util/types.ts @@ -37,6 +37,10 @@ export function isNegativeZero(value: number): boolean { return value === 0 && 1 / value === 1 / -0; } +export function isNumber(value: unknown): value is number { + return typeof value === 'number'; +} + /** * Returns whether a value is an integer and in the safe integer range * @param value - The value to test for being an integer and in the safe range @@ -51,6 +55,10 @@ export function isSafeInteger(value: unknown): boolean { ); } +export function isString(value: unknown): value is string { + return typeof value === 'string'; +} + /** The subset of the browser's Window interface used by the SDK. */ export interface WindowLike { readonly localStorage: Storage; @@ -65,3 +73,19 @@ export interface DocumentLike { addEventListener(type: string, listener: EventListener): void; removeEventListener(type: string, listener: EventListener): void; } + +/** + * @beta + * Utility type to create an type that only allows one + * property of the Type param T to be set. + * + * type XorY = OneOf<{ x: unknown, y: unknown}> + * let a = { x: "foo" } // OK + * let b = { y: "foo" } // OK + * let c = { a: "foo", y: "foo" } // Not OK + */ +export type OneOf = { + [K in keyof T]: Pick & { + [P in Exclude]?: undefined; + }; +}[keyof T]; diff --git a/packages/firestore/test/integration/api/aggregation.test.ts b/packages/firestore/test/integration/api/aggregation.test.ts index ba44dddf51..c6a3489040 100644 --- a/packages/firestore/test/integration/api/aggregation.test.ts +++ b/packages/firestore/test/integration/api/aggregation.test.ts @@ -17,6 +17,7 @@ import { expect } from 'chai'; +import { it } from '../../util/mocha_extensions'; import { collection, collectionGroup, @@ -42,7 +43,6 @@ import { withTestCollection, withTestDb } from '../util/helpers'; -import { USE_EMULATOR } from '../util/settings'; apiDescribe('Count queries', persistence => { it('can run count query getCountFromServer', () => { @@ -150,7 +150,7 @@ apiDescribe('Count queries', persistence => { // production, since the Firestore Emulator does not require index creation // and will, therefore, never fail in this situation. // eslint-disable-next-line no-restricted-properties - (USE_EMULATOR ? it.skip : it)( + it.skipEnterprise.skipEmulator( 'getCountFromServer error message contains console link if missing index', () => { return withEmptyTestCollection(persistence, async coll => { @@ -161,6 +161,7 @@ apiDescribe('Count queries', persistence => { ); // TODO(b/316359394) Remove the special logic for non-default databases // once cl/582465034 is rolled out to production. + // @ts-ignore internal API usage if (coll.firestore._databaseId.isDefaultDatabase) { await expect( getCountFromServer(query_) @@ -362,7 +363,7 @@ apiDescribe('Aggregation queries', persistence => { // production, since the Firestore Emulator does not require index creation // and will, therefore, never fail in this situation. // eslint-disable-next-line no-restricted-properties - (USE_EMULATOR ? it.skip : it)( + it.skipEmulator.skipEnterprise( 'getAggregateFromServer error message contains console link good if missing index', () => { return withEmptyTestCollection(persistence, async coll => { @@ -373,6 +374,7 @@ apiDescribe('Aggregation queries', persistence => { ); // TODO(b/316359394) Remove the special logic for non-default databases // once cl/582465034 is rolled out to production. + // @ts-ignore internal API usage if (coll.firestore._databaseId.isDefaultDatabase) { await expect( getAggregateFromServer(query_, { @@ -480,26 +482,29 @@ apiDescribe('Aggregation queries - sum / average', persistence => { }); }); - it('fails when exceeding the max (5) aggregations using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const promise = getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - count: count(), - totalPagesX: sum('pages'), - averagePagesY: average('pages'), - countZ: count() + it.skipEnterprise( + 'fails when exceeding the max (5) aggregations using getAggregationFromServer', + () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const promise = getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count(), + totalPagesX: sum('pages'), + averagePagesY: average('pages'), + countZ: count() + }); + + await expect(promise).to.eventually.be.rejectedWith( + /maximum number of aggregations/ + ); }); - - await expect(promise).to.eventually.be.rejectedWith( - /maximum number of aggregations/ - ); - }); - }); + } + ); it('returns undefined when getting the result of an unrequested aggregation', () => { const testDocs = { @@ -940,47 +945,50 @@ apiDescribe('Aggregation queries - sum / average', persistence => { }); }); - it('performs sum over a result set of zero documents using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: 3 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('pages', '>', 200)), - { - totalPages: sum('pages') + it.skipEnterprise( + 'performs sum over a result set of zero documents using getAggregationFromServer', + () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 } - ); - expect(snapshot.data().totalPages).to.equal(0); - }); - }); + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('pages', '>', 200)), + { + totalPages: sum('pages') + } + ); + expect(snapshot.data().totalPages).to.equal(0); + }); + } + ); it('performs sum only on numeric fields using getAggregationFromServer', () => { const testDocs = { diff --git a/packages/firestore/test/integration/api/array_transforms.test.ts b/packages/firestore/test/integration/api/array_transforms.test.ts index e1a628435e..19c9417fec 100644 --- a/packages/firestore/test/integration/api/array_transforms.test.ts +++ b/packages/firestore/test/integration/api/array_transforms.test.ts @@ -34,8 +34,6 @@ import { } from '../util/firebase_export'; import { apiDescribe, withTestDb, withTestDoc } from '../util/helpers'; -addEqualityMatcher(); - /** * Note: Transforms are tested pretty thoroughly in server_timestamp.test.ts * (via set, update, transactions, nested in documents, multiple transforms @@ -43,6 +41,8 @@ addEqualityMatcher(); * semantics. */ apiDescribe('Array Transforms:', persistence => { + addEqualityMatcher(); + // A document reference to read and write to. let docRef: DocumentReference; diff --git a/packages/firestore/test/integration/api/bundle.test.ts b/packages/firestore/test/integration/api/bundle.test.ts index 467026d375..3bca3c5496 100644 --- a/packages/firestore/test/integration/api/bundle.test.ts +++ b/packages/firestore/test/integration/api/bundle.test.ts @@ -86,6 +86,7 @@ apiDescribe('Bundles', persistence => { // Extract elements from BUNDLE_TEMPLATE and replace the project ID. const elements = BUNDLE_TEMPLATE.map(e => + // @ts-ignore internal API usage e.replace('{0}', projectId).replace('(default)', db._databaseId.database) ); diff --git a/packages/firestore/test/integration/api/composite_index_query.test.ts b/packages/firestore/test/integration/api/composite_index_query.test.ts index d08cc77bde..0dbc765788 100644 --- a/packages/firestore/test/integration/api/composite_index_query.test.ts +++ b/packages/firestore/test/integration/api/composite_index_query.test.ts @@ -17,6 +17,7 @@ import { expect } from 'chai'; +import { it } from '../../util/mocha_extensions'; import { CompositeIndexTestHelper } from '../util/composite_index_test_helper'; import { where, @@ -196,27 +197,33 @@ apiDescribe('Composite Index Queries', persistence => { }); }); - it('performs aggregations on documents with all aggregated fields using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100, year: 1980 }, - b: { author: 'authorB', title: 'titleB', pages: 50, year: 2020 }, - c: { author: 'authorC', title: 'titleC', pages: 150, year: 2021 }, - d: { author: 'authorD', title: 'titleD', pages: 50 } - }; - const testHelper = new CompositeIndexTestHelper(); - return testHelper.withTestDocs(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(testHelper.query(coll), { - totalPages: sum('pages'), - averagePages: average('pages'), - averageYear: average('year'), - count: count() + it.skipEmulator.skipEnterprise( + 'performs aggregations on documents with all aggregated fields using getAggregationFromServer', + () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100, year: 1980 }, + b: { author: 'authorB', title: 'titleB', pages: 50, year: 2020 }, + c: { author: 'authorC', title: 'titleC', pages: 150, year: 2021 }, + d: { author: 'authorD', title: 'titleD', pages: 50 } + }; + const testHelper = new CompositeIndexTestHelper(); + return testHelper.withTestDocs(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + testHelper.query(coll), + { + totalPages: sum('pages'), + averagePages: average('pages'), + averageYear: average('year'), + count: count() + } + ); + expect(snapshot.data().totalPages).to.equal(300); + expect(snapshot.data().averagePages).to.equal(100); + expect(snapshot.data().averageYear).to.equal(2007); + expect(snapshot.data().count).to.equal(3); }); - expect(snapshot.data().totalPages).to.equal(300); - expect(snapshot.data().averagePages).to.equal(100); - expect(snapshot.data().averageYear).to.equal(2007); - expect(snapshot.data().count).to.equal(3); - }); - }); + } + ); it('performs aggregates on multiple fields where one aggregate could cause short-circuit due to NaN using getAggregationFromServer', () => { const testDocs = { @@ -262,56 +269,62 @@ apiDescribe('Composite Index Queries', persistence => { }); }); - it('performs aggregates when using `array-contains-any` operator getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: [5, 1000] - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: [4] - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: [2222, 3] - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: [0] - } - }; - const testHelper = new CompositeIndexTestHelper(); - return testHelper.withTestDocs(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - testHelper.query(coll, where('rating', 'array-contains-any', [5, 3])), - { - totalRating: sum('rating'), - averageRating: average('rating'), - totalPages: sum('pages'), - averagePages: average('pages'), - countOfDocs: count() + it.skipEnterprise( + 'performs aggregates when using `array-contains-any` operator getAggregationFromServer', + () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: [5, 1000] + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: [4] + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: [2222, 3] + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: [0] } - ); - expect(snapshot.data().totalRating).to.equal(0); - expect(snapshot.data().averageRating).to.be.null; - expect(snapshot.data().totalPages).to.equal(200); - expect(snapshot.data().averagePages).to.equal(100); - expect(snapshot.data().countOfDocs).to.equal(2); - }); - }); + }; + const testHelper = new CompositeIndexTestHelper(); + return testHelper.withTestDocs(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + testHelper.query( + coll, + where('rating', 'array-contains-any', [5, 3]) + ), + { + totalRating: sum('rating'), + averageRating: average('rating'), + totalPages: sum('pages'), + averagePages: average('pages'), + countOfDocs: count() + } + ); + expect(snapshot.data().totalRating).to.equal(0); + expect(snapshot.data().averageRating).to.be.null; + expect(snapshot.data().totalPages).to.equal(200); + expect(snapshot.data().averagePages).to.equal(100); + expect(snapshot.data().countOfDocs).to.equal(2); + }); + } + ); }); describe('Multiple Inequality', () => { @@ -950,18 +963,24 @@ apiDescribe('Composite Index Queries', persistence => { }); }); - it('inequality query will reject if document key appears only in equality filter', () => { - const testHelper = new CompositeIndexTestHelper(); - return testHelper.withTestCollection(persistence, async coll => { - const query_ = testHelper.query( - coll, - where('key', '!=', 42), - where(documentId(), '==', 'doc1') - ); - await expect(testHelper.getDocs(query_)).to.be.eventually.rejectedWith( - 'Equality on key is not allowed if there are other inequality fields and key does not appear in inequalities.' - ); - }); - }); + // + it.skipEnterprise( + 'inequality query will reject if document key appears only in equality filter', + () => { + const testHelper = new CompositeIndexTestHelper(); + return testHelper.withTestCollection(persistence, async coll => { + const query_ = testHelper.query( + coll, + where('key', '!=', 42), + where(documentId(), '==', 'doc1') + ); + await expect( + testHelper.getDocs(query_) + ).to.be.eventually.rejectedWith( + 'Equality on key is not allowed if there are other inequality fields and key does not appear in inequalities.' + ); + }); + } + ); }); }); diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index b63c03a4f6..f38a590e04 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -20,6 +20,7 @@ import { Deferred, isNode } from '@firebase/util'; import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; +import { it } from '../../util/mocha_extensions'; import { EventsAccumulator } from '../util/events_accumulator'; import { addDoc, @@ -1900,6 +1901,7 @@ apiDescribe('Database', persistence => { const firestore2 = newTestFirestore( newTestApp(options.projectId!, name), DEFAULT_SETTINGS, + // @ts-ignore internal API usage firestore._databaseId.database ); await enableIndexedDbPersistence(firestore2); @@ -1945,6 +1947,7 @@ apiDescribe('Database', persistence => { const firestore2 = newTestFirestore( newTestApp(options.projectId!, name), undefined, + // @ts-ignore internal API usage docRef.firestore._databaseId.database ); await enableIndexedDbPersistence(firestore2); @@ -2109,6 +2112,7 @@ apiDescribe('Database', persistence => { const deferred: Deferred = new Deferred(); const unsubscribe = onSnapshot(docRef, snapshot => {}, deferred.resolve); + // @ts-ignore internal API usage await firestore._restart(); await expect(deferred.promise) @@ -2733,58 +2737,10 @@ apiDescribe('Database', persistence => { }); }); - it('snapshot listener sorts filtered query by DocumentId same way as get query', async () => { - const testDocs = { - 'A': { a: 1 }, - 'a': { a: 1 }, - 'Aa': { a: 1 }, - '7': { a: 1 }, - '12': { a: 1 }, - '__id7__': { a: 1 }, - '__id12__': { a: 1 }, - '__id-2__': { a: 1 }, - '_id1__': { a: 1 }, - '__id1_': { a: 1 }, - '__id': { a: 1 }, - // largest long numbers - '__id9223372036854775807__': { a: 1 }, - '__id9223372036854775806__': { a: 1 }, - // smallest long numbers - '__id-9223372036854775808__': { a: 1 }, - '__id-9223372036854775807__': { a: 1 } - }; - - return withTestCollection(persistence, testDocs, async collectionRef => { - const filteredQuery = query( - collectionRef, - orderBy(documentId()), - where(documentId(), '>', '__id7__'), - where(documentId(), '<=', 'Aa') - ); - const expectedDocs = [ - '__id12__', - '__id9223372036854775806__', - '__id9223372036854775807__', - '12', - '7', - 'A', - 'Aa' - ]; - - const getSnapshot = await getDocsFromServer(filteredQuery); - expect(toIds(getSnapshot)).to.deep.equal(expectedDocs); - - const storeEvent = new EventsAccumulator(); - const unsubscribe = onSnapshot(filteredQuery, storeEvent.storeEvent); - const watchSnapshot = await storeEvent.awaitEvent(); - expect(toIds(watchSnapshot)).to.deep.equal(expectedDocs); - unsubscribe(); - }); - }); - - // eslint-disable-next-line no-restricted-properties - (persistence.gc === 'lru' ? describe : describe.skip)('offline', () => { - it('SDK orders query the same way online and offline', async () => { + // Enterprise does not sort numeric IDs before string + it.skipEnterprise( + 'snapshot listener sorts filtered query by DocumentId same way as get query', + async () => { const testDocs = { 'A': { a: 1 }, 'a': { a: 1 }, @@ -2809,37 +2765,13 @@ apiDescribe('Database', persistence => { persistence, testDocs, async collectionRef => { - const orderedQuery = query(collectionRef, orderBy(documentId())); - let expectedDocs = [ - '__id-9223372036854775808__', - '__id-9223372036854775807__', - '__id-2__', - '__id7__', - '__id12__', - '__id9223372036854775806__', - '__id9223372036854775807__', - '12', - '7', - 'A', - 'Aa', - '__id', - '__id1_', - '_id1__', - 'a' - ]; - await checkOnlineAndOfflineResultsMatch( - collectionRef, - orderedQuery, - ...expectedDocs - ); - const filteredQuery = query( collectionRef, orderBy(documentId()), where(documentId(), '>', '__id7__'), where(documentId(), '<=', 'Aa') ); - expectedDocs = [ + const expectedDocs = [ '__id12__', '__id9223372036854775806__', '__id9223372036854775807__', @@ -2848,14 +2780,100 @@ apiDescribe('Database', persistence => { 'A', 'Aa' ]; - await checkOnlineAndOfflineResultsMatch( - collectionRef, + + const getSnapshot = await getDocsFromServer(filteredQuery); + expect(toIds(getSnapshot)).to.deep.equal(expectedDocs); + + const storeEvent = new EventsAccumulator(); + const unsubscribe = onSnapshot( filteredQuery, - ...expectedDocs + storeEvent.storeEvent ); + const watchSnapshot = await storeEvent.awaitEvent(); + expect(toIds(watchSnapshot)).to.deep.equal(expectedDocs); + unsubscribe(); } ); - }); + } + ); + + // eslint-disable-next-line no-restricted-properties + (persistence.gc === 'lru' ? describe : describe.skip)('offline', () => { + it.skipEnterprise( + 'SDK orders query the same way online and offline', + async () => { + const testDocs = { + 'A': { a: 1 }, + 'a': { a: 1 }, + 'Aa': { a: 1 }, + '7': { a: 1 }, + '12': { a: 1 }, + '__id7__': { a: 1 }, + '__id12__': { a: 1 }, + '__id-2__': { a: 1 }, + '_id1__': { a: 1 }, + '__id1_': { a: 1 }, + '__id': { a: 1 }, + // largest long numbers + '__id9223372036854775807__': { a: 1 }, + '__id9223372036854775806__': { a: 1 }, + // smallest long numbers + '__id-9223372036854775808__': { a: 1 }, + '__id-9223372036854775807__': { a: 1 } + }; + + return withTestCollection( + persistence, + testDocs, + async collectionRef => { + const orderedQuery = query(collectionRef, orderBy(documentId())); + let expectedDocs = [ + '__id-9223372036854775808__', + '__id-9223372036854775807__', + '__id-2__', + '__id7__', + '__id12__', + '__id9223372036854775806__', + '__id9223372036854775807__', + '12', + '7', + 'A', + 'Aa', + '__id', + '__id1_', + '_id1__', + 'a' + ]; + await checkOnlineAndOfflineResultsMatch( + collectionRef, + orderedQuery, + ...expectedDocs + ); + + const filteredQuery = query( + collectionRef, + orderBy(documentId()), + where(documentId(), '>', '__id7__'), + where(documentId(), '<=', 'Aa') + ); + expectedDocs = [ + '__id12__', + '__id9223372036854775806__', + '__id9223372036854775807__', + '12', + '7', + 'A', + 'Aa' + ]; + await checkOnlineAndOfflineResultsMatch( + collectionRef, + filteredQuery, + ...expectedDocs + ); + } + ); + } + ); }); }); diff --git a/packages/firestore/test/integration/api/pipeline.test.ts b/packages/firestore/test/integration/api/pipeline.test.ts new file mode 100644 index 0000000000..22bc3656ba --- /dev/null +++ b/packages/firestore/test/integration/api/pipeline.test.ts @@ -0,0 +1,4346 @@ +/** + * @license + * Copyright 2024 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 { FirebaseError } from '@firebase/util'; +import { expect, use } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import { addEqualityMatcher } from '../../util/equality_matcher'; +import { Deferred } from '../../util/promise'; +import { + GeoPoint, + Timestamp, + Bytes, + getFirestore, + terminate, + vector, + CollectionReference, + doc, + DocumentData, + Firestore, + setDoc, + collection, + documentId as documentIdFieldPath, + writeBatch, + addDoc, + DocumentReference, + deleteDoc +} from '../util/firebase_export'; +import { apiDescribe, withTestCollection } from '../util/helpers'; +import { + array, + mod, + pipelineResultEqual, + sum, + descending, + map, + execute, + add, + arrayContainsAll, + unixSecondsToTimestamp, + and, + arrayContains, + arrayContainsAny, + count, + average, + cosineDistance, + not, + countAll, + dotProduct, + endsWith, + equal, + reverse, + toUpper, + euclideanDistance, + greaterThan, + like, + lessThan, + stringContains, + divide, + lessThanOrEqual, + arrayLength, + mapGet, + notEqual, + or, + regexContains, + regexMatch, + startsWith, + stringConcat, + subtract, + conditional, + equalAny, + logicalMaximum, + notEqualAny, + multiply, + countIf, + exists, + charLength, + minimum, + maximum, + isError, + ifError, + trim, + isAbsent, + timestampSubtract, + mapRemove, + mapMerge, + documentId, + substring, + logicalMinimum, + xor, + field, + constant, + FindNearestStageOptions, + AggregateFunction, + arrayGet, + ascending, + byteLength, + FunctionExpression, + timestampAdd, + timestampToUnixMicros, + timestampToUnixMillis, + timestampToUnixSeconds, + toLower, + unixMicrosToTimestamp, + unixMillisToTimestamp, + vectorLength, + countDistinct, + ceil, + floor, + exp, + pow, + round, + collectionId, + ln, + log, + sqrt, + stringReverse, + length, + abs, + concat, + currentTimestamp, + ifAbsent, + join, + log10, + arraySum, + PipelineSnapshot, + timestampTruncate, + split, + type +} from '../util/pipeline_export'; + +use(chaiAsPromised); + +const timestampDeltaMS = 1000; + +apiDescribe.skipClassic('Pipelines', persistence => { + addEqualityMatcher(); + + let firestore: Firestore; + let randomCol: CollectionReference; + let beginDocCreation: number = 0; + let endDocCreation: number = 0; + + async function testCollectionWithDocs(docs: { + [id: string]: DocumentData; + }): Promise> { + beginDocCreation = new Date().valueOf(); + for (const id in docs) { + if (docs.hasOwnProperty(id)) { + const ref = doc(randomCol, id); + await setDoc(ref, docs[id]); + } + } + endDocCreation = new Date().valueOf(); + return randomCol; + } + + function expectResults(snapshot: PipelineSnapshot, ...docs: string[]): void; + function expectResults( + snapshot: PipelineSnapshot, + ...data: DocumentData[] + ): void; + + function expectResults( + snapshot: PipelineSnapshot, + ...data: DocumentData[] | string[] + ): void { + const docs = snapshot.results; + + expect(docs.length).to.equal(data.length); + + if (data.length > 0) { + if (typeof data[0] === 'string') { + const actualIds = docs.map(doc => doc.id); + expect(actualIds).to.deep.equal(data); + } else { + docs.forEach(r => { + expect(r.data()).to.deep.equal(data.shift()); + }); + } + } + } + + async function setupBookDocs(): Promise> { + const bookDocs: { [id: string]: DocumentData } = { + book1: { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + embedding: vector([10, 1, 1, 1, 1, 1, 1, 1, 1, 1]) + }, + book2: { + title: 'Pride and Prejudice', + author: 'Jane Austen', + genre: 'Romance', + published: 1813, + rating: 4.5, + tags: ['classic', 'social commentary', 'love'], + awards: { none: true }, + embedding: vector([1, 10, 1, 1, 1, 1, 1, 1, 1, 1]) + }, + book3: { + title: 'One Hundred Years of Solitude', + author: 'Gabriel García Márquez', + genre: 'Magical Realism', + published: 1967, + rating: 4.3, + tags: ['family', 'history', 'fantasy'], + awards: { nobel: true, nebula: false }, + embedding: vector([1, 1, 10, 1, 1, 1, 1, 1, 1, 1]) + }, + book4: { + title: 'The Lord of the Rings', + author: 'J.R.R. Tolkien', + genre: 'Fantasy', + published: 1954, + rating: 4.7, + tags: ['adventure', 'magic', 'epic'], + awards: { hugo: false, nebula: false }, + remarks: null, + cost: NaN, + embedding: vector([1, 1, 1, 10, 1, 1, 1, 1, 1, 1]) + }, + book5: { + title: "The Handmaid's Tale", + author: 'Margaret Atwood', + genre: 'Dystopian', + published: 1985, + rating: 4.1, + tags: ['feminism', 'totalitarianism', 'resistance'], + awards: { 'arthur c. clarke': true, 'booker prize': false }, + embedding: vector([1, 1, 1, 1, 10, 1, 1, 1, 1, 1]) + }, + book6: { + title: 'Crime and Punishment', + author: 'Fyodor Dostoevsky', + genre: 'Psychological Thriller', + published: 1866, + rating: 4.3, + tags: ['philosophy', 'crime', 'redemption'], + awards: { none: true }, + embedding: vector([1, 1, 1, 1, 1, 10, 1, 1, 1, 1]) + }, + book7: { + title: 'To Kill a Mockingbird', + author: 'Harper Lee', + genre: 'Southern Gothic', + published: 1960, + rating: 4.2, + tags: ['racism', 'injustice', 'coming-of-age'], + awards: { pulitzer: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 10, 1, 1, 1]) + }, + book8: { + title: '1984', + author: 'George Orwell', + genre: 'Dystopian', + published: 1949, + rating: 4.2, + tags: ['surveillance', 'totalitarianism', 'propaganda'], + awards: { prometheus: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 1, 10, 1, 1]) + }, + book9: { + title: 'The Great Gatsby', + author: 'F. Scott Fitzgerald', + genre: 'Modernist', + published: 1925, + rating: 4.0, + tags: ['wealth', 'american dream', 'love'], + awards: { none: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 1, 1, 10, 1]) + }, + book10: { + title: 'Dune', + author: 'Frank Herbert', + genre: 'Science Fiction', + published: 1965, + rating: 4.6, + tags: ['politics', 'desert', 'ecology'], + awards: { hugo: true, nebula: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 1, 1, 1, 10]) + } + }; + return testCollectionWithDocs(bookDocs); + } + + let testDeferred: Deferred | undefined; + let withTestCollectionPromise: Promise | undefined; + + beforeEach(async () => { + const setupDeferred = new Deferred(); + testDeferred = new Deferred(); + withTestCollectionPromise = withTestCollection( + persistence, + {}, + async (collectionRef, firestoreInstance) => { + randomCol = collectionRef; + firestore = firestoreInstance; + await setupBookDocs(); + setupDeferred.resolve(); + + return testDeferred?.promise; + } + ); + + await setupDeferred.promise; + }); + + afterEach(async () => { + testDeferred?.resolve(); + await withTestCollectionPromise; + }); + + describe('pipeline results', () => { + it('empty snapshot as expected', async () => { + const snapshot = await execute( + firestore.pipeline().collection(randomCol.path).limit(0) + ); + expect(snapshot.results.length).to.equal(0); + }); + + it('full snapshot as expected', async () => { + const ppl = firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('__name__')); + const snapshot = await execute(ppl); + expect(snapshot.results.length).to.equal(10); + expectResults( + snapshot, + 'book1', + 'book10', + 'book2', + 'book3', + 'book4', + 'book5', + 'book6', + 'book7', + 'book8', + 'book9' + ); + }); + + it('result equals works', async () => { + const ppl = firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('title')) + .limit(1); + const snapshot1 = await execute(ppl); + const snapshot2 = await execute(ppl); + expect(snapshot1.results.length).to.equal(1); + expect(snapshot2.results.length).to.equal(1); + expect(pipelineResultEqual(snapshot1.results[0], snapshot2.results[0])).to + .be.true; + }); + + it('returns execution time', async () => { + const start = new Date().valueOf(); + const pipeline = firestore.pipeline().collection(randomCol.path); + + const snapshot = await execute(pipeline); + const end = new Date().valueOf(); + + expect(snapshot.executionTime.toDate().valueOf()).to.approximately( + (start + end) / 2, + timestampDeltaMS + ); + }); + + it('returns execution time for an empty query', async () => { + const start = new Date().valueOf(); + const pipeline = firestore.pipeline().collection(randomCol.path).limit(0); + + const snapshot = await execute(pipeline); + const end = new Date().valueOf(); + + expect(snapshot.results.length).to.equal(0); + + expect(snapshot.executionTime.toDate().valueOf()).to.approximately( + (start + end) / 2, + timestampDeltaMS + ); + }); + + it('returns create and update time for each document', async () => { + const pipeline = firestore.pipeline().collection(randomCol.path); + + let snapshot = await execute(pipeline); + expect(snapshot.results.length).to.equal(10); + snapshot.results.forEach(doc => { + expect(doc.createTime).to.not.be.null; + expect(doc.updateTime).to.not.be.null; + + expect(doc.createTime!.toDate().valueOf()).to.approximately( + (beginDocCreation + endDocCreation) / 2, + timestampDeltaMS + ); + expect(doc.updateTime!.toDate().valueOf()).to.approximately( + (beginDocCreation + endDocCreation) / 2, + timestampDeltaMS + ); + expect(doc.createTime?.valueOf()).to.equal(doc.updateTime?.valueOf()); + }); + + const wb = writeBatch(firestore); + snapshot.results.forEach(doc => { + wb.update(doc.ref!, { newField: 'value' }); + }); + await wb.commit(); + + snapshot = await execute(pipeline); + expect(snapshot.results.length).to.equal(10); + snapshot.results.forEach(doc => { + expect(doc.createTime).to.not.be.null; + expect(doc.updateTime).to.not.be.null; + expect(doc.createTime!.toDate().valueOf()).to.be.lessThan( + doc.updateTime!.toDate().valueOf() + ); + }); + }); + + it('returns execution time for an aggregate query', async () => { + const start = new Date().valueOf(); + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .aggregate(average('rating').as('avgRating')); + + const snapshot = await execute(pipeline); + const end = new Date().valueOf(); + + expect(snapshot.results.length).to.equal(1); + + expect(snapshot.executionTime.toDate().valueOf()).to.approximately( + (start + end) / 2, + timestampDeltaMS + ); + }); + + it('returns undefined create and update time for each result in an aggregate query', async () => { + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .aggregate({ + accumulators: [average('rating').as('avgRating')], + groups: ['genre'] + }); + + const snapshot = await execute(pipeline); + + expect(snapshot.results.length).to.equal(8); + + snapshot.results.forEach(doc => { + expect(doc.updateTime).to.be.undefined; + expect(doc.createTime).to.be.undefined; + }); + }); + }); + + describe('pipeline sources', () => { + it('supports CollectionReference as source', async () => { + const snapshot = await execute( + firestore.pipeline().collection(randomCol) + ); + expect(snapshot.results.length).to.equal(10); + }); + + it('supports list of documents as source', async () => { + const collName = randomCol.id; + + const snapshot = await execute( + firestore + .pipeline() + .documents([ + `${collName}/book1`, + doc(randomCol, 'book2'), + doc(randomCol, 'book3').path + ]) + ); + expect(snapshot.results.length).to.equal(3); + }); + + it('reject CollectionReference for another DB', async () => { + const db2 = getFirestore(firestore.app, 'notDefault'); + + expect(() => { + firestore.pipeline().collection(collection(db2, 'foo')); + }).to.throw(/Invalid CollectionReference/); + + await terminate(db2); + }); + + it('reject DocumentReference for another DB', async () => { + const db2 = getFirestore(firestore.app, 'notDefault'); + + expect(() => { + firestore.pipeline().documents([doc(db2, 'foo/bar')]); + }).to.throw(/Invalid DocumentReference/); + + await terminate(db2); + }); + + it('supports collection group as source', async () => { + const randomSubCollectionId = Math.random().toString(16).slice(2); + const doc1 = await addDoc( + collection(randomCol, 'book1', randomSubCollectionId), + { order: 1 } + ); + const doc2 = await addDoc( + collection(randomCol, 'book2', randomSubCollectionId), + { order: 2 } + ); + const snapshot = await execute( + firestore + .pipeline() + .collectionGroup(randomSubCollectionId) + .sort(ascending('order')) + ); + expectResults(snapshot, doc1.id, doc2.id); + }); + + it('supports database as source', async () => { + const randomId = Math.random().toString(16).slice(2); + const doc1 = await addDoc(collection(randomCol, 'book1', 'sub'), { + order: 1, + randomId + }); + const doc2 = await addDoc(collection(randomCol, 'book2', 'sub'), { + order: 2, + randomId + }); + const snapshot = await execute( + firestore + .pipeline() + .database() + .where(equal('randomId', randomId)) + .sort(ascending('order')) + ); + expectResults(snapshot, doc1.id, doc2.id); + }); + }); + + describe('supported data types', () => { + it('accepts and returns all data types', async () => { + const refDate = new Date(); + const refTimestamp = Timestamp.now(); + const constants = [ + constant(1).as('number'), + constant('a string').as('string'), + constant(true).as('boolean'), + constant(null).as('null'), + constant(new GeoPoint(0.1, 0.2)).as('geoPoint'), + constant(refTimestamp).as('timestamp'), + constant(refDate).as('date'), + constant( + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])) + ).as('bytes'), + constant(doc(firestore, 'foo', 'bar')).as('documentReference'), + constant(vector([1, 2, 3])).as('vectorValue'), + map({ + 'number': 1, + 'string': 'a string', + 'boolean': true, + 'null': null, + 'geoPoint': new GeoPoint(0.1, 0.2), + 'timestamp': refTimestamp, + 'date': refDate, + 'uint8Array': Bytes.fromUint8Array( + new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]) + ), + 'documentReference': doc(firestore, 'foo', 'bar'), + 'vectorValue': vector([1, 2, 3]), + 'map': { + 'number': 2, + 'string': 'b string' + }, + 'array': [1, 'c string'] + }).as('map'), + array([ + 1, + 'a string', + true, + null, + new GeoPoint(0.1, 0.2), + refTimestamp, + refDate, + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])), + doc(firestore, 'foo', 'bar'), + vector([1, 2, 3]), + { + 'number': 2, + 'string': 'b string' + } + ]).as('array') + ]; + + const snapshots = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(constants[0], ...constants.slice(1)) + ); + + expectResults(snapshots, { + 'number': 1, + 'string': 'a string', + 'boolean': true, + 'null': null, + 'geoPoint': new GeoPoint(0.1, 0.2), + 'timestamp': refTimestamp, + 'date': Timestamp.fromDate(refDate), + 'bytes': Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])), + 'documentReference': doc(firestore, 'foo', 'bar'), + 'vectorValue': vector([1, 2, 3]), + 'map': { + 'number': 1, + 'string': 'a string', + 'boolean': true, + 'null': null, + 'geoPoint': new GeoPoint(0.1, 0.2), + 'timestamp': refTimestamp, + 'date': Timestamp.fromDate(refDate), + 'uint8Array': Bytes.fromUint8Array( + new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]) + ), + 'documentReference': doc(firestore, 'foo', 'bar'), + 'vectorValue': vector([1, 2, 3]), + 'map': { + 'number': 2, + 'string': 'b string' + }, + 'array': [1, 'c string'] + }, + 'array': [ + 1, + 'a string', + true, + null, + new GeoPoint(0.1, 0.2), + refTimestamp, + Timestamp.fromDate(refDate), + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])), + doc(firestore, 'foo', 'bar'), + vector([1, 2, 3]), + { + 'number': 2, + 'string': 'b string' + } + ] + }); + }); + + it('throws on undefined in a map', async () => { + expect(() => { + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + map({ + 'number': 1, + undefined + }).as('foo') + ); + }).to.throw( + 'Function map() called with invalid data. Unsupported field value: undefined' + ); + }); + + it('throws on undefined in an array', async () => { + expect(() => { + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(array([1, undefined]).as('foo')); + }).to.throw( + 'Function array() called with invalid data. Unsupported field value: undefined' + ); + }); + + it('converts arrays and plain objects to functionValues if the customer intent is unspecified', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + 'title', + 'author', + 'genre', + 'rating', + 'published', + 'tags', + 'awards' + ) + .addFields( + array([ + 1, + 2, + field('genre'), + multiply('rating', 10), + [field('title')], + { + published: field('published') + } + ]).as('metadataArray'), + map({ + genre: field('genre'), + rating: multiply('rating', 10), + nestedArray: [field('title')], + nestedMap: { + published: field('published') + } + }).as('metadata') + ) + .where( + and( + equal('metadataArray', [ + 1, + 2, + field('genre'), + multiply('rating', 10), + [field('title')], + { + published: field('published') + } + ]), + equal('metadata', { + genre: field('genre'), + rating: multiply('rating', 10), + nestedArray: [field('title')], + nestedMap: { + published: field('published') + } + }) + ) + ) + ); + + expect(snapshot.results.length).to.equal(1); + + expectResults(snapshot, { + title: 'The Lord of the Rings', + author: 'J.R.R. Tolkien', + genre: 'Fantasy', + published: 1954, + rating: 4.7, + tags: ['adventure', 'magic', 'epic'], + awards: { hugo: false, nebula: false }, + metadataArray: [ + 1, + 2, + 'Fantasy', + 47, + ['The Lord of the Rings'], + { + published: 1954 + } + ], + metadata: { + genre: 'Fantasy', + rating: 47, + nestedArray: ['The Lord of the Rings'], + nestedMap: { + published: 1954 + } + } + }); + }); + + it('supports boolean value constants as a BooleanExpression', async () => { + const snapshots = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + conditional(constant(true), constant('TRUE'), constant('FALSE')).as( + 'true' + ), + conditional( + constant(false), + constant('TRUE'), + constant('FALSE') + ).as('false') + ) + ); + + expectResults(snapshots, { + 'true': 'TRUE', + 'false': 'FALSE' + }); + }); + }); + + describe('stages', () => { + describe('aggregate stage', () => { + it('supports aggregate', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countAll().as('count')) + ); + expectResults(snapshot, { count: 10 }); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('genre', 'Science Fiction')) + .aggregate( + countAll().as('count'), + average('rating').as('avgRating'), + maximum('rating').as('maxRating'), + sum('rating').as('sumRating') + ) + ); + expectResults(snapshot, { + count: 2, + avgRating: 4.4, + maxRating: 4.6, + sumRating: 8.8 + }); + }); + + it('throws on Duplicate aliases', async () => { + expect(() => + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countAll().as('count'), count('foo').as('count')) + ).to.throw("Duplicate alias or field 'count'"); + }); + + it('throws on duplicate group aliases', async () => { + expect(() => + firestore + .pipeline() + .collection(randomCol.path) + .aggregate({ + accumulators: [countAll().as('count')], + groups: ['bax', field('bar').as('bax')] + }) + ).to.throw("Duplicate alias or field 'bax'"); + }); + + it('supports aggregate options', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate({ + accumulators: [countAll().as('count')] + }) + ); + expectResults(snapshot, { count: 10 }); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('genre', 'Science Fiction')) + .aggregate( + countAll().as('count'), + average('rating').as('avgRating'), + maximum('rating').as('maxRating'), + sum('rating').as('sumRating') + ) + ); + expectResults(snapshot, { + count: 2, + avgRating: 4.4, + maxRating: 4.6, + sumRating: 8.8 + }); + }); + + it('rejects groups without accumulators', async () => { + await expect( + execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(lessThan('published', 1900)) + .aggregate({ + accumulators: [], + groups: ['genre'] + }) + ) + ).to.be.rejected; + }); + + it('returns group and accumulate results', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(lessThan(field('published'), 1984)) + .aggregate({ + accumulators: [average('rating').as('avgRating')], + groups: ['genre'] + }) + .where(greaterThan('avgRating', 4.3)) + .sort(field('avgRating').descending()) + ); + expectResults( + snapshot, + { avgRating: 4.7, genre: 'Fantasy' }, + { avgRating: 4.5, genre: 'Romance' }, + { avgRating: 4.4, genre: 'Science Fiction' } + ); + }); + + it('returns min, max, count, and countAll accumulations', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate( + count('cost').as('booksWithCost'), + countAll().as('count'), + maximum('rating').as('maxRating'), + minimum('published').as('minPublished') + ) + ); + expectResults(snapshot, { + booksWithCost: 1, + count: 10, + maxRating: 4.7, + minPublished: 1813 + }); + }); + + it('returns countif accumulation', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countIf(field('rating').greaterThan(4.3)).as('count')) + ); + const expectedResults = { + count: 3 + }; + expectResults(snapshot, expectedResults); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(field('rating').greaterThan(4.3).countIf().as('count')) + ); + expectResults(snapshot, expectedResults); + }); + + it('returns countDistinct accumulation', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countDistinct('genre').as('distinctGenres')) + ); + expectResults(snapshot, { distinctGenres: 8 }); + }); + }); + + describe('distinct stage', () => { + it('returns distinct values as expected', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .distinct('genre', 'author') + .sort(field('genre').ascending(), field('author').ascending()) + ); + expectResults( + snapshot, + { genre: 'Dystopian', author: 'George Orwell' }, + { genre: 'Dystopian', author: 'Margaret Atwood' }, + { genre: 'Fantasy', author: 'J.R.R. Tolkien' }, + { genre: 'Magical Realism', author: 'Gabriel García Márquez' }, + { genre: 'Modernist', author: 'F. Scott Fitzgerald' }, + { genre: 'Psychological Thriller', author: 'Fyodor Dostoevsky' }, + { genre: 'Romance', author: 'Jane Austen' }, + { genre: 'Science Fiction', author: 'Douglas Adams' }, + { genre: 'Science Fiction', author: 'Frank Herbert' }, + { genre: 'Southern Gothic', author: 'Harper Lee' } + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .distinct('genre', 'author') + .sort({ + orderings: [ + field('genre').ascending(), + field('author').ascending() + ] + }) + ); + expectResults( + snapshot, + { genre: 'Dystopian', author: 'George Orwell' }, + { genre: 'Dystopian', author: 'Margaret Atwood' }, + { genre: 'Fantasy', author: 'J.R.R. Tolkien' }, + { genre: 'Magical Realism', author: 'Gabriel García Márquez' }, + { genre: 'Modernist', author: 'F. Scott Fitzgerald' }, + { genre: 'Psychological Thriller', author: 'Fyodor Dostoevsky' }, + { genre: 'Romance', author: 'Jane Austen' }, + { genre: 'Science Fiction', author: 'Douglas Adams' }, + { genre: 'Science Fiction', author: 'Frank Herbert' }, + { genre: 'Southern Gothic', author: 'Harper Lee' } + ); + }); + }); + + describe('select stage', () => { + it('can select fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .sort(field('author').ascending()) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams' + }, + { title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' }, + { title: 'Dune', author: 'Frank Herbert' }, + { title: 'Crime and Punishment', author: 'Fyodor Dostoevsky' }, + { + title: 'One Hundred Years of Solitude', + author: 'Gabriel García Márquez' + }, + { title: '1984', author: 'George Orwell' }, + { title: 'To Kill a Mockingbird', author: 'Harper Lee' }, + { title: 'The Lord of the Rings', author: 'J.R.R. Tolkien' }, + { title: 'Pride and Prejudice', author: 'Jane Austen' }, + { title: "The Handmaid's Tale", author: 'Margaret Atwood' } + ); + }); + + it('throws on Duplicate aliases', async () => { + expect(() => { + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(constant(1).as('foo'), constant(2).as('foo')); + }).to.throw("Duplicate alias or field 'foo'"); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select({ selections: ['title', field('author').as('auth0r')] }) + .sort(field('auth0r').ascending()) + .limit(2) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + auth0r: 'Douglas Adams' + }, + { title: 'The Great Gatsby', auth0r: 'F. Scott Fitzgerald' } + ); + }); + }); + + describe('addField stage', () => { + it('can add fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .addFields(constant('bar').as('foo')) + .sort(field('author').ascending()) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + foo: 'bar' + }, + { + title: 'The Great Gatsby', + author: 'F. Scott Fitzgerald', + foo: 'bar' + }, + { title: 'Dune', author: 'Frank Herbert', foo: 'bar' }, + { + title: 'Crime and Punishment', + author: 'Fyodor Dostoevsky', + foo: 'bar' + }, + { + title: 'One Hundred Years of Solitude', + author: 'Gabriel García Márquez', + foo: 'bar' + }, + { title: '1984', author: 'George Orwell', foo: 'bar' }, + { + title: 'To Kill a Mockingbird', + author: 'Harper Lee', + foo: 'bar' + }, + { + title: 'The Lord of the Rings', + author: 'J.R.R. Tolkien', + foo: 'bar' + }, + { title: 'Pride and Prejudice', author: 'Jane Austen', foo: 'bar' }, + { + title: "The Handmaid's Tale", + author: 'Margaret Atwood', + foo: 'bar' + } + ); + }); + + it('throws on Duplicate aliases', async () => { + expect(() => + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .addFields(constant('bar').as('foo'), constant('baz').as('foo')) + .sort(field('author').ascending()) + ).to.throw("Duplicate alias or field 'foo'"); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .addFields({ + fields: [constant('bar').as('foo')] + }) + .sort(field('author').ascending()) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + foo: 'bar' + }, + { + title: 'The Great Gatsby', + author: 'F. Scott Fitzgerald', + foo: 'bar' + }, + { title: 'Dune', author: 'Frank Herbert', foo: 'bar' }, + { + title: 'Crime and Punishment', + author: 'Fyodor Dostoevsky', + foo: 'bar' + }, + { + title: 'One Hundred Years of Solitude', + author: 'Gabriel García Márquez', + foo: 'bar' + }, + { title: '1984', author: 'George Orwell', foo: 'bar' }, + { + title: 'To Kill a Mockingbird', + author: 'Harper Lee', + foo: 'bar' + }, + { + title: 'The Lord of the Rings', + author: 'J.R.R. Tolkien', + foo: 'bar' + }, + { title: 'Pride and Prejudice', author: 'Jane Austen', foo: 'bar' }, + { + title: "The Handmaid's Tale", + author: 'Margaret Atwood', + foo: 'bar' + } + ); + }); + }); + + describe('removeFields stage', () => { + it('can remove fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .sort(field('author').ascending()) + .removeFields(field('author')) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + title: 'The Great Gatsby' + }, + { title: 'Dune' }, + { + title: 'Crime and Punishment' + }, + { + title: 'One Hundred Years of Solitude' + }, + { title: '1984' }, + { + title: 'To Kill a Mockingbird' + }, + { + title: 'The Lord of the Rings' + }, + { title: 'Pride and Prejudice' }, + { + title: "The Handmaid's Tale" + } + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'genre') + .sort(field('author').ascending()) + .removeFields({ + fields: [field('author'), 'genre'] + }) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + title: 'The Great Gatsby' + }, + { title: 'Dune' }, + { + title: 'Crime and Punishment' + }, + { + title: 'One Hundred Years of Solitude' + }, + { title: '1984' }, + { + title: 'To Kill a Mockingbird' + }, + { + title: 'The Lord of the Rings' + }, + { title: 'Pride and Prejudice' }, + { + title: "The Handmaid's Tale" + } + ); + }); + }); + + describe('findNearest stage', () => { + it('can find nearest', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .sort(field('author').ascending()) + .removeFields(field('author')) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + title: 'The Great Gatsby' + }, + { title: 'Dune' }, + { + title: 'Crime and Punishment' + }, + { + title: 'One Hundred Years of Solitude' + }, + { title: '1984' }, + { + title: 'To Kill a Mockingbird' + }, + { + title: 'The Lord of the Rings' + }, + { title: 'Pride and Prejudice' }, + { + title: "The Handmaid's Tale" + } + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'genre') + .sort(field('author').ascending()) + .removeFields({ + fields: [field('author'), 'genre'] + }) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + title: 'The Great Gatsby' + }, + { title: 'Dune' }, + { + title: 'Crime and Punishment' + }, + { + title: 'One Hundred Years of Solitude' + }, + { title: '1984' }, + { + title: 'To Kill a Mockingbird' + }, + { + title: 'The Lord of the Rings' + }, + { title: 'Pride and Prejudice' }, + { + title: "The Handmaid's Tale" + } + ); + }); + }); + + describe('where stage', () => { + it('where with and (2 conditions)', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + and( + greaterThan('rating', 4.5), + equalAny('genre', ['Science Fiction', 'Romance', 'Fantasy']) + ) + ) + ); + expectResults(snapshot, 'book10', 'book4'); + }); + + it('where with and (3 conditions)', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + and( + greaterThan('rating', 4.5), + equalAny('genre', ['Science Fiction', 'Romance', 'Fantasy']), + lessThan('published', 1965) + ) + ) + ); + expectResults(snapshot, 'book4'); + }); + + it('where with or', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + or( + equal('genre', 'Romance'), + equal('genre', 'Dystopian'), + equal('genre', 'Fantasy') + ) + ) + .sort(ascending('title')) + .select('title') + ); + expectResults( + snapshot, + { title: '1984' }, + { title: 'Pride and Prejudice' }, + { title: "The Handmaid's Tale" }, + { title: 'The Lord of the Rings' } + ); + }); + + it('where with xor', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + xor( + equal('genre', 'Romance'), + equal('genre', 'Dystopian'), + equal('genre', 'Fantasy'), + equal('published', 1949) + ) + ) + .select('title') + ); + expectResults( + snapshot, + { title: 'Pride and Prejudice' }, + { title: 'The Lord of the Rings' }, + { title: "The Handmaid's Tale" } + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where({ + condition: and( + greaterThan('rating', 4.5), + equalAny('genre', ['Science Fiction', 'Romance', 'Fantasy']) + ) + }) + ); + expectResults(snapshot, 'book10', 'book4'); + }); + }); + + describe('sort, offset, and limit stages', () => { + it('supports sort, offset, and limits', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('author').ascending()) + .offset(5) + .limit(3) + .select('title', 'author') + ); + expectResults( + snapshot, + { title: '1984', author: 'George Orwell' }, + { title: 'To Kill a Mockingbird', author: 'Harper Lee' }, + { title: 'The Lord of the Rings', author: 'J.R.R. Tolkien' } + ); + }); + + it('sort, offset, and limit stages support options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort({ + orderings: [field('author').ascending()] + }) + .offset({ offset: 5 }) + .limit({ limit: 3 }) + .select('title', 'author') + ); + expectResults( + snapshot, + { title: '1984', author: 'George Orwell' }, + { title: 'To Kill a Mockingbird', author: 'Harper Lee' }, + { title: 'The Lord of the Rings', author: 'J.R.R. Tolkien' } + ); + }); + }); + + describe('raw stage', () => { + it('can select fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .rawStage('select', [ + { + title: field('title'), + metadata: { + author: field('author') + } + } + ]) + .sort(field('author').ascending()) + .limit(1) + ); + expectResults(snapshot, { + metadata: { + author: 'Frank Herbert' + }, + title: 'Dune' + }); + }); + + it('can add fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('author').ascending()) + .limit(1) + .select('title', 'author') + .rawStage('add_fields', [ + { + display: stringConcat('title', ' - ', field('author')) + } + ]) + ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + display: "The Hitchhiker's Guide to the Galaxy - Douglas Adams" + }); + }); + + it('can filter with where', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .rawStage('where', [field('author').equal('Douglas Adams')]) + ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams' + }); + }); + + it('can limit, offset, and sort', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .rawStage('sort', [ + { + direction: 'ascending', + expression: field('author') + } + ]) + .rawStage('offset', [3]) + .rawStage('limit', [1]) + ); + expectResults(snapshot, { + author: 'Fyodor Dostoevsky', + title: 'Crime and Punishment' + }); + }); + + it('can perform aggregate query', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'rating') + .rawStage('aggregate', [ + { averageRating: field('rating').average() }, + {} + ]) + ); + expectResults(snapshot, { + averageRating: 4.3100000000000005 + }); + }); + + it('can perform distinct query', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'rating') + .rawStage('distinct', [{ rating: field('rating') }]) + .sort(field('rating').descending()) + ); + expectResults( + snapshot, + { + rating: 4.7 + }, + { + rating: 4.6 + }, + { + rating: 4.5 + }, + { + rating: 4.3 + }, + { + rating: 4.2 + }, + { + rating: 4.1 + }, + { + rating: 4.0 + } + ); + }); + + it('can perform FindNearest query', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol) + .rawStage( + 'find_nearest', + [ + field('embedding'), + vector([10, 1, 2, 1, 1, 1, 1, 1, 1, 1]), + 'euclidean' + ], + { + 'distance_field': field('computedDistance'), + limit: 2 + } + ) + .select('title', 'computedDistance') + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + computedDistance: 1 + }, + { + title: 'One Hundred Years of Solitude', + computedDistance: 12.041594578792296 + } + ); + }); + }); + + describe('replaceWith stage', () => { + it('run pipeline with replaceWith field name', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .replaceWith('awards') + ); + expectResults(snapshot, { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }); + }); + + it('run pipeline with replaceWith Expr result', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .replaceWith( + map({ + foo: 'bar', + baz: { + title: field('title') + } + }) + ) + ); + expectResults(snapshot, { + foo: 'bar', + baz: { title: "The Hitchhiker's Guide to the Galaxy" } + }); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .replaceWith({ map: 'awards' }) + ); + expectResults(snapshot, { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }); + }); + }); + + describe('sample stage', () => { + it('run pipeline with sample limit of 3', async () => { + const snapshot = await execute( + firestore.pipeline().collection(randomCol.path).sample(3) + ); + expect(snapshot.results.length).to.equal(3); + }); + + it('run pipeline with sample limit of {documents: 3}', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sample({ documents: 3 }) + ); + expect(snapshot.results.length).to.equal(3); + }); + + it('run pipeline with sample limit of {percentage: 0.6}', async () => { + let avgSize = 0; + const numIterations = 30; + for (let i = 0; i < numIterations; i++) { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sample({ percentage: 0.6 }) + ); + + avgSize += snapshot.results.length; + } + avgSize /= numIterations; + expect(avgSize).to.be.closeTo(6, 1); + }); + }); + + describe('union stage', () => { + it('run pipeline with union', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .union(firestore.pipeline().collection(randomCol.path)) + .sort(field(documentIdFieldPath()).ascending()) + ); + expectResults( + snapshot, + 'book1', + 'book1', + 'book10', + 'book10', + 'book2', + 'book2', + 'book3', + 'book3', + 'book4', + 'book4', + 'book5', + 'book5', + 'book6', + 'book6', + 'book7', + 'book7', + 'book8', + 'book8', + 'book9', + 'book9' + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .union({ other: firestore.pipeline().collection(randomCol.path) }) + .sort(field(documentIdFieldPath()).ascending()) + ); + expectResults( + snapshot, + 'book1', + 'book1', + 'book10', + 'book10', + 'book2', + 'book2', + 'book3', + 'book3', + 'book4', + 'book4', + 'book5', + 'book5', + 'book6', + 'book6', + 'book7', + 'book7', + 'book8', + 'book8', + 'book9', + 'book9' + ); + }); + }); + + describe('unnest stage', () => { + it('run pipeline with unnest', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .unnest(field('tags').as('tag')) + .select( + 'title', + 'author', + 'genre', + 'published', + 'rating', + 'tags', + 'tag', + 'awards', + 'nestedField' + ) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'comedy', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'space', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'adventure', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + } + ); + }); + + it('unnest with index field', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .unnest(field('tags').as('tag'), 'tagsIndex') + .select( + 'title', + 'author', + 'genre', + 'published', + 'rating', + 'tags', + 'tag', + 'awards', + 'nestedField', + 'tagsIndex' + ) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'comedy', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 0 + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'space', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 1 + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'adventure', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 2 + } + ); + }); + + it('unnest an expr', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .unnest(array([1, 2, 3]).as('copy')) + .select( + 'title', + 'author', + 'genre', + 'published', + 'rating', + 'tags', + 'copy', + 'awards', + 'nestedField' + ) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + copy: 1, + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + copy: 2, + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + copy: 3, + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + } + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .unnest({ + selectable: field('tags').as('tag'), + indexField: 'tagsIndex' + }) + .select( + 'title', + 'author', + 'genre', + 'published', + 'rating', + 'tags', + 'tag', + 'awards', + 'nestedField', + 'tagsIndex' + ) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'comedy', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 0 + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'space', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 1 + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'adventure', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 2 + } + ); + }); + }); + + describe('findNearest stage', () => { + it('run pipeline with findNearest', async () => { + const measures: Array = [ + 'euclidean', + 'dot_product', + 'cosine' + ]; + for (const measure of measures) { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol) + .findNearest({ + field: 'embedding', + vectorValue: vector([10, 1, 3, 1, 2, 1, 1, 1, 1, 1]), + limit: 3, + distanceMeasure: measure + }) + .select('title') + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + title: 'One Hundred Years of Solitude' + }, + { + title: "The Handmaid's Tale" + } + ); + } + }); + + it('optionally returns the computed distance', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol) + .findNearest({ + field: 'embedding', + vectorValue: vector([10, 1, 2, 1, 1, 1, 1, 1, 1, 1]), + limit: 2, + distanceMeasure: 'euclidean', + distanceField: 'computedDistance' + }) + .select('title', 'computedDistance') + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + computedDistance: 1 + }, + { + title: 'One Hundred Years of Solitude', + computedDistance: 12.041594578792296 + } + ); + }); + }); + }); + + describe('error handling', () => { + it('error properties are propagated from the firestore backend', async () => { + try { + const myPipeline = firestore + .pipeline() + .collection(randomCol.path) + .rawStage('select', [ + // incorrect parameter type + field('title') + ]); + + await execute(myPipeline); + + expect.fail('expected pipeline.execute() to throw'); + } catch (e: unknown) { + expect(e instanceof FirebaseError).to.be.true; + const err = e as FirebaseError; + expect(err['code']).to.equal('invalid-argument'); + expect(typeof err['message']).to.equal('string'); + + expect(err['message']).to.match(/^3 INVALID_ARGUMENT: .*$/); + } + }); + }); + + describe('function expressions', () => { + it('logical max works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + 'title', + logicalMaximum(constant(1960), field('published'), 1961).as( + 'published-safe' + ) + ) + .sort(field('title').ascending()) + .limit(3) + ); + expectResults( + snapshot, + { title: '1984', 'published-safe': 1961 }, + { title: 'Crime and Punishment', 'published-safe': 1961 }, + { title: 'Dune', 'published-safe': 1965 } + ); + }); + + it('logical min works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + 'title', + logicalMinimum(constant(1960), field('published'), 1961).as( + 'published-safe' + ) + ) + .sort(field('title').ascending()) + .limit(3) + ); + expectResults( + snapshot, + { title: '1984', 'published-safe': 1949 }, + { title: 'Crime and Punishment', 'published-safe': 1866 }, + { title: 'Dune', 'published-safe': 1960 } + ); + }); + + it('conditional works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + 'title', + conditional( + lessThan(field('published'), 1960), + constant(1960), + field('published') + ).as('published-safe'), + field('rating') + .greaterThanOrEqual(4.5) + .conditional(constant('great'), constant('good')) + .as('rating') + ) + .sort(field('title').ascending()) + .limit(3) + ); + expectResults( + snapshot, + { title: '1984', 'published-safe': 1960, rating: 'good' }, + { + title: 'Crime and Punishment', + 'published-safe': 1960, + rating: 'good' + }, + { title: 'Dune', 'published-safe': 1965, rating: 'great' } + ); + }); + + it('equalAny works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equalAny('published', [1979, 1999, 1967])) + .sort(descending('title')) + .select('title') + ); + expectResults( + snapshot, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'One Hundred Years of Solitude' } + ); + }); + + it('notEqualAny works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + notEqualAny( + 'published', + [1965, 1925, 1949, 1960, 1866, 1985, 1954, 1967, 1979] + ) + ) + .select('title') + ); + expectResults(snapshot, { title: 'Pride and Prejudice' }); + }); + + it('arrayContains works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(arrayContains('tags', 'comedy')) + .select('title') + ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy" + }); + }); + + it('arrayContainsAny works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(arrayContainsAny('tags', ['comedy', 'classic'])) + .sort(descending('title')) + .select('title') + ); + expectResults( + snapshot, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'Pride and Prejudice' } + ); + }); + + it('arrayContainsAll works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(arrayContainsAll('tags', ['adventure', 'magic'])) + .select('title') + ); + expectResults(snapshot, { title: 'The Lord of the Rings' }); + }); + + it('arrayLength works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select(arrayLength('tags').as('tagsCount')) + .where(equal('tagsCount', 3)) + ); + expect(snapshot.results.length).to.equal(10); + }); + + it('testStrConcat', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('author')) + .select( + field('author').stringConcat(' - ', field('title')).as('bookInfo') + ) + .limit(1) + ); + expectResults(snapshot, { + bookInfo: "Douglas Adams - The Hitchhiker's Guide to the Galaxy" + }); + }); + + it('testStartsWith', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(startsWith('title', 'The')) + .select('title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { title: 'The Great Gatsby' }, + { title: "The Handmaid's Tale" }, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'The Lord of the Rings' } + ); + }); + + it('testEndsWith', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(endsWith('title', 'y')) + .select('title') + .sort(field('title').descending()) + ); + expectResults( + snapshot, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'The Great Gatsby' } + ); + }); + + it('testStrContains', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(stringContains('title', "'s")) + .select('title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { title: "The Handmaid's Tale" }, + { title: "The Hitchhiker's Guide to the Galaxy" } + ); + }); + + it('testLength', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select(charLength('title').as('titleLength'), field('title')) + .where(greaterThan('titleLength', 20)) + .sort(field('title').ascending()) + ); + + expectResults( + snapshot, + + { + titleLength: 29, + title: 'One Hundred Years of Solitude' + }, + { + titleLength: 36, + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + titleLength: 21, + title: 'The Lord of the Rings' + }, + { + titleLength: 21, + title: 'To Kill a Mockingbird' + } + ); + }); + + it('testLike', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(like('title', '%Guide%')) + .select('title') + ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy" + }); + }); + + it('testRegexContains', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(regexContains('title', '(?i)(the|of)')) + ); + expect(snapshot.results.length).to.equal(5); + }); + + it('testRegexMatches', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(regexMatch('title', '.*(?i)(the|of).*')) + ); + expect(snapshot.results.length).to.equal(5); + }); + + it('testArithmeticOperations', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', 'To Kill a Mockingbird')) + .select( + add(field('rating'), 1).as('ratingPlusOne'), + subtract(field('published'), 1900).as('yearsSince1900'), + field('rating').multiply(10).as('ratingTimesTen'), + divide('rating', 2).as('ratingDividedByTwo'), + multiply('rating', 20).as('ratingTimes20'), + add('rating', 3).as('ratingPlus3'), + mod('rating', 2).as('ratingMod2') + ) + .limit(1) + ); + expectResults(snapshot, { + ratingPlusOne: 5.2, + yearsSince1900: 60, + ratingTimesTen: 42, + ratingDividedByTwo: 2.1, + ratingTimes20: 84, + ratingPlus3: 7.2, + ratingMod2: 0.20000000000000018 + }); + }); + + it('testComparisonOperators', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + and( + greaterThan('rating', 4.2), + lessThanOrEqual(field('rating'), 4.5), + notEqual('genre', 'Science Fiction') + ) + ) + .select('rating', 'title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { rating: 4.3, title: 'Crime and Punishment' }, + { + rating: 4.3, + title: 'One Hundred Years of Solitude' + }, + { rating: 4.5, title: 'Pride and Prejudice' } + ); + }); + + it('testLogicalOperators', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + or( + and( + greaterThan('rating', 4.5), + equal('genre', 'Science Fiction') + ), + lessThan('published', 1900) + ) + ) + .select('title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { title: 'Crime and Punishment' }, + { title: 'Dune' }, + { title: 'Pride and Prejudice' } + ); + }); + + it('testChecks', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + equal('rating', null).as('ratingIsNull'), + equal('rating', NaN).as('ratingIsNaN'), + isError(divide(constant(1), constant(0))).as('isError'), + ifError(divide(constant(1), constant(0)), constant('was error')).as( + 'ifError' + ), + ifError( + divide(constant(1), constant(0)).greaterThan(1), + constant(true) + ) + .not() + .as('ifErrorBooleanExpression'), + isAbsent('foo').as('isAbsent'), + notEqual('title', null).as('titleIsNotNull'), + notEqual('cost', NaN).as('costIsNotNan'), + exists('fooBarBaz').as('fooBarBazExists'), + field('title').exists().as('titleExists') + ) + ); + expectResults(snapshot, { + ratingIsNull: false, + ratingIsNaN: false, + isError: true, + ifError: 'was error', + ifErrorBooleanExpression: false, + isAbsent: true, + titleIsNotNull: true, + costIsNotNan: false, + fooBarBazExists: false, + titleExists: true + }); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + field('rating').equal(null).as('ratingIsNull'), + field('rating').equal(NaN).as('ratingIsNaN'), + divide(constant(1), constant(0)).isError().as('isError'), + divide(constant(1), constant(0)) + .ifError(constant('was error')) + .as('ifError'), + divide(constant(1), constant(0)) + .greaterThan(1) + .ifError(constant(true)) + .not() + .as('ifErrorBooleanExpression'), + field('foo').isAbsent().as('isAbsent'), + field('title').notEqual(null).as('titleIsNotNull'), + field('cost').notEqual(NaN).as('costIsNotNan') + ) + ); + expectResults(snapshot, { + ratingIsNull: false, + ratingIsNaN: false, + isError: true, + ifError: 'was error', + ifErrorBooleanExpression: false, + isAbsent: true, + titleIsNotNull: true, + costIsNotNan: false + }); + }); + + it('testMapGet', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('published').descending()) + .select( + field('awards').mapGet('hugo').as('hugoAward'), + field('awards').mapGet('others').as('others'), + field('title') + ) + .where(equal('hugoAward', true)) + ); + expectResults( + snapshot, + { + hugoAward: true, + title: "The Hitchhiker's Guide to the Galaxy", + others: { unknown: { year: 1980 } } + }, + { hugoAward: true, title: 'Dune' } + ); + }); + + it('testDistanceFunctions', async () => { + const sourceVector = vector([0.1, 0.1]); + const targetVector = vector([0.5, 0.8]); + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + cosineDistance(constant(sourceVector), targetVector).as( + 'cosineDistance' + ), + dotProduct(constant(sourceVector), targetVector).as( + 'dotProductDistance' + ), + euclideanDistance(constant(sourceVector), targetVector).as( + 'euclideanDistance' + ) + ) + .limit(1) + ); + + expectResults(snapshot, { + cosineDistance: 0.02560880430538015, + dotProductDistance: 0.13, + euclideanDistance: 0.806225774829855 + }); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + constant(sourceVector) + .cosineDistance(targetVector) + .as('cosineDistance'), + constant(sourceVector) + .dotProduct(targetVector) + .as('dotProductDistance'), + constant(sourceVector) + .euclideanDistance(targetVector) + .as('euclideanDistance') + ) + .limit(1) + ); + + expectResults(snapshot, { + cosineDistance: 0.02560880430538015, + dotProductDistance: 0.13, + euclideanDistance: 0.806225774829855 + }); + }); + + it('testVectorLength', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(vectorLength(constant(vector([1, 2, 3]))).as('vectorLength')) + ); + expectResults(snapshot, { + vectorLength: 3 + }); + }); + + it('testNestedFields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('awards.hugo', true)) + .sort(descending('title')) + .select('title', 'awards.hugo') + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + 'awards.hugo': true + }, + { title: 'Dune', 'awards.hugo': true } + ); + }); + + it('test mapGet with field name including . notation', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .replaceWith( + map({ + title: 'foo', + nested: { + level: { + '1': 'bar' + }, + 'level.1': { + 'level.2': 'baz' + } + } + }) + ) + .select( + 'title', + field('nested.level.1'), + mapGet('nested', 'level.1').mapGet('level.2').as('nested') + ) + ); + expectResults(snapshot, { + title: 'foo', + 'nested.level.`1`': 'bar', + nested: 'baz' + }); + }); + + describe('rawFunction', () => { + it('add selectable', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(descending('rating')) + .limit(1) + .select( + new FunctionExpression('add', [field('rating'), constant(1)]).as( + 'rating' + ) + ) + ); + expectResults(snapshot, { + rating: 5.7 + }); + }); + + it('and (variadic) selectable', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + new FunctionExpression('and', [ + field('rating').greaterThan(0), + field('title').charLength().lessThan(5), + field('tags').arrayContains('propaganda') + ]).asBoolean() + ) + .select('title') + ); + expectResults(snapshot, { + title: '1984' + }); + }); + + it('array contains any', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + new FunctionExpression('array_contains_any', [ + field('tags'), + array(['politics']) + ]).asBoolean() + ) + .select('title') + ); + expectResults(snapshot, { + title: 'Dune' + }); + }); + + it('countif aggregate', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate( + new AggregateFunction('count_if', [ + field('rating').greaterThanOrEqual(4.5) + ]).as('countOfBest') + ) + ); + expectResults(snapshot, { + countOfBest: 3 + }); + }); + + it('sort by char_len', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort( + new FunctionExpression('char_length', [ + field('title') + ]).ascending(), + descending('__name__') + ) + .limit(3) + .select('title') + ); + expectResults( + snapshot, + { + title: '1984' + }, + { + title: 'Dune' + }, + { + title: 'The Great Gatsby' + } + ); + }); + }); + + it('supports array', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(array([1, 2, 3, 4]).as('metadata')) + ); + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: [1, 2, 3, 4] + }); + }); + + it('evaluates expression in array', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + array([1, 2, field('genre'), multiply('rating', 10)]).as('metadata') + ) + ); + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: [1, 2, 'Fantasy', 47] + }); + }); + + it('supports arrayGet', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(3) + .select(arrayGet('tags', 0).as('firstTag')) + ); + const expectedResults = [ + { + firstTag: 'adventure' + }, + { + firstTag: 'politics' + }, + { + firstTag: 'classic' + } + ]; + expectResults(snapshot, ...expectedResults); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(3) + .select(field('tags').arrayGet(0).as('firstTag')) + ); + expectResults(snapshot, ...expectedResults); + }); + + it('supports map', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + map({ + foo: 'bar' + }).as('metadata') + ) + ); + + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: { + foo: 'bar' + } + }); + }); + + it('evaluates expression in map', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + map({ + genre: field('genre'), + rating: field('rating').multiply(10) + }).as('metadata') + ) + ); + + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: { + genre: 'Fantasy', + rating: 47 + } + }); + }); + + it('supports mapRemove', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(mapRemove('awards', 'hugo').as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false } + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('awards').mapRemove('hugo').as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false } + }); + }); + + it('supports mapMerge', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(mapMerge('awards', { fakeAward: true }).as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false, hugo: false, fakeAward: true } + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('awards').mapMerge({ fakeAward: true }).as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false, hugo: false, fakeAward: true } + }); + }); + + it('supports timestamp conversions', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + unixSecondsToTimestamp(constant(1741380235)).as( + 'unixSecondsToTimestamp' + ), + unixMillisToTimestamp(constant(1741380235123)).as( + 'unixMillisToTimestamp' + ), + unixMicrosToTimestamp(constant(1741380235123456)).as( + 'unixMicrosToTimestamp' + ), + timestampToUnixSeconds( + constant(new Timestamp(1741380235, 123456789)) + ).as('timestampToUnixSeconds'), + timestampToUnixMicros( + constant(new Timestamp(1741380235, 123456789)) + ).as('timestampToUnixMicros'), + timestampToUnixMillis( + constant(new Timestamp(1741380235, 123456789)) + ).as('timestampToUnixMillis') + ) + ); + expectResults(snapshot, { + unixMicrosToTimestamp: new Timestamp(1741380235, 123456000), + unixMillisToTimestamp: new Timestamp(1741380235, 123000000), + unixSecondsToTimestamp: new Timestamp(1741380235, 0), + timestampToUnixSeconds: 1741380235, + timestampToUnixMicros: 1741380235123456, + timestampToUnixMillis: 1741380235123 + }); + }); + + it('supports timestamp math', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(constant(new Timestamp(1741380235, 0)).as('timestamp')) + .select( + timestampAdd('timestamp', 'day', 10).as('plus10days'), + timestampAdd('timestamp', 'hour', 10).as('plus10hours'), + timestampAdd('timestamp', 'minute', 10).as('plus10minutes'), + timestampAdd('timestamp', 'second', 10).as('plus10seconds'), + timestampAdd('timestamp', 'microsecond', 10).as('plus10micros'), + timestampAdd('timestamp', 'millisecond', 10).as('plus10millis'), + timestampSubtract('timestamp', 'day', 10).as('minus10days'), + timestampSubtract('timestamp', 'hour', 10).as('minus10hours'), + timestampSubtract('timestamp', 'minute', 10).as('minus10minutes'), + timestampSubtract('timestamp', 'second', 10).as('minus10seconds'), + timestampSubtract('timestamp', 'microsecond', 10).as( + 'minus10micros' + ), + timestampSubtract('timestamp', 'millisecond', 10).as( + 'minus10millis' + ) + ) + ); + expectResults(snapshot, { + plus10days: new Timestamp(1742244235, 0), + plus10hours: new Timestamp(1741416235, 0), + plus10minutes: new Timestamp(1741380835, 0), + plus10seconds: new Timestamp(1741380245, 0), + plus10micros: new Timestamp(1741380235, 10000), + plus10millis: new Timestamp(1741380235, 10000000), + minus10days: new Timestamp(1740516235, 0), + minus10hours: new Timestamp(1741344235, 0), + minus10minutes: new Timestamp(1741379635, 0), + minus10seconds: new Timestamp(1741380225, 0), + minus10micros: new Timestamp(1741380234, 999990000), + minus10millis: new Timestamp(1741380234, 990000000) + }); + }).timeout(10000); + + it('supports byteLength', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol) + .limit(1) + .select( + constant( + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])) + ).as('bytes') + ) + .select(byteLength('bytes').as('byteLength')) + ); + + expectResults(snapshot, { + byteLength: 8 + }); + }); + + it('supports not', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol) + .limit(1) + .select(constant(true).as('trueField')) + .select('trueField', not(equal('trueField', true)).as('falseField')) + ); + + expectResults(snapshot, { + trueField: true, + falseField: false + }); + }); + + it('can reverse an array', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('tags').arrayReverse().as('reversedTags')) + ); + expectResults(snapshot, { + reversedTags: ['adventure', 'space', 'comedy'] + }); + }); + + it('can reverse an array with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(reverse('tags').as('reversedTags')) + ); + expectResults(snapshot, { + reversedTags: ['adventure', 'space', 'comedy'] + }); + }); + + it('can compute the ceiling of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').ceil().as('ceilingRating')) + ); + expectResults(snapshot, { + ceilingRating: 5 + }); + }); + + it('can compute the ceiling of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(ceil('rating').as('ceilingRating')) + ); + expectResults(snapshot, { + ceilingRating: 5 + }); + }); + + it('can compute the floor of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').floor().as('floorRating')) + ); + expectResults(snapshot, { + floorRating: 4 + }); + }); + + it('can compute the floor of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(floor('rating').as('floorRating')) + ); + expectResults(snapshot, { + floorRating: 4 + }); + }); + + it('can compute e to the power of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(field('rating').exp().as('expRating')) + ); + expectResults(snapshot, { + expRating: 109.94717245212352 + }); + }); + + it('can compute e to the power of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(exp('rating').as('expRating')) + ); + expect(snapshot.results[0].get('expRating')).to.be.approximately( + 109.94717245212351, + 0.000001 + ); + }); + + it('can compute the power of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').pow(2).as('powerRating')) + ); + expect(snapshot.results[0].get('powerRating')).to.be.approximately( + 17.64, + 0.0001 + ); + }); + + it('can compute the power of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(pow('rating', 2).as('powerRating')) + ); + expect(snapshot.results[0].get('powerRating')).to.be.approximately( + 17.64, + 0.0001 + ); + }); + + it('can round a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').round().as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: 4 + }); + }); + + it('can round a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(round('rating').as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: 4 + }); + }); + + it('can round a numeric value away from zero for positive half-way values', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .addFields(constant(1.5).as('positiveHalf')) + .select(field('positiveHalf').round().as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: 2 + }); + }); + + it('can round a numeric value away from zero for negative half-way values', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .addFields(constant(-1.5).as('negativeHalf')) + .select(field('negativeHalf').round().as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: -2 + }); + }); + + it('can round a numeric value to specified precision', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .replaceWith( + map({ + foo: 4.123456 + }) + ) + .select( + field('foo').round(0).as('0'), + round('foo', 1).as('1'), + round('foo', constant(2)).as('2'), + round(field('foo'), 4).as('4') + ) + ); + expectResults(snapshot, { + '0': 4, + '1': 4.1, + '2': 4.12, + '4': 4.1235 + }); + }); + + it('can get the collectionId from a path', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(field('__name__').collectionId().as('collectionId')) + ); + expectResults(snapshot, { + collectionId: randomCol.id + }); + }); + + it('can get the collectionId from a path with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(collectionId('__name__').as('collectionId')) + ); + expectResults(snapshot, { + collectionId: randomCol.id + }); + }); + + it('can compute the length of a string value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('title').length().as('titleLength')) + ); + expectResults(snapshot, { + titleLength: 36 + }); + }); + + it('can compute the length of a string value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(length('title').as('titleLength')) + ); + expectResults(snapshot, { + titleLength: 36 + }); + }); + + it('can compute the length of an array value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('tags').length().as('tagsLength')) + ); + expectResults(snapshot, { + tagsLength: 3 + }); + }); + + it('can compute the length of an array value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(length('tags').as('tagsLength')) + ); + expectResults(snapshot, { + tagsLength: 3 + }); + }); + + it('can compute the length of a map value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('awards').length().as('awardsLength')) + ); + expectResults(snapshot, { + awardsLength: 3 + }); + }); + + it('can compute the length of a vector value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('embedding').length().as('embeddingLength')) + ); + expectResults(snapshot, { + embeddingLength: 10 + }); + }); + + it('can compute the length of a bytes value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select(constant('12é').as('value')) + .limit(1) + .select(field('value').byteLength().as('valueLength')) + ); + expectResults(snapshot, { + valueLength: 4 + }); + }); + + it('can compute the natural logarithm of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').ln().as('lnRating')) + ); + expect(snapshot.results[0]!.data().lnRating).to.be.closeTo(1.435, 0.001); + }); + + it('can compute the natural logarithm of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(ln('rating').as('lnRating')) + ); + expect(snapshot.results[0]!.data().lnRating).to.be.closeTo(1.435, 0.001); + }); + + it('can compute the natural logarithm of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(ln('rating').as('lnRating')) + ); + expectResults(snapshot, { + lnRating: 1.4350845252893227 + }); + }); + + it('can compute the logarithm of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(log(field('rating'), 10).as('logRating')) + ); + expectResults(snapshot, { + logRating: 0.6232492903979004 + }); + }); + + it('can compute the logarithm of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(log('rating', 10).as('logRating')) + ); + expectResults(snapshot, { + logRating: 0.6232492903979004 + }); + }); + + it('can round a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').round().as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: 4 + }); + }); + + it('can round a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(round('rating').as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: 4 + }); + }); + + it('can compute the square root of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').sqrt().as('sqrtRating')) + ); + expectResults(snapshot, { + sqrtRating: 2.04939015319192 + }); + }); + + it('can compute the square root of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(sqrt('rating').as('sqrtRating')) + ); + expectResults(snapshot, { + sqrtRating: 2.04939015319192 + }); + }); + + it('can reverse a string', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('title').reverse().as('reversedTitle')) + ); + expectResults(snapshot, { + reversedTitle: "yxalaG eht ot ediuG s'rekihhctiH ehT" + }); + }); + + it('can reverse a string with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(stringReverse('title').as('reversedTitle')) + ); + expectResults(snapshot, { + reversedTitle: "yxalaG eht ot ediuG s'rekihhctiH ehT" + }); + }); + + it('supports Document_id', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + documentId(field('__name__')).as('docId'), + documentId(field('__path__')).as('noDocId') + ) + ); + expectResults(snapshot, { + docId: 'book4', + noDocId: null + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('__name__').documentId().as('docId')) + ); + expectResults(snapshot, { + docId: 'book4' + }); + }); + + it('supports substring', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(substring('title', 9, 2).as('of')) + ); + expectResults(snapshot, { + of: 'of' + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('title').substring(9, 2).as('of')) + ); + expectResults(snapshot, { + of: 'of' + }); + }); + + it('supports substring without length', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(substring('title', 9).as('of')) + ); + expectResults(snapshot, { + of: 'of the Rings' + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('title').substring(9).as('of')) + ); + expectResults(snapshot, { + of: 'of the Rings' + }); + }); + + it('test toLower', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('title')) + .select(toLower('author').as('lowercaseAuthor')) + .limit(1) + ); + expectResults(snapshot, { + lowercaseAuthor: 'george orwell' + }); + }); + + it('test toUpper', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('title')) + .select(toUpper('author').as('uppercaseAuthor')) + .limit(1) + ); + expectResults(snapshot, { uppercaseAuthor: 'GEORGE ORWELL' }); + }); + + it('testTrim', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .replaceWith( + map({ + spacedTitle: " The Hitchhiker's Guide to the Galaxy ", + userNameWithQuotes: '"alice"', + bytes: Bytes.fromUint8Array( + Uint8Array.from([0x00, 0x01, 0x02, 0x00, 0x00]) + ) + }) + ) + .select( + trim('spacedTitle').as('trimmedTitle'), + field('spacedTitle'), + field('userNameWithQuotes').trim('"').as('userName'), + field('bytes') + .trim(Bytes.fromUint8Array(Uint8Array.from([0x00]))) + .as('bytes') + ) + .limit(1) + ); + expectResults(snapshot, { + spacedTitle: " The Hitchhiker's Guide to the Galaxy ", + trimmedTitle: "The Hitchhiker's Guide to the Galaxy", + userName: 'alice', + bytes: Bytes.fromUint8Array(Uint8Array.from([0x01, 0x02])) + }); + }); + + it('test reverse', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', '1984')) + .limit(1) + .select(reverse('title').as('reverseTitle')) + ); + expectResults(snapshot, { reverseTitle: '4891' }); + }); + + it('testAbs', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + constant(-10).as('neg10'), + constant(-22.22).as('neg22'), + constant(1).as('pos1') + ) + .select( + abs('neg10').as('10'), + abs(field('neg22')).as('22'), + field('pos1').as('1') + ) + ); + expectResults(snapshot, { + '10': 10, + '22': 22.22, + '1': 1 + }); + }); + + it('can compute the base-10 logarithm of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(field('rating').log10().as('log10Rating')) + ); + expect(snapshot.results[0]!.data().log10Rating).to.be.closeTo( + 0.672, + 0.001 + ); + }); + + it('can compute the base-10 logarithm of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(log10('rating').as('log10Rating')) + ); + expect(snapshot.results[0]!.data().log10Rating).to.be.closeTo( + 0.672, + 0.001 + ); + }); + + it('can concat fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .addFields( + concat('author', ' ', field('title')).as('display'), + field('author').concat(': ', field('title')).as('display2') + ) + .where(equal('author', 'Douglas Adams')) + .select('display', 'display2') + ); + expectResults(snapshot, { + display: "Douglas Adams The Hitchhiker's Guide to the Galaxy", + display2: "Douglas Adams: The Hitchhiker's Guide to the Galaxy" + }); + }); + + it('supports currentTimestamp', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .addFields(currentTimestamp().as('now')) + .select('now') + ); + const now = snapshot.results[0].get('now') as Timestamp; + expect(now).instanceof(Timestamp); + expect( + now.toDate().getUTCSeconds() - new Date().getUTCSeconds() + ).lessThan(5000); + }); + + it('supports ifAbsent', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .replaceWith( + map({ + title: 'foo' + }) + ) + .select( + ifAbsent('title', 'default title').as('title'), + field('name').ifAbsent('default name').as('name'), + field('name').ifAbsent(field('title')).as('nameOrTitle') + ) + ); + + expectResults(snapshot, { + title: 'foo', + name: 'default name', + nameOrTitle: 'foo' + }); + }); + + it('supports join', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .replaceWith( + map({ + tags: ['foo', 'bar', 'baz'], + delimeter: '|' + }) + ) + .select(join('tags', ',').as('csv'), field('tags').join('|').as('or')) + ); + + expectResults(snapshot, { + csv: 'foo,bar,baz', + or: 'foo|bar|baz' + }); + }); + + it('can compute the sum of the elements in an array', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .addFields(array([150, 200]).as('sales')) + .select(field('sales').arraySum().as('totalSales')) + ); + expectResults(snapshot, { + totalSales: 350 + }); + }); + + it('can compute the sum of the elements in an array with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .addFields(array([150, 200]).as('sales')) + .select(arraySum('sales').as('totalSales')) + ); + expectResults(snapshot, { + totalSales: 350 + }); + }); + + it('truncate timestamp', async () => { + const results = await execute( + firestore + .pipeline() + .collection(randomCol) + .limit(1) + .replaceWith( + map({ + timestamp: new Timestamp( + Date.UTC(2025, 10, 30, 1, 2, 3) / 1000, + 456789 + ) + }) + ) + .select( + timestampTruncate('timestamp', 'year').as('trunc_year'), + timestampTruncate(field('timestamp'), 'month').as('trunc_month'), + timestampTruncate(field('timestamp'), constant('day')).as( + 'trunc_day' + ), + field('timestamp') + .timestampTruncate(constant('day'), 'MST') + .as('trunc_day_mst'), + field('timestamp').timestampTruncate('hour').as('trunc_hour'), + field('timestamp') + .timestampTruncate(constant('minute')) + .as('trunc_minute'), + field('timestamp').timestampTruncate('second').as('trunc_second') + ) + ); + + expectResults(results, { + 'trunc_year': new Timestamp(Date.UTC(2025, 0) / 1000, 0), + 'trunc_month': new Timestamp(Date.UTC(2025, 10) / 1000, 0), + 'trunc_day': new Timestamp(Date.UTC(2025, 10, 30) / 1000, 0), + 'trunc_day_mst': new Timestamp( + Date.UTC(2025, 10, 29) / 1000 + 7 * 3600, + 0 + ), + 'trunc_hour': new Timestamp(Date.UTC(2025, 10, 30, 1) / 1000, 0), + 'trunc_minute': new Timestamp(Date.UTC(2025, 10, 30, 1, 2) / 1000, 0), + 'trunc_second': new Timestamp(Date.UTC(2025, 10, 30, 1, 2, 3) / 1000, 0) + }); + }); + + it('supports split', async () => { + const results = await execute( + firestore + .pipeline() + .collection(randomCol) + .limit(1) + .replaceWith( + map({ + csv: 'foo,bar,baz', + data: 'baz:bar:foo', + csvDelimeter: ',', + bytes: Bytes.fromUint8Array( + Uint8Array.from([0x01, 0x00, 0x02, 0x00, 0x03]) + ) + }) + ) + .select( + split('csv', field('csvDelimeter')).as('csv'), + split(field('data'), ':').as('data'), + field('bytes') + .split(constant(Bytes.fromUint8Array(Uint8Array.from([0x00])))) + .as('bytes') + ) + ); + + expectResults(results, { + csv: ['foo', 'bar', 'baz'], + data: ['baz', 'bar', 'foo'], + bytes: [ + Bytes.fromUint8Array(Uint8Array.from([0x01])), + Bytes.fromUint8Array(Uint8Array.from([0x02])), + Bytes.fromUint8Array(Uint8Array.from([0x03])) + ] + }); + + void expect( + execute( + firestore + .pipeline() + .collection(randomCol) + .limit(1) + .replaceWith( + map({ + csv: 'foo,bar,baz' + }) + ) + .select( + field('csv') + .split(constant(Bytes.fromUint8Array(Uint8Array.from([0x00])))) + .as('dontSplitStringAndBytes') + ) + ) + ).to.be.rejected; + }); + + it('supports type', async () => { + const result = await execute( + firestore + .pipeline() + .collection(randomCol) + .limit(1) + .replaceWith( + map({ + int: constant(1), + float: constant(1.1), + str: constant('a string'), + bool: constant(true), + null: constant(null), + geoPoint: constant(new GeoPoint(0.1, 0.2)), + timestamp: constant(new Timestamp(123456, 0)), + date: constant(new Date()), + bytes: constant( + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])) + ), + docRef: constant(doc(firestore, 'foo', 'bar')), + vector: constant(vector([1, 2, 3])), + map: map({ + 'number': 1, + 'string': 'a string' + }), + array: array([1, 'a string']) + }) + ) + .select( + type('int').as('int'), + field('float').type().as('float'), + field('str').type().as('str'), + type('bool').as('bool'), + type('null').as('null'), + type('geoPoint').as('geoPoint'), + type('timestamp').as('timestamp'), + type('date').as('date'), + type('bytes').as('bytes'), + type('docRef').as('docRef'), + type('vector').as('vector'), + type('map').as('map'), + type('array').as('array') + ) + ); + + expectResults(result, { + int: 'int64', + float: 'float64', + str: 'string', + bool: 'boolean', + null: 'null', + geoPoint: 'geo_point', + timestamp: 'timestamp', + date: 'timestamp', + bytes: 'bytes', + docRef: 'reference', + vector: 'vector', + map: 'map', + array: 'array' + }); + }); + + // TODO(new-expression): Add new expression tests above this line + }); + + describe('pagination', () => { + let addedDocs: DocumentReference[] = []; + + /** + * Adds several books to the test collection. These + * additional books support pagination test scenarios + * that would otherwise not be possible with the original + * set of books. + * @param collectionReference + */ + async function addBooks( + collectionReference: CollectionReference + ): Promise { + let docRef = doc(collectionReference, 'book11'); + addedDocs.push(docRef); + await setDoc(docRef, { + title: 'Jonathan Strange & Mr Norrell', + author: 'Susanna Clarke', + genre: 'Fantasy', + published: 2004, + rating: 4.6, + tags: ['historical fantasy', 'magic', 'alternate history', 'england'], + awards: { hugo: false, nebula: false } + }); + docRef = doc(collectionReference, 'book12'); + addedDocs.push(docRef); + await setDoc(docRef, { + title: 'The Master and Margarita', + author: 'Mikhail Bulgakov', + genre: 'Satire', + published: 1967, // Though written much earlier + rating: 4.6, + tags: [ + 'russian literature', + 'supernatural', + 'philosophy', + 'dark comedy' + ], + awards: {} + }); + docRef = doc(collectionReference, 'book13'); + addedDocs.push(docRef); + await setDoc(docRef, { + title: 'A Long Way to a Small, Angry Planet', + author: 'Becky Chambers', + genre: 'Science Fiction', + published: 2014, + rating: 4.6, + tags: ['space opera', 'found family', 'character-driven', 'optimistic'], + awards: { hugo: false, nebula: false, kitschies: true } + }); + } + + afterEach(async () => { + for (let i = 0; i < addedDocs.length; i++) { + await deleteDoc(addedDocs[i]); + } + addedDocs = []; + }); + + it('supports pagination with filters', async () => { + await addBooks(randomCol); + const pageSize = 2; + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'rating', '__name__') + .sort(field('rating').descending(), field('__name__').ascending()); + + let snapshot = await execute(pipeline.limit(pageSize)); + expectResults( + snapshot, + { title: 'The Lord of the Rings', rating: 4.7 }, + { title: 'Dune', rating: 4.6 } + ); + + const lastDoc = snapshot.results[snapshot.results.length - 1]; + + snapshot = await execute( + pipeline + .where( + or( + and( + field('rating').equal(lastDoc.get('rating')), + field('__name__').greaterThan(lastDoc.ref) + ), + field('rating').lessThan(lastDoc.get('rating')) + ) + ) + .limit(pageSize) + ); + expectResults( + snapshot, + { title: 'Jonathan Strange & Mr Norrell', rating: 4.6 }, + { title: 'The Master and Margarita', rating: 4.6 } + ); + }); + + it('supports pagination with offsets', async () => { + await addBooks(randomCol); + + const secondFilterField = '__name__'; + + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'rating', secondFilterField) + .sort( + field('rating').descending(), + field(secondFilterField).ascending() + ); + + const pageSize = 2; + let currPage = 0; + + let snapshot = await execute( + pipeline.offset(currPage++ * pageSize).limit(pageSize) + ); + + expectResults( + snapshot, + { + title: 'The Lord of the Rings', + rating: 4.7 + }, + { title: 'Dune', rating: 4.6 } + ); + + snapshot = await execute( + pipeline.offset(currPage++ * pageSize).limit(pageSize) + ); + expectResults( + snapshot, + { + title: 'Jonathan Strange & Mr Norrell', + rating: 4.6 + }, + { title: 'The Master and Margarita', rating: 4.6 } + ); + + snapshot = await execute( + pipeline.offset(currPage++ * pageSize).limit(pageSize) + ); + expectResults( + snapshot, + { + title: 'A Long Way to a Small, Angry Planet', + rating: 4.6 + }, + { + title: 'Pride and Prejudice', + rating: 4.5 + } + ); + }); + }); + + describe('stage options', () => { + describe('forceIndex', () => { + // SKIP: requires pre-existing index + // eslint-disable-next-line no-restricted-properties + it.skip('Collection Stage', async () => { + const snapshot = await execute( + firestore.pipeline().collection({ + collection: randomCol, + forceIndex: 'unknown' + }) + ); + expect(snapshot.results.length).to.equal(10); + }); + + // SKIP: requires pre-existing index + // eslint-disable-next-line no-restricted-properties + it.skip('CollectionGroup Stage', async () => { + const snapshot = await execute( + firestore.pipeline().collectionGroup({ + collectionId: randomCol.id, + forceIndex: 'unknown' + }) + ); + expect(snapshot.results.length).to.equal(10); + }); + }); + }); +}); diff --git a/packages/firestore/test/integration/api/provider.test.ts b/packages/firestore/test/integration/api/provider.test.ts index cc7888a538..b89056395e 100644 --- a/packages/firestore/test/integration/api/provider.test.ts +++ b/packages/firestore/test/integration/api/provider.test.ts @@ -29,7 +29,9 @@ import { enableIndexedDbPersistence, setDoc, memoryLocalCache, - getDocFromCache + getDocFromCache, + // @ts-ignore internal API usage + ensureFirestoreConfigured } from '../util/firebase_export'; import { DEFAULT_SETTINGS } from '../util/settings'; @@ -67,10 +69,15 @@ describe('Firestore Provider', () => { const fs4 = getFirestore(app, 'name1'); const fs5 = getFirestore(app, 'name2'); + // @ts-ignore internal API usage expect(fs1._databaseId.database).to.be.equal('init1'); + // @ts-ignore internal API usage expect(fs2._databaseId.database).to.be.equal('init2'); + // @ts-ignore internal API usage expect(fs3._databaseId.database).to.be.equal('(default)'); + // @ts-ignore internal API usage expect(fs4._databaseId.database).to.be.equal('name1'); + // @ts-ignore internal API usage expect(fs5._databaseId.database).to.be.equal('name2'); expect(fs1).to.not.be.equal(fs2); @@ -200,4 +207,18 @@ describe('Firestore Provider', () => { return terminate(firestore).then(() => terminate(firestore)); }); + + it('passes API key to database info', () => { + const app = initializeApp( + { apiKey: 'fake-api-key-x', projectId: 'test-project' }, + 'test-app-getFirestore-x' + ); + const fs = getFirestore(app); + ensureFirestoreConfigured(fs); + + // @ts-ignore internal API usage + expect(fs._firestoreClient?._databaseInfo.apiKey).to.equal( + 'fake-api-key-x' + ); + }); }); diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index a12c843bf2..0ebf1011bc 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -19,6 +19,7 @@ import { isNode } from '@firebase/util'; import { expect } from 'chai'; import { addEqualityMatcher } from '../../util/equality_matcher'; +import { it } from '../../util/mocha_extensions'; import { Deferred } from '../../util/promise'; import { EventsAccumulator } from '../util/events_accumulator'; import { @@ -32,7 +33,6 @@ import { doc, DocumentChange, DocumentChangeType, - DocumentData, documentId, enableNetwork, endAt, @@ -61,18 +61,13 @@ import { } from '../util/firebase_export'; import { apiDescribe, - RetryError, toChangesArray, toDataArray, - PERSISTENCE_MODE_UNSPECIFIED, withEmptyTestCollection, - withRetry, withTestCollection, withTestDb, checkOnlineAndOfflineResultsMatch } from '../util/helpers'; -import { USE_EMULATOR } from '../util/settings'; -import { captureExistenceFilterMismatches } from '../util/testing_hooks_util'; apiDescribe('Queries', persistence => { addEqualityMatcher(); @@ -711,7 +706,7 @@ apiDescribe('Queries', persistence => { }); // eslint-disable-next-line no-restricted-properties - (USE_EMULATOR ? it.skip : it)( + it.skipEmulator.skipEnterprise( 'can catch error message for missing index with error handler', () => { return withEmptyTestCollection(persistence, async coll => { @@ -730,6 +725,7 @@ apiDescribe('Queries', persistence => { err => { expect(err.code).to.equal('failed-precondition'); expect(err.message).to.exist; + // @ts-ignore internal API usage if (coll.firestore._databaseId.isDefaultDatabase) { expect(err.message).to.match( /index.*https:\/\/console\.firebase\.google\.com/ @@ -986,7 +982,7 @@ apiDescribe('Queries', persistence => { }); }); - it('can use IN filters', async () => { + it.skipEnterprise('can use IN filters', async () => { const testDocs = { a: { zip: 98101 }, b: { zip: 91102 }, @@ -1145,7 +1141,7 @@ apiDescribe('Queries', persistence => { }); }); - it('can use array-contains-any filters', async () => { + it.skipEnterprise('can use array-contains-any filters', async () => { const testDocs = { a: { array: [42] }, b: { array: ['a', 42, 'c'] }, @@ -1969,429 +1965,6 @@ apiDescribe('Queries', persistence => { }); }); - // TODO(b/291365820): Stop skipping this test when running against the - // Firestore emulator once the emulator is improved to include a bloom filter - // in the existence filter messages that it sends. - // eslint-disable-next-line no-restricted-properties - (USE_EMULATOR ? it.skip : it)( - 'resuming a query should use bloom filter to avoid full requery', - async () => { - // Prepare the names and contents of the 100 documents to create. - const testDocs: { [key: string]: object } = {}; - for (let i = 0; i < 100; i++) { - testDocs['doc' + (1000 + i)] = { key: 42 }; - } - - // Ensure that the local cache is configured to use LRU garbage - // collection (rather than eager garbage collection) so that the resume - // token and document data does not get prematurely evicted. - const lruPersistence = persistence.toLruGc(); - - return withRetry(async attemptNumber => { - return withTestCollection( - lruPersistence, - testDocs, - async (coll, db) => { - // Run a query to populate the local cache with the 100 documents - // and a resume token. - const snapshot1 = await getDocs(coll); - expect(snapshot1.size, 'snapshot1.size').to.equal(100); - const createdDocuments = snapshot1.docs.map( - snapshot => snapshot.ref - ); - - // Delete 50 of the 100 documents. Use a different Firestore - // instance to avoid affecting the local cache. - const deletedDocumentIds = new Set(); - await withTestDb(PERSISTENCE_MODE_UNSPECIFIED, async db2 => { - const batch = writeBatch(db2); - for (let i = 0; i < createdDocuments.length; i += 2) { - const documentToDelete = doc(db2, createdDocuments[i].path); - batch.delete(documentToDelete); - deletedDocumentIds.add(documentToDelete.id); - } - await batch.commit(); - }); - - // Wait for 10 seconds, during which Watch will stop tracking the - // query and will send an existence filter rather than "delete" - // events when the query is resumed. - await new Promise(resolve => setTimeout(resolve, 10000)); - - // Resume the query and save the resulting snapshot for - // verification. Use some internal testing hooks to "capture" the - // existence filter mismatches to verify that Watch sent a bloom - // filter, and it was used to avert a full requery. - const [existenceFilterMismatches, snapshot2] = - await captureExistenceFilterMismatches(() => getDocs(coll)); - - // Verify that the snapshot from the resumed query contains the - // expected documents; that is, that it contains the 50 documents - // that were _not_ deleted. - const actualDocumentIds = snapshot2.docs - .map(documentSnapshot => documentSnapshot.ref.id) - .sort(); - const expectedDocumentIds = createdDocuments - .filter(documentRef => !deletedDocumentIds.has(documentRef.id)) - .map(documentRef => documentRef.id) - .sort(); - expect(actualDocumentIds, 'snapshot2.docs').to.deep.equal( - expectedDocumentIds - ); - - // Verify that Watch sent an existence filter with the correct - // counts when the query was resumed. - expect( - existenceFilterMismatches, - 'existenceFilterMismatches' - ).to.have.length(1); - const { localCacheCount, existenceFilterCount, bloomFilter } = - existenceFilterMismatches[0]; - expect(localCacheCount, 'localCacheCount').to.equal(100); - expect(existenceFilterCount, 'existenceFilterCount').to.equal(50); - - // Verify that Watch sent a valid bloom filter. - if (!bloomFilter) { - expect.fail( - 'The existence filter should have specified a bloom filter ' + - 'in its `unchanged_names` field.' - ); - throw new Error('should never get here'); - } - - expect(bloomFilter.hashCount, 'bloomFilter.hashCount').to.be.above( - 0 - ); - expect( - bloomFilter.bitmapLength, - 'bloomFilter.bitmapLength' - ).to.be.above(0); - expect(bloomFilter.padding, 'bloomFilterPadding').to.be.above(0); - expect(bloomFilter.padding, 'bloomFilterPadding').to.be.below(8); - - // Verify that the bloom filter was successfully used to avert a - // full requery. If a false positive occurred then retry the entire - // test. Although statistically rare, false positives are expected - // to happen occasionally. When a false positive _does_ happen, just - // retry the test with a different set of documents. If that retry - // also_ experiences a false positive, then fail the test because - // that is so improbable that something must have gone wrong. - if (attemptNumber === 1 && !bloomFilter.applied) { - throw new RetryError(); - } - - expect( - bloomFilter.applied, - `bloomFilter.applied with attemptNumber=${attemptNumber}` - ).to.be.true; - } - ); - }); - } - ).timeout('90s'); - - // TODO(b/291365820): Stop skipping this test when running against the - // Firestore emulator once the emulator is improved to include a bloom filter - // in the existence filter messages that it sends. - // eslint-disable-next-line no-restricted-properties - (USE_EMULATOR ? it.skip : it)( - 'bloom filter should avert a full re-query when documents were added, ' + - 'deleted, removed, updated, and unchanged since the resume token', - async () => { - // Prepare the names and contents of the 20 documents to create. - const testDocs: { [key: string]: object } = {}; - for (let i = 0; i < 20; i++) { - testDocs['doc' + (1000 + i)] = { - key: 42, - removed: false - }; - } - - // Ensure that the local cache is configured to use LRU garbage - // collection (rather than eager garbage collection) so that the resume - // token and document data does not get prematurely evicted. - const lruPersistence = persistence.toLruGc(); - - return withRetry(async attemptNumber => { - return withTestCollection(lruPersistence, testDocs, async coll => { - // Run a query to populate the local cache with the 20 documents - // and a resume token. - const snapshot1 = await getDocs( - query(coll, where('removed', '==', false)) - ); - expect(snapshot1.size, 'snapshot1.size').to.equal(20); - const createdDocuments = snapshot1.docs.map(snapshot => snapshot.ref); - - // Out of the 20 existing documents, leave 5 docs untouched, delete 5 docs, - // remove 5 docs, update 5 docs, and add 15 new docs. - const deletedDocumentIds = new Set(); - const removedDocumentIds = new Set(); - const updatedDocumentIds = new Set(); - const addedDocumentIds: string[] = []; - - // Use a different Firestore instance to avoid affecting the local cache. - await withTestDb(PERSISTENCE_MODE_UNSPECIFIED, async db2 => { - const batch = writeBatch(db2); - - for (let i = 0; i < createdDocuments.length; i += 4) { - const documentToDelete = doc(db2, createdDocuments[i].path); - batch.delete(documentToDelete); - deletedDocumentIds.add(documentToDelete.id); - } - expect(deletedDocumentIds.size).to.equal(5); - - // Update 5 documents to no longer match the query. - for (let i = 1; i < createdDocuments.length; i += 4) { - const documentToModify = doc(db2, createdDocuments[i].path); - batch.update(documentToModify, { - removed: true - }); - removedDocumentIds.add(documentToModify.id); - } - expect(removedDocumentIds.size).to.equal(5); - - // Update 5 documents, but ensure they still match the query. - for (let i = 2; i < createdDocuments.length; i += 4) { - const documentToModify = doc(db2, createdDocuments[i].path); - batch.update(documentToModify, { - key: 43 - }); - updatedDocumentIds.add(documentToModify.id); - } - expect(updatedDocumentIds.size).to.equal(5); - - for (let i = 0; i < 15; i += 1) { - const documentToAdd = doc( - db2, - coll.path + '/newDoc' + (1000 + i) - ); - batch.set(documentToAdd, { - key: 42, - removed: false - }); - addedDocumentIds.push(documentToAdd.id); - } - - // Ensure the sets above are disjoint. - const mergedSet = new Set(); - [ - deletedDocumentIds, - removedDocumentIds, - updatedDocumentIds, - addedDocumentIds - ].forEach(set => { - set.forEach(documentId => mergedSet.add(documentId)); - }); - expect(mergedSet.size).to.equal(30); - - await batch.commit(); - }); - - // Wait for 10 seconds, during which Watch will stop tracking the - // query and will send an existence filter rather than "delete" - // events when the query is resumed. - await new Promise(resolve => setTimeout(resolve, 10000)); - - // Resume the query and save the resulting snapshot for - // verification. Use some internal testing hooks to "capture" the - // existence filter mismatches to verify that Watch sent a bloom - // filter, and it was used to avert a full requery. - const [existenceFilterMismatches, snapshot2] = - await captureExistenceFilterMismatches(() => - getDocs(query(coll, where('removed', '==', false))) - ); - - // Verify that the snapshot from the resumed query contains the - // expected documents; that is, 10 existing documents that still - // match the query, and 15 documents that are newly added. - const actualDocumentIds = snapshot2.docs - .map(documentSnapshot => documentSnapshot.ref.id) - .sort(); - const expectedDocumentIds = createdDocuments - .map(documentRef => documentRef.id) - .filter(documentId => !deletedDocumentIds.has(documentId)) - .filter(documentId => !removedDocumentIds.has(documentId)) - .concat(addedDocumentIds) - .sort(); - - expect(actualDocumentIds, 'snapshot2.docs').to.deep.equal( - expectedDocumentIds - ); - expect(actualDocumentIds.length).to.equal(25); - - // Verify that Watch sent an existence filter with the correct - // counts when the query was resumed. - expect( - existenceFilterMismatches, - 'existenceFilterMismatches' - ).to.have.length(1); - const { localCacheCount, existenceFilterCount, bloomFilter } = - existenceFilterMismatches[0]; - expect(localCacheCount, 'localCacheCount').to.equal(35); - expect(existenceFilterCount, 'existenceFilterCount').to.equal(25); - - // Verify that Watch sent a valid bloom filter. - if (!bloomFilter) { - expect.fail( - 'The existence filter should have specified a bloom filter ' + - 'in its `unchanged_names` field.' - ); - throw new Error('should never get here'); - } - - // Verify that the bloom filter was successfully used to avert a - // full requery. If a false positive occurred then retry the entire - // test. Although statistically rare, false positives are expected - // to happen occasionally. When a false positive _does_ happen, just - // retry the test with a different set of documents. If that retry - // also_ experiences a false positive, then fail the test because - // that is so improbable that something must have gone wrong. - if (attemptNumber === 1 && !bloomFilter.applied) { - throw new RetryError(); - } - - expect( - bloomFilter.applied, - `bloomFilter.applied with attemptNumber=${attemptNumber}` - ).to.be.true; - }); - }); - } - ).timeout('90s'); - - // TODO(b/291365820): Stop skipping this test when running against the - // Firestore emulator once the emulator is improved to include a bloom filter - // in the existence filter messages that it sends. - // eslint-disable-next-line no-restricted-properties - (USE_EMULATOR ? it.skip : it)( - 'bloom filter should correctly encode complex Unicode characters', - async () => { - // Firestore does not do any Unicode normalization on the document IDs. - // Therefore, two document IDs that are canonically-equivalent (i.e. they - // visually appear identical) but are represented by a different sequence - // of Unicode code points are treated as distinct document IDs. - const testDocIds = [ - 'DocumentToDelete', - // The next two strings both end with "e" with an accent: the first uses - // the dedicated Unicode code point for this character, while the second - // uses the standard lowercase "e" followed by the accent combining - // character. - 'LowercaseEWithAcuteAccent_\u00E9', - 'LowercaseEWithAcuteAccent_\u0065\u0301', - // The next two strings both end with an "e" with two different accents - // applied via the following two combining characters. The combining - // characters are specified in a different order and Firestore treats - // these document IDs as unique, despite the order of the combining - // characters being irrelevant. - 'LowercaseEWithMultipleAccents_\u0065\u0301\u0327', - 'LowercaseEWithMultipleAccents_\u0065\u0327\u0301', - // The next string contains a character outside the BMP (the "basic - // multilingual plane"); that is, its code point is greater than 0xFFFF. - // In UTF-16 (which JavaScript uses to store Unicode strings) this - // requires a surrogate pair, two 16-bit code units, to represent this - // character. Make sure that its presence is correctly tested in the - // bloom filter, which uses UTF-8 encoding. - 'Smiley_\u{1F600}' - ]; - - // Verify assumptions about the equivalence of strings in `testDocIds`. - expect(testDocIds[1].normalize()).equals(testDocIds[2].normalize()); - expect(testDocIds[3].normalize()).equals(testDocIds[4].normalize()); - expect(testDocIds[5]).equals('Smiley_\uD83D\uDE00'); - - // Create the mapping from document ID to document data for the document - // IDs specified in `testDocIds`. - const testDocs = testDocIds.reduce((map, docId) => { - map[docId] = { foo: 42 }; - return map; - }, {} as { [key: string]: DocumentData }); - - // Ensure that the local cache is configured to use LRU garbage collection - // (rather than eager garbage collection) so that the resume token and - // document data does not get prematurely evicted. - const lruPersistence = persistence.toLruGc(); - - return withTestCollection(lruPersistence, testDocs, async (coll, db) => { - // Run a query to populate the local cache with documents that have - // names with complex Unicode characters. - const snapshot1 = await getDocs(coll); - const snapshot1DocumentIds = snapshot1.docs.map( - documentSnapshot => documentSnapshot.id - ); - expect(snapshot1DocumentIds, 'snapshot1DocumentIds').to.have.members( - testDocIds - ); - - // Delete one of the documents so that the next call to getDocs() will - // experience an existence filter mismatch. Use a different Firestore - // instance to avoid affecting the local cache. - const documentToDelete = doc(coll, 'DocumentToDelete'); - await withTestDb(PERSISTENCE_MODE_UNSPECIFIED, async db2 => { - await deleteDoc(doc(db2, documentToDelete.path)); - }); - - // Wait for 10 seconds, during which Watch will stop tracking the query - // and will send an existence filter rather than "delete" events when - // the query is resumed. - await new Promise(resolve => setTimeout(resolve, 10000)); - - // Resume the query and save the resulting snapshot for verification. - // Use some internal testing hooks to "capture" the existence filter - // mismatches. - const [existenceFilterMismatches, snapshot2] = - await captureExistenceFilterMismatches(() => getDocs(coll)); - const snapshot2DocumentIds = snapshot2.docs.map( - documentSnapshot => documentSnapshot.id - ); - const testDocIdsMinusDeletedDocId = testDocIds.filter( - documentId => documentId !== documentToDelete.id - ); - expect(snapshot2DocumentIds, 'snapshot2DocumentIds').to.have.members( - testDocIdsMinusDeletedDocId - ); - - // Verify that Watch sent an existence filter with the correct counts. - expect( - existenceFilterMismatches, - 'existenceFilterMismatches' - ).to.have.length(1); - const existenceFilterMismatch = existenceFilterMismatches[0]; - expect( - existenceFilterMismatch.localCacheCount, - 'localCacheCount' - ).to.equal(testDocIds.length); - expect( - existenceFilterMismatch.existenceFilterCount, - 'existenceFilterCount' - ).to.equal(testDocIds.length - 1); - - // Verify that we got a bloom filter from Watch. - const bloomFilter = existenceFilterMismatch.bloomFilter!; - expect(bloomFilter?.mightContain, 'bloomFilter.mightContain').to.not.be - .undefined; - - // The bloom filter application should statistically be successful - // almost every time; the _only_ time when it would _not_ be successful - // is if there is a false positive when testing for 'DocumentToDelete' - // in the bloom filter. So verify that the bloom filter application is - // successful, unless there was a false positive. - const isFalsePositive = bloomFilter.mightContain(documentToDelete); - expect(bloomFilter.applied, 'bloomFilter.applied').to.equal( - !isFalsePositive - ); - - // Verify that the bloom filter contains the document paths with complex - // Unicode characters. - for (const testDoc of snapshot2.docs.map(snapshot => snapshot.ref)) { - expect( - bloomFilter.mightContain(testDoc), - `bloomFilter.mightContain('${testDoc.path}')` - ).to.be.true; - } - }); - } - ).timeout('90s'); - it('can query large documents with multi-byte character strings', () => { function randomMultiByteCharString(length: number): string { const charCodes: number[] = []; diff --git a/packages/firestore/test/integration/api/query_to_pipeline.test.ts b/packages/firestore/test/integration/api/query_to_pipeline.test.ts new file mode 100644 index 0000000000..34afdc6ab0 --- /dev/null +++ b/packages/firestore/test/integration/api/query_to_pipeline.test.ts @@ -0,0 +1,817 @@ +/** + * @license + * Copyright 2024 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 { expect, use } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import { addEqualityMatcher } from '../../util/equality_matcher'; +import { + doc, + DocumentData, + setDoc, + query, + where, + FieldPath, + orderBy, + limit, + limitToLast, + startAt, + startAfter, + endAt, + endBefore, + collectionGroup, + collection, + and, + documentId, + addDoc, + getDoc, + or, + getDocs +} from '../util/firebase_export'; +import { + apiDescribe, + PERSISTENCE_MODE_UNSPECIFIED, + withTestCollection +} from '../util/helpers'; +import { execute, PipelineSnapshot } from '../util/pipeline_export'; + +use(chaiAsPromised); + +// This is the Query integration tests from the lite API (no cache support) +// with some additional test cases added for more complete coverage. +apiDescribe.skipClassic('Query to Pipeline', persistence => { + addEqualityMatcher(); + + function verifyResults( + actual: PipelineSnapshot, + ...expected: DocumentData[] + ): void { + const results = actual.results; + expect(results.length).to.equal(expected.length); + + for (let i = 0; i < expected.length; ++i) { + expect(results[i].data()).to.deep.equal(expected[i]); + } + } + + it('supports default query', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { 1: { foo: 1 } }, + async (collRef, db) => { + const snapshot = await execute(db.pipeline().createFrom(collRef)); + verifyResults(snapshot, { foo: 1 }); + } + ); + }); + + it('supports filtered query', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('foo', '==', 1)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1 }); + } + ); + }); + + it('supports filtered query (with FieldPath)', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, where(new FieldPath('foo'), '==', 1)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1 }); + } + ); + }); + + it('supports ordered query (with default order)', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo')); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1 }, { foo: 2 }); + } + ); + }); + + it('supports ordered query (with asc)', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo', 'asc')); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1 }, { foo: 2 }); + } + ); + }); + + it('supports ordered query (with desc)', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo', 'desc')); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2 }, { foo: 1 }); + } + ); + }); + + it('supports limit query', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo'), limit(1)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1 }); + } + ); + }); + + it('supports limitToLast query', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 }, + 3: { foo: 3 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo'), limitToLast(2)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2 }, { foo: 3 }); + } + ); + }); + + it('supports startAt', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo'), startAt(2)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2 }); + } + ); + }); + + it('supports startAt with limitToLast', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 }, + 3: { foo: 3 }, + 4: { foo: 4 }, + 5: { foo: 5 } + }, + async (collRef, db) => { + const query1 = query( + collRef, + orderBy('foo'), + startAt(3), + limitToLast(4) + ); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 3 }, { foo: 4 }, { foo: 5 }); + } + ); + }); + + it('supports endAt with limitToLast', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 }, + 3: { foo: 3 }, + 4: { foo: 4 }, + 5: { foo: 5 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo'), endAt(3), limitToLast(2)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2 }, { foo: 3 }); + } + ); + }); + + it('supports startAfter (with DocumentSnapshot)', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { id: 1, foo: 1, bar: 1, baz: 1 }, + 2: { id: 2, foo: 1, bar: 1, baz: 2 }, + 3: { id: 3, foo: 1, bar: 1, baz: 2 }, + 4: { id: 4, foo: 1, bar: 2, baz: 1 }, + 5: { id: 5, foo: 1, bar: 2, baz: 2 }, + 6: { id: 6, foo: 1, bar: 2, baz: 2 }, + 7: { id: 7, foo: 2, bar: 1, baz: 1 }, + 8: { id: 8, foo: 2, bar: 1, baz: 2 }, + 9: { id: 9, foo: 2, bar: 1, baz: 2 }, + 10: { id: 10, foo: 2, bar: 2, baz: 1 }, + 11: { id: 11, foo: 2, bar: 2, baz: 2 }, + 12: { id: 12, foo: 2, bar: 2, baz: 2 } + }, + async (collRef, db) => { + let docRef = await getDoc(doc(collRef, '2')); + let query1 = query( + collRef, + orderBy('foo'), + orderBy('bar'), + orderBy('baz'), + startAfter(docRef) + ); + let snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults( + snapshot, + { id: 3, foo: 1, bar: 1, baz: 2 }, + { id: 4, foo: 1, bar: 2, baz: 1 }, + { id: 5, foo: 1, bar: 2, baz: 2 }, + { id: 6, foo: 1, bar: 2, baz: 2 }, + { id: 7, foo: 2, bar: 1, baz: 1 }, + { id: 8, foo: 2, bar: 1, baz: 2 }, + { id: 9, foo: 2, bar: 1, baz: 2 }, + { id: 10, foo: 2, bar: 2, baz: 1 }, + { id: 11, foo: 2, bar: 2, baz: 2 }, + { id: 12, foo: 2, bar: 2, baz: 2 } + ); + + docRef = await getDoc(doc(collRef, '3')); + query1 = query( + collRef, + orderBy('foo'), + orderBy('bar'), + orderBy('baz'), + startAfter(docRef) + ); + snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults( + snapshot, + { id: 4, foo: 1, bar: 2, baz: 1 }, + { id: 5, foo: 1, bar: 2, baz: 2 }, + { id: 6, foo: 1, bar: 2, baz: 2 }, + { id: 7, foo: 2, bar: 1, baz: 1 }, + { id: 8, foo: 2, bar: 1, baz: 2 }, + { id: 9, foo: 2, bar: 1, baz: 2 }, + { id: 10, foo: 2, bar: 2, baz: 1 }, + { id: 11, foo: 2, bar: 2, baz: 2 }, + { id: 12, foo: 2, bar: 2, baz: 2 } + ); + } + ); + }); + + it('supports startAt (with DocumentSnapshot)', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { id: 1, foo: 1, bar: 1, baz: 1 }, + 2: { id: 2, foo: 1, bar: 1, baz: 2 }, + 3: { id: 3, foo: 1, bar: 1, baz: 2 }, + 4: { id: 4, foo: 1, bar: 2, baz: 1 }, + 5: { id: 5, foo: 1, bar: 2, baz: 2 }, + 6: { id: 6, foo: 1, bar: 2, baz: 2 }, + 7: { id: 7, foo: 2, bar: 1, baz: 1 }, + 8: { id: 8, foo: 2, bar: 1, baz: 2 }, + 9: { id: 9, foo: 2, bar: 1, baz: 2 }, + 10: { id: 10, foo: 2, bar: 2, baz: 1 }, + 11: { id: 11, foo: 2, bar: 2, baz: 2 }, + 12: { id: 12, foo: 2, bar: 2, baz: 2 } + }, + async (collRef, db) => { + let docRef = await getDoc(doc(collRef, '2')); + let query1 = query( + collRef, + orderBy('foo'), + orderBy('bar'), + orderBy('baz'), + startAt(docRef) + ); + let snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults( + snapshot, + { id: 2, foo: 1, bar: 1, baz: 2 }, + { id: 3, foo: 1, bar: 1, baz: 2 }, + { id: 4, foo: 1, bar: 2, baz: 1 }, + { id: 5, foo: 1, bar: 2, baz: 2 }, + { id: 6, foo: 1, bar: 2, baz: 2 }, + { id: 7, foo: 2, bar: 1, baz: 1 }, + { id: 8, foo: 2, bar: 1, baz: 2 }, + { id: 9, foo: 2, bar: 1, baz: 2 }, + { id: 10, foo: 2, bar: 2, baz: 1 }, + { id: 11, foo: 2, bar: 2, baz: 2 }, + { id: 12, foo: 2, bar: 2, baz: 2 } + ); + + docRef = await getDoc(doc(collRef, '3')); + query1 = query( + collRef, + orderBy('foo'), + orderBy('bar'), + orderBy('baz'), + startAt(docRef) + ); + snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults( + snapshot, + { id: 3, foo: 1, bar: 1, baz: 2 }, + { id: 4, foo: 1, bar: 2, baz: 1 }, + { id: 5, foo: 1, bar: 2, baz: 2 }, + { id: 6, foo: 1, bar: 2, baz: 2 }, + { id: 7, foo: 2, bar: 1, baz: 1 }, + { id: 8, foo: 2, bar: 1, baz: 2 }, + { id: 9, foo: 2, bar: 1, baz: 2 }, + { id: 10, foo: 2, bar: 2, baz: 1 }, + { id: 11, foo: 2, bar: 2, baz: 2 }, + { id: 12, foo: 2, bar: 2, baz: 2 } + ); + } + ); + }); + + it('supports startAfter', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo'), startAfter(1)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2 }); + } + ); + }); + + it('supports endAt', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo'), endAt(1)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1 }); + } + ); + }); + + it('supports endBefore', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo'), endBefore(2)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1 }); + } + ); + }); + + it('supports pagination', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + let query1 = query(collRef, orderBy('foo'), limit(1)); + const pipeline1 = db.pipeline().createFrom(query1); + let snapshot = await execute(pipeline1); + verifyResults(snapshot, { foo: 1 }); + + // Pass the document snapshot from the previous snapshot + query1 = query(query1, startAfter(snapshot.results[0].get('foo'))); + snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2 }); + } + ); + }); + + it('supports pagination on DocumentIds', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + let query1 = query( + collRef, + orderBy('foo'), + orderBy(documentId(), 'asc'), + limit(1) + ); + const pipeline1 = db.pipeline().createFrom(query1); + let snapshot = await execute(pipeline1); + verifyResults(snapshot, { foo: 1 }); + + // Pass the document snapshot from the previous snapshot + query1 = query( + query1, + startAfter( + snapshot.results[0].get('foo'), + snapshot.results[0].ref?.id + ) + ); + snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2 }); + } + ); + }); + + it('supports collection groups', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + {}, + async (collRef, db) => { + const collectionGroupId = `${collRef.id}group`; + + const fooDoc = doc( + collRef.firestore, + `${collRef.id}/foo/${collectionGroupId}/doc1` + ); + const barDoc = doc( + collRef.firestore, + `${collRef.id}/bar/baz/boo/${collectionGroupId}/doc2` + ); + await setDoc(fooDoc, { foo: 1 }); + await setDoc(barDoc, { bar: 1 }); + + const query1 = collectionGroup(collRef.firestore, collectionGroupId); + const snapshot = await execute(db.pipeline().createFrom(query1)); + + verifyResults(snapshot, { bar: 1 }, { foo: 1 }); + } + ); + }); + + it('supports query over collection path with special characters', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + {}, + async (collRef, db) => { + const docWithSpecials = doc(collRef, 'so!@#$%^&*()_+special'); + + const collectionWithSpecials = collection( + docWithSpecials, + 'so!@#$%^&*()_+special' + ); + await addDoc(collectionWithSpecials, { foo: 1 }); + await addDoc(collectionWithSpecials, { foo: 2 }); + + const snapshot = await execute( + db + .pipeline() + .createFrom(query(collectionWithSpecials, orderBy('foo', 'asc'))) + ); + + verifyResults(snapshot, { foo: 1 }, { foo: 2 }); + } + ); + }); + + it('supports multiple inequality on same field', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + '01': { id: 1, foo: 1, bar: 1, baz: 1 }, + '02': { id: 2, foo: 1, bar: 1, baz: 2 }, + '03': { id: 3, foo: 1, bar: 1, baz: 2 }, + '04': { id: 4, foo: 1, bar: 2, baz: 1 }, + '05': { id: 5, foo: 1, bar: 2, baz: 2 }, + '06': { id: 6, foo: 1, bar: 2, baz: 2 }, + '07': { id: 7, foo: 2, bar: 1, baz: 1 }, + '08': { id: 8, foo: 2, bar: 1, baz: 2 }, + '09': { id: 9, foo: 2, bar: 1, baz: 2 }, + '10': { id: 10, foo: 2, bar: 2, baz: 1 }, + '11': { id: 11, foo: 2, bar: 2, baz: 2 }, + '12': { id: 12, foo: 2, bar: 2, baz: 2 } + }, + async (collRef, db) => { + const query1 = query( + collRef, + and(where('id', '>', 2), where('id', '<=', 10)) + ); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults( + snapshot, + { id: 3, foo: 1, bar: 1, baz: 2 }, + { id: 4, foo: 1, bar: 2, baz: 1 }, + { id: 5, foo: 1, bar: 2, baz: 2 }, + { id: 6, foo: 1, bar: 2, baz: 2 }, + { id: 7, foo: 2, bar: 1, baz: 1 }, + { id: 8, foo: 2, bar: 1, baz: 2 }, + { id: 9, foo: 2, bar: 1, baz: 2 }, + { id: 10, foo: 2, bar: 2, baz: 1 } + ); + } + ); + }); + + it('supports multiple inequality on different fields', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + '01': { id: 1, foo: 1, bar: 1, baz: 1 }, + '02': { id: 2, foo: 1, bar: 1, baz: 2 }, + '03': { id: 3, foo: 1, bar: 1, baz: 2 }, + '04': { id: 4, foo: 1, bar: 2, baz: 1 }, + '05': { id: 5, foo: 1, bar: 2, baz: 2 }, + '06': { id: 6, foo: 1, bar: 2, baz: 2 }, + '07': { id: 7, foo: 2, bar: 1, baz: 1 }, + '08': { id: 8, foo: 2, bar: 1, baz: 2 }, + '09': { id: 9, foo: 2, bar: 1, baz: 2 }, + '10': { id: 10, foo: 2, bar: 2, baz: 1 }, + '11': { id: 11, foo: 2, bar: 2, baz: 2 }, + '12': { id: 12, foo: 2, bar: 2, baz: 2 } + }, + async (collRef, db) => { + const query1 = query( + collRef, + and(where('id', '>=', 2), where('baz', '<', 2)) + ); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults( + snapshot, + { id: 4, foo: 1, bar: 2, baz: 1 }, + { id: 7, foo: 2, bar: 1, baz: 1 }, + { id: 10, foo: 2, bar: 2, baz: 1 } + ); + } + ); + }); + + it('supports collectionGroup query', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { 1: { foo: 1 } }, + async (collRef, db) => { + const snapshot = await execute( + db.pipeline().createFrom(collectionGroup(db, collRef.id)) + ); + verifyResults(snapshot, { foo: 1 }); + } + ); + }); + + it('supports eq nan', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: NaN }, + 2: { foo: 2, bar: 1 }, + 3: { foo: 3, bar: 'bar' } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', '==', NaN)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1, bar: NaN }); + } + ); + }); + + it('supports neq nan', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: NaN }, + 2: { foo: 2, bar: 1 }, + 3: { foo: 3, bar: 'bar' } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', '!=', NaN)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2, bar: 1 }, { foo: 3, bar: 'bar' }); + } + ); + }); + + it('supports eq null', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: null }, + 2: { foo: 2, bar: 1 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', '==', null)); + const classicSnapshot = await getDocs(query1); + const classicData = classicSnapshot.docs.map(d => d.data()); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, ...classicData); + } + ); + }); + + it('supports neq null', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: null }, + 2: { foo: 2, bar: 1 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', '!=', null)); + const classicSnapshot = await getDocs(query1); + const classicData = classicSnapshot.docs.map(d => d.data()); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, ...classicData); + } + ); + }); + + it('supports neq', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: 0 }, + 2: { foo: 2, bar: 1 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', '!=', 0)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2, bar: 1 }); + } + ); + }); + + it('supports array contains', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: [0, 2, 4, 6] }, + 2: { foo: 2, bar: [1, 3, 5, 7] } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', 'array-contains', 4)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1, bar: [0, 2, 4, 6] }); + } + ); + }); + + it('supports array contains any', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: [0, 2, 4, 6] }, + 2: { foo: 2, bar: [1, 3, 5, 7] }, + 3: { foo: 3, bar: [10, 20, 30, 40] } + }, + async (collRef, db) => { + const query1 = query( + collRef, + where('bar', 'array-contains-any', [4, 5]) + ); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults( + snapshot, + { foo: 1, bar: [0, 2, 4, 6] }, + { foo: 2, bar: [1, 3, 5, 7] } + ); + } + ); + }); + + it('supports in', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: 2 }, + 2: { foo: 2 }, + 3: { foo: 3, bar: 10 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', 'in', [0, 10, 20])); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 3, bar: 10 }); + } + ); + }); + + it('supports in with 1', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: 2 }, + 2: { foo: 2 }, + 3: { foo: 3, bar: 10 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', 'in', [2])); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1, bar: 2 }); + } + ); + }); + + it('supports not in', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: 2 }, + 2: { foo: 2, bar: 1 }, + 3: { foo: 3, bar: 10 } + }, + async (collRef, db) => { + const query1 = query( + collRef, + where('bar', 'not-in', [0, 10, 20]), + orderBy('foo') + ); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1, bar: 2 }, { foo: 2, bar: 1 }); + } + ); + }); + + it('supports not in with 1', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: 2 }, + 2: { foo: 2 }, + 3: { foo: 3, bar: 10 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', 'not-in', [2])); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 3, bar: 10 }); + } + ); + }); + + it('supports or operator', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: 2 }, + 2: { foo: 2, bar: 0 }, + 3: { foo: 3, bar: 10 } + }, + async (collRef, db) => { + const query1 = query( + collRef, + or(where('bar', '==', 2), where('foo', '==', 3)), + orderBy('foo') + ); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1, bar: 2 }, { foo: 3, bar: 10 }); + } + ); + }); +}); diff --git a/packages/firestore/test/integration/api_internal/console.test.ts b/packages/firestore/test/integration/api_internal/console.test.ts new file mode 100644 index 0000000000..88474adad4 --- /dev/null +++ b/packages/firestore/test/integration/api_internal/console.test.ts @@ -0,0 +1,69 @@ +/** + * @license + * Copyright 2025 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 { expect } from 'chai'; + +import { + count, + setDoc, + getAggregateFromServer, + getDoc, + average +} from '../util/firebase_export'; +import { apiDescribe, withTestCollection, withTestDoc } from '../util/helpers'; + +apiDescribe('console support', persistence => { + it('supports DocumentSnapshot serialization to proto', async () => { + await withTestDoc(persistence, async (docRef, firestore) => { + await setDoc(docRef, { foo: 3, bar: 3.5 }); + const doc = await getDoc(docRef); + expect(doc._fieldsProto()).to.deep.equal({ + 'foo': { + 'integerValue': '3' + }, + 'bar': { + 'doubleValue': 3.5 + } + }); + }); + }); + + it('supports AggregateSnapshot serialization to proto', async () => { + await withTestCollection( + persistence, + { + 1: { foo: 1 }, + 2: { foo: 1 } + }, + async collRef => { + const doc = await getAggregateFromServer(collRef, { + count: count(), + avg: average('foo') + }); + // @ts-ignore internal API usage + expect(doc._fieldsProto()).to.deep.equal({ + 'count': { + 'integerValue': '2' + }, + 'avg': { + 'doubleValue': 1.0 + } + }); + } + ); + }); +}); diff --git a/packages/firestore/test/integration/api_internal/pipeline.test.ts b/packages/firestore/test/integration/api_internal/pipeline.test.ts new file mode 100644 index 0000000000..4011aa7fb1 --- /dev/null +++ b/packages/firestore/test/integration/api_internal/pipeline.test.ts @@ -0,0 +1,200 @@ +/** + * @license + * Copyright 2025 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 { expect } from 'chai'; + +import { Deferred } from '../../util/promise'; +import { + addDoc, + CollectionReference, + Firestore, + vector +} from '../util/firebase_export'; +import { apiDescribe, withTestCollection } from '../util/helpers'; +import { + field, + _internalPipelineToExecutePipelineRequestProto, + execute +} from '../util/pipeline_export'; + +apiDescribe.skipClassic('Pipelines', persistence => { + let firestore: Firestore; + let randomCol: CollectionReference; + let testDeferred: Deferred | undefined; + let withTestCollectionPromise: Promise | undefined; + + beforeEach(async () => { + const setupDeferred = new Deferred(); + testDeferred = new Deferred(); + withTestCollectionPromise = withTestCollection( + persistence, + {}, + async (collectionRef, firestoreInstance) => { + randomCol = collectionRef; + firestore = firestoreInstance; + setupDeferred.resolve(); + + return testDeferred?.promise; + } + ); + + await setupDeferred.promise; + }); + + afterEach(async () => { + testDeferred?.resolve(); + await withTestCollectionPromise; + }); + + describe('console support', () => { + it('supports pipeline query serialization to proto', async () => { + // Perform the same test as the console + const pipeline = firestore + .pipeline() + .collection('customers') + .where(field('country').equal('United Kingdom')); + + const proto = _internalPipelineToExecutePipelineRequestProto(pipeline); + + const expectedStructuredPipelineProto = + '{"pipeline":{"stages":[{"name":"collection","options":{},"args":[{"referenceValue":"/customers"}]},{"name":"where","options":{},"args":[{"functionValue":{"name":"equal","args":[{"fieldReferenceValue":"country"},{"stringValue":"United Kingdom"}]}}]}]}}'; + expect(JSON.stringify(proto.structuredPipeline)).to.equal( + expectedStructuredPipelineProto + ); + }); + + it('supports PipelineSnapshot serialization to proto', async () => { + await addDoc(randomCol, { + title: '1984', + author: 'George Orwell', + genre: 'Dystopian', + published: 1949, + rating: 4.2, + tags: ['surveillance', 'totalitarianism', 'propaganda'], + awards: { prometheus: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 1, 10, 1, 1]) + }); + + // Perform the same test as the console + const pipeline = firestore + .pipeline() + .collection(randomCol) + .sort(field('title').ascending()) + .limit(1); + + const result = await execute(pipeline); + + expect(result.results[0]._fieldsProto()).to.deep.equal({ + 'author': { + 'stringValue': 'George Orwell' + }, + 'awards': { + 'mapValue': { + 'fields': { + 'prometheus': { + 'booleanValue': true + } + } + } + }, + 'embedding': { + 'mapValue': { + 'fields': { + '__type__': { + 'stringValue': '__vector__' + }, + 'value': { + 'arrayValue': { + 'values': [ + { + 'doubleValue': 1 + }, + { + 'doubleValue': 1 + }, + { + 'doubleValue': 1 + }, + { + 'doubleValue': 1 + }, + { + 'doubleValue': 1 + }, + { + 'doubleValue': 1 + }, + { + 'doubleValue': 1 + }, + { + 'doubleValue': 10 + }, + { + 'doubleValue': 1 + }, + { + 'doubleValue': 1 + } + ] + } + } + } + } + }, + 'genre': { + 'stringValue': 'Dystopian' + }, + 'published': { + 'integerValue': '1949' + }, + 'rating': { + 'doubleValue': 4.2 + }, + 'tags': { + 'arrayValue': { + 'values': [ + { + 'stringValue': 'surveillance' + }, + { + 'stringValue': 'totalitarianism' + }, + { + 'stringValue': 'propaganda' + } + ] + } + }, + 'title': { + 'stringValue': '1984' + } + }); + }); + + it('performs validation', async () => { + expect(() => { + const pipeline = firestore + .pipeline() + .collection('customers') + .where(field('country').equal(new Map([]))); + + _internalPipelineToExecutePipelineRequestProto(pipeline); + }).to.throw(); + }); + }); +}); diff --git a/packages/firestore/test/integration/api_internal/query.test.ts b/packages/firestore/test/integration/api_internal/query.test.ts new file mode 100644 index 0000000000..9d2fb5a38a --- /dev/null +++ b/packages/firestore/test/integration/api_internal/query.test.ts @@ -0,0 +1,467 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; + +import { addEqualityMatcher } from '../../util/equality_matcher'; +import { it } from '../../util/mocha_extensions'; +import { + deleteDoc, + doc, + DocumentData, + getDocs, + query, + where, + writeBatch +} from '../util/firebase_export'; +import { + apiDescribe, + RetryError, + PERSISTENCE_MODE_UNSPECIFIED, + withRetry, + withTestCollection, + withTestDb +} from '../util/helpers'; +import { USE_EMULATOR } from '../util/settings'; +import { captureExistenceFilterMismatches } from '../util/testing_hooks_util'; + +addEqualityMatcher(); + +apiDescribe('Queries', persistence => { + // TODO(b/291365820): Stop skipping this test when running against the + // Firestore emulator once the emulator is improved to include a bloom filter + // in the existence filter messages that it sends. + // eslint-disable-next-line no-restricted-properties + (USE_EMULATOR ? it.skip : it)( + 'resuming a query should use bloom filter to avoid full requery', + async () => { + // Prepare the names and contents of the 100 documents to create. + const testDocs: { [key: string]: object } = {}; + for (let i = 0; i < 100; i++) { + testDocs['doc' + (1000 + i)] = { key: 42 }; + } + + // Ensure that the local cache is configured to use LRU garbage + // collection (rather than eager garbage collection) so that the resume + // token and document data does not get prematurely evicted. + const lruPersistence = persistence.toLruGc(); + + return withRetry(async attemptNumber => { + return withTestCollection( + lruPersistence, + testDocs, + async (coll, db) => { + // Run a query to populate the local cache with the 100 documents + // and a resume token. + const snapshot1 = await getDocs(coll); + expect(snapshot1.size, 'snapshot1.size').to.equal(100); + const createdDocuments = snapshot1.docs.map( + snapshot => snapshot.ref + ); + + // Delete 50 of the 100 documents. Use a different Firestore + // instance to avoid affecting the local cache. + const deletedDocumentIds = new Set(); + await withTestDb(PERSISTENCE_MODE_UNSPECIFIED, async db2 => { + const batch = writeBatch(db2); + for (let i = 0; i < createdDocuments.length; i += 2) { + const documentToDelete = doc(db2, createdDocuments[i].path); + batch.delete(documentToDelete); + deletedDocumentIds.add(documentToDelete.id); + } + await batch.commit(); + }); + + // Wait for 10 seconds, during which Watch will stop tracking the + // query and will send an existence filter rather than "delete" + // events when the query is resumed. + await new Promise(resolve => setTimeout(resolve, 10000)); + + // Resume the query and save the resulting snapshot for + // verification. Use some internal testing hooks to "capture" the + // existence filter mismatches to verify that Watch sent a bloom + // filter, and it was used to avert a full requery. + const [existenceFilterMismatches, snapshot2] = + await captureExistenceFilterMismatches(() => getDocs(coll)); + + // Verify that the snapshot from the resumed query contains the + // expected documents; that is, that it contains the 50 documents + // that were _not_ deleted. + const actualDocumentIds = snapshot2.docs + .map(documentSnapshot => documentSnapshot.ref.id) + .sort(); + const expectedDocumentIds = createdDocuments + .filter(documentRef => !deletedDocumentIds.has(documentRef.id)) + .map(documentRef => documentRef.id) + .sort(); + expect(actualDocumentIds, 'snapshot2.docs').to.deep.equal( + expectedDocumentIds + ); + + // Verify that Watch sent an existence filter with the correct + // counts when the query was resumed. + expect( + existenceFilterMismatches, + 'existenceFilterMismatches' + ).to.have.length(1); + const { localCacheCount, existenceFilterCount, bloomFilter } = + existenceFilterMismatches[0]; + expect(localCacheCount, 'localCacheCount').to.equal(100); + expect(existenceFilterCount, 'existenceFilterCount').to.equal(50); + + // Verify that Watch sent a valid bloom filter. + if (!bloomFilter) { + expect.fail( + 'The existence filter should have specified a bloom filter ' + + 'in its `unchanged_names` field.' + ); + throw new Error('should never get here'); + } + + expect(bloomFilter.hashCount, 'bloomFilter.hashCount').to.be.above( + 0 + ); + expect( + bloomFilter.bitmapLength, + 'bloomFilter.bitmapLength' + ).to.be.above(0); + expect(bloomFilter.padding, 'bloomFilterPadding').to.be.above(0); + expect(bloomFilter.padding, 'bloomFilterPadding').to.be.below(8); + + // Verify that the bloom filter was successfully used to avert a + // full requery. If a false positive occurred then retry the entire + // test. Although statistically rare, false positives are expected + // to happen occasionally. When a false positive _does_ happen, just + // retry the test with a different set of documents. If that retry + // also_ experiences a false positive, then fail the test because + // that is so improbable that something must have gone wrong. + if (attemptNumber === 1 && !bloomFilter.applied) { + throw new RetryError(); + } + + expect( + bloomFilter.applied, + `bloomFilter.applied with attemptNumber=${attemptNumber}` + ).to.be.true; + } + ); + }); + } + ).timeout('90s'); + + // TODO(b/291365820): Stop skipping this test when running against the + // Firestore emulator once the emulator is improved to include a bloom filter + // in the existence filter messages that it sends. + // eslint-disable-next-line no-restricted-properties + (USE_EMULATOR ? it.skip : it)( + 'bloom filter should avert a full re-query when documents were added, ' + + 'deleted, removed, updated, and unchanged since the resume token', + async () => { + // Prepare the names and contents of the 20 documents to create. + const testDocs: { [key: string]: object } = {}; + for (let i = 0; i < 20; i++) { + testDocs['doc' + (1000 + i)] = { + key: 42, + removed: false + }; + } + + // Ensure that the local cache is configured to use LRU garbage + // collection (rather than eager garbage collection) so that the resume + // token and document data does not get prematurely evicted. + const lruPersistence = persistence.toLruGc(); + + return withRetry(async attemptNumber => { + return withTestCollection(lruPersistence, testDocs, async coll => { + // Run a query to populate the local cache with the 20 documents + // and a resume token. + const snapshot1 = await getDocs( + query(coll, where('removed', '==', false)) + ); + expect(snapshot1.size, 'snapshot1.size').to.equal(20); + const createdDocuments = snapshot1.docs.map(snapshot => snapshot.ref); + + // Out of the 20 existing documents, leave 5 docs untouched, delete 5 docs, + // remove 5 docs, update 5 docs, and add 15 new docs. + const deletedDocumentIds = new Set(); + const removedDocumentIds = new Set(); + const updatedDocumentIds = new Set(); + const addedDocumentIds: string[] = []; + + // Use a different Firestore instance to avoid affecting the local cache. + await withTestDb(PERSISTENCE_MODE_UNSPECIFIED, async db2 => { + const batch = writeBatch(db2); + + for (let i = 0; i < createdDocuments.length; i += 4) { + const documentToDelete = doc(db2, createdDocuments[i].path); + batch.delete(documentToDelete); + deletedDocumentIds.add(documentToDelete.id); + } + expect(deletedDocumentIds.size).to.equal(5); + + // Update 5 documents to no longer match the query. + for (let i = 1; i < createdDocuments.length; i += 4) { + const documentToModify = doc(db2, createdDocuments[i].path); + batch.update(documentToModify, { + removed: true + }); + removedDocumentIds.add(documentToModify.id); + } + expect(removedDocumentIds.size).to.equal(5); + + // Update 5 documents, but ensure they still match the query. + for (let i = 2; i < createdDocuments.length; i += 4) { + const documentToModify = doc(db2, createdDocuments[i].path); + batch.update(documentToModify, { + key: 43 + }); + updatedDocumentIds.add(documentToModify.id); + } + expect(updatedDocumentIds.size).to.equal(5); + + for (let i = 0; i < 15; i += 1) { + const documentToAdd = doc( + db2, + coll.path + '/newDoc' + (1000 + i) + ); + batch.set(documentToAdd, { + key: 42, + removed: false + }); + addedDocumentIds.push(documentToAdd.id); + } + + // Ensure the sets above are disjoint. + const mergedSet = new Set(); + [ + deletedDocumentIds, + removedDocumentIds, + updatedDocumentIds, + addedDocumentIds + ].forEach(set => { + set.forEach(documentId => mergedSet.add(documentId)); + }); + expect(mergedSet.size).to.equal(30); + + await batch.commit(); + }); + + // Wait for 10 seconds, during which Watch will stop tracking the + // query and will send an existence filter rather than "delete" + // events when the query is resumed. + await new Promise(resolve => setTimeout(resolve, 10000)); + + // Resume the query and save the resulting snapshot for + // verification. Use some internal testing hooks to "capture" the + // existence filter mismatches to verify that Watch sent a bloom + // filter, and it was used to avert a full requery. + const [existenceFilterMismatches, snapshot2] = + await captureExistenceFilterMismatches(() => + getDocs(query(coll, where('removed', '==', false))) + ); + + // Verify that the snapshot from the resumed query contains the + // expected documents; that is, 10 existing documents that still + // match the query, and 15 documents that are newly added. + const actualDocumentIds = snapshot2.docs + .map(documentSnapshot => documentSnapshot.ref.id) + .sort(); + const expectedDocumentIds = createdDocuments + .map(documentRef => documentRef.id) + .filter(documentId => !deletedDocumentIds.has(documentId)) + .filter(documentId => !removedDocumentIds.has(documentId)) + .concat(addedDocumentIds) + .sort(); + + expect(actualDocumentIds, 'snapshot2.docs').to.deep.equal( + expectedDocumentIds + ); + expect(actualDocumentIds.length).to.equal(25); + + // Verify that Watch sent an existence filter with the correct + // counts when the query was resumed. + expect( + existenceFilterMismatches, + 'existenceFilterMismatches' + ).to.have.length(1); + const { localCacheCount, existenceFilterCount, bloomFilter } = + existenceFilterMismatches[0]; + expect(localCacheCount, 'localCacheCount').to.equal(35); + expect(existenceFilterCount, 'existenceFilterCount').to.equal(25); + + // Verify that Watch sent a valid bloom filter. + if (!bloomFilter) { + expect.fail( + 'The existence filter should have specified a bloom filter ' + + 'in its `unchanged_names` field.' + ); + throw new Error('should never get here'); + } + + // Verify that the bloom filter was successfully used to avert a + // full requery. If a false positive occurred then retry the entire + // test. Although statistically rare, false positives are expected + // to happen occasionally. When a false positive _does_ happen, just + // retry the test with a different set of documents. If that retry + // also_ experiences a false positive, then fail the test because + // that is so improbable that something must have gone wrong. + if (attemptNumber === 1 && !bloomFilter.applied) { + throw new RetryError(); + } + + expect( + bloomFilter.applied, + `bloomFilter.applied with attemptNumber=${attemptNumber}` + ).to.be.true; + }); + }); + } + ).timeout('90s'); + + // TODO(b/291365820): Stop skipping this test when running against the + // Firestore emulator once the emulator is improved to include a bloom filter + // in the existence filter messages that it sends. + // eslint-disable-next-line no-restricted-properties + (USE_EMULATOR ? it.skip : it)( + 'bloom filter should correctly encode complex Unicode characters', + async () => { + // Firestore does not do any Unicode normalization on the document IDs. + // Therefore, two document IDs that are canonically-equivalent (i.e. they + // visually appear identical) but are represented by a different sequence + // of Unicode code points are treated as distinct document IDs. + const testDocIds = [ + 'DocumentToDelete', + // The next two strings both end with "e" with an accent: the first uses + // the dedicated Unicode code point for this character, while the second + // uses the standard lowercase "e" followed by the accent combining + // character. + 'LowercaseEWithAcuteAccent_\u00E9', + 'LowercaseEWithAcuteAccent_\u0065\u0301', + // The next two strings both end with an "e" with two different accents + // applied via the following two combining characters. The combining + // characters are specified in a different order and Firestore treats + // these document IDs as unique, despite the order of the combining + // characters being irrelevant. + 'LowercaseEWithMultipleAccents_\u0065\u0301\u0327', + 'LowercaseEWithMultipleAccents_\u0065\u0327\u0301', + // The next string contains a character outside the BMP (the "basic + // multilingual plane"); that is, its code point is greater than 0xFFFF. + // In UTF-16 (which JavaScript uses to store Unicode strings) this + // requires a surrogate pair, two 16-bit code units, to represent this + // character. Make sure that its presence is correctly tested in the + // bloom filter, which uses UTF-8 encoding. + 'Smiley_\u{1F600}' + ]; + + // Verify assumptions about the equivalence of strings in `testDocIds`. + expect(testDocIds[1].normalize()).equals(testDocIds[2].normalize()); + expect(testDocIds[3].normalize()).equals(testDocIds[4].normalize()); + expect(testDocIds[5]).equals('Smiley_\uD83D\uDE00'); + + // Create the mapping from document ID to document data for the document + // IDs specified in `testDocIds`. + const testDocs = testDocIds.reduce((map, docId) => { + map[docId] = { foo: 42 }; + return map; + }, {} as { [key: string]: DocumentData }); + + // Ensure that the local cache is configured to use LRU garbage collection + // (rather than eager garbage collection) so that the resume token and + // document data does not get prematurely evicted. + const lruPersistence = persistence.toLruGc(); + + return withTestCollection(lruPersistence, testDocs, async (coll, db) => { + // Run a query to populate the local cache with documents that have + // names with complex Unicode characters. + const snapshot1 = await getDocs(coll); + const snapshot1DocumentIds = snapshot1.docs.map( + documentSnapshot => documentSnapshot.id + ); + expect(snapshot1DocumentIds, 'snapshot1DocumentIds').to.have.members( + testDocIds + ); + + // Delete one of the documents so that the next call to getDocs() will + // experience an existence filter mismatch. Use a different Firestore + // instance to avoid affecting the local cache. + const documentToDelete = doc(coll, 'DocumentToDelete'); + await withTestDb(PERSISTENCE_MODE_UNSPECIFIED, async db2 => { + await deleteDoc(doc(db2, documentToDelete.path)); + }); + + // Wait for 10 seconds, during which Watch will stop tracking the query + // and will send an existence filter rather than "delete" events when + // the query is resumed. + await new Promise(resolve => setTimeout(resolve, 10000)); + + // Resume the query and save the resulting snapshot for verification. + // Use some internal testing hooks to "capture" the existence filter + // mismatches. + const [existenceFilterMismatches, snapshot2] = + await captureExistenceFilterMismatches(() => getDocs(coll)); + const snapshot2DocumentIds = snapshot2.docs.map( + documentSnapshot => documentSnapshot.id + ); + const testDocIdsMinusDeletedDocId = testDocIds.filter( + documentId => documentId !== documentToDelete.id + ); + expect(snapshot2DocumentIds, 'snapshot2DocumentIds').to.have.members( + testDocIdsMinusDeletedDocId + ); + + // Verify that Watch sent an existence filter with the correct counts. + expect( + existenceFilterMismatches, + 'existenceFilterMismatches' + ).to.have.length(1); + const existenceFilterMismatch = existenceFilterMismatches[0]; + expect( + existenceFilterMismatch.localCacheCount, + 'localCacheCount' + ).to.equal(testDocIds.length); + expect( + existenceFilterMismatch.existenceFilterCount, + 'existenceFilterCount' + ).to.equal(testDocIds.length - 1); + + // Verify that we got a bloom filter from Watch. + const bloomFilter = existenceFilterMismatch.bloomFilter!; + expect(bloomFilter?.mightContain, 'bloomFilter.mightContain').to.not.be + .undefined; + + // The bloom filter application should statistically be successful + // almost every time; the _only_ time when it would _not_ be successful + // is if there is a false positive when testing for 'DocumentToDelete' + // in the bloom filter. So verify that the bloom filter application is + // successful, unless there was a false positive. + const isFalsePositive = bloomFilter.mightContain(documentToDelete); + expect(bloomFilter.applied, 'bloomFilter.applied').to.equal( + !isFalsePositive + ); + + // Verify that the bloom filter contains the document paths with complex + // Unicode characters. + for (const testDoc of snapshot2.docs.map(snapshot => snapshot.ref)) { + expect( + bloomFilter.mightContain(testDoc), + `bloomFilter.mightContain('${testDoc.path}')` + ).to.be.true; + } + }); + } + ).timeout('90s'); +}); diff --git a/packages/firestore/test/integration/util/composite_index_test_helper.ts b/packages/firestore/test/integration/util/composite_index_test_helper.ts index a908ed1345..fbabe977dc 100644 --- a/packages/firestore/test/integration/util/composite_index_test_helper.ts +++ b/packages/firestore/test/integration/util/composite_index_test_helper.ts @@ -40,7 +40,9 @@ import { deleteDoc as deleteDocument, doc, and, + // @ts-ignore internal API usage _AutoId, + // @ts-ignore internal API usage _FieldPath, newTestFirestore, newTestApp, diff --git a/packages/firestore/test/integration/util/helpers.ts b/packages/firestore/test/integration/util/helpers.ts index b36ed98029..04546ce35b 100644 --- a/packages/firestore/test/integration/util/helpers.ts +++ b/packages/firestore/test/integration/util/helpers.ts @@ -18,6 +18,8 @@ import { isIndexedDBAvailable } from '@firebase/util'; import { expect } from 'chai'; +import { describe } from '../../util/mocha_extensions'; + import { clearIndexedDbPersistence, collection, @@ -44,6 +46,7 @@ import { Query, getDocsFromServer, getDocsFromCache, + // @ts-ignore internal API usage _AutoId } from './firebase_export'; import { @@ -177,7 +180,10 @@ export function isPersistenceAvailable(): boolean { * persistence both disabled and enabled (if the browser is supported). */ function apiDescribeInternal( - describeFn: Mocha.PendingSuiteFunction, + describeFn: + | Mocha.PendingSuiteFunction + | Mocha.SuiteFunction + | Mocha.ExclusiveSuiteFunction, message: string, testSuite: (persistence: PersistenceMode) => void ): void { @@ -204,6 +210,9 @@ interface ApiDescribe { (message: string, testSuite: (persistence: PersistenceMode) => void): void; skip: ApiSuiteFunction; only: ApiSuiteFunction; + skipEnterprise: ApiSuiteFunction; + skipEmulator: ApiSuiteFunction; + skipClassic: ApiSuiteFunction; } export const apiDescribe = apiDescribeInternal.bind( @@ -214,6 +223,15 @@ export const apiDescribe = apiDescribeInternal.bind( apiDescribe.skip = apiDescribeInternal.bind(null, describe.skip); // eslint-disable-next-line no-restricted-properties apiDescribe.only = apiDescribeInternal.bind(null, describe.only); +apiDescribe.skipEnterprise = apiDescribeInternal.bind( + null, + describe.skipEnterprise +); +apiDescribe.skipClassic = apiDescribeInternal.bind(null, describe.skipClassic); +apiDescribe.skipEmulator = apiDescribeInternal.bind( + null, + describe.skipEmulator +); /** Converts the documents in a QuerySnapshot to an array with the data of each document. */ export function toDataArray(docSet: QuerySnapshot): DocumentData[] { diff --git a/packages/firestore/test/integration/util/internal_helpers.ts b/packages/firestore/test/integration/util/internal_helpers.ts index e5e64b5fbf..b196599f40 100644 --- a/packages/firestore/test/integration/util/internal_helpers.ts +++ b/packages/firestore/test/integration/util/internal_helpers.ts @@ -62,7 +62,8 @@ export function getDefaultDatabaseInfo(): DatabaseInfo { DEFAULT_SETTINGS.experimentalLongPollingOptions ?? {} ), /*use FetchStreams= */ false, - /*isUsingEmulator=*/ false + /*isUsingEmulator=*/ false, + undefined ); } diff --git a/packages/firestore/test/integration/util/pipeline_export.ts b/packages/firestore/test/integration/util/pipeline_export.ts new file mode 100644 index 0000000000..d2495d772a --- /dev/null +++ b/packages/firestore/test/integration/util/pipeline_export.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Imports firebase via the raw sources and re-exports it. The +// "/integration/firestore" test suite replaces this file with a +// reference to the minified sources. If you change any exports in this file, +// you need to also adjust "integration/firestore/pipeline_export.ts". + +export * from '../../../pipelines/pipelines'; diff --git a/packages/firestore/test/integration/util/testing_hooks_util.ts b/packages/firestore/test/integration/util/testing_hooks_util.ts index 72604f91a8..7b99cf4040 100644 --- a/packages/firestore/test/integration/util/testing_hooks_util.ts +++ b/packages/firestore/test/integration/util/testing_hooks_util.ts @@ -17,7 +17,9 @@ import { DocumentReference, + // @ts-ignore internal API usage _TestingHooks as TestingHooks, + // @ts-ignore internal API usage _TestingHooksExistenceFilterMismatchInfo as ExistenceFilterMismatchInfoInternal } from './firebase_export'; diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 54595ade4d..63f55b1aa6 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -93,6 +93,7 @@ import { DEFAULT_SETTINGS, USE_EMULATOR } from '../integration/util/settings'; +import { it, describe } from '../util/mocha_extensions'; import { Post, @@ -2463,27 +2464,24 @@ describe('Count queries', () => { // production, since the Firestore Emulator does not require index creation // and will, therefore, never fail in this situation. // eslint-disable-next-line no-restricted-properties - (USE_EMULATOR ? it.skip : it)( - 'getCount error message contains console link if missing index', - () => { - return withTestCollection(async coll => { - const query_ = query( - coll, - where('key1', '==', 42), - where('key2', '<', 42) + it.skip('getCount error message contains console link if missing index', () => { + return withTestCollection(async coll => { + const query_ = query( + coll, + where('key1', '==', 42), + where('key2', '<', 42) + ); + // TODO(b/316359394) Remove the special logic for non-default databases + // once cl/582465034 is rolled out to production. + if (coll.firestore._databaseId.isDefaultDatabase) { + await expect(getCount(query_)).to.be.eventually.rejectedWith( + /index.*https:\/\/console\.firebase\.google\.com/ ); - // TODO(b/316359394) Remove the special logic for non-default databases - // once cl/582465034 is rolled out to production. - if (coll.firestore._databaseId.isDefaultDatabase) { - await expect(getCount(query_)).to.be.eventually.rejectedWith( - /index.*https:\/\/console\.firebase\.google\.com/ - ); - } else { - await expect(getCount(query_)).to.be.eventually.rejected; - } - }); - } - ); + } else { + await expect(getCount(query_)).to.be.eventually.rejected; + } + }); + }); }); describe('Aggregate queries', () => { @@ -2772,7 +2770,7 @@ describe('Aggregate queries', () => { // production, since the Firestore Emulator does not require index creation // and will, therefore, never fail in this situation. // eslint-disable-next-line no-restricted-properties - (USE_EMULATOR ? it.skip : it)( + it.skipEmulator.skipEnterprise( 'getAggregate error message contains console link if missing index', () => { return withTestCollection(async coll => { @@ -2906,26 +2904,29 @@ describe('Aggregate queries - sum / average', () => { }); }); - it('fails when exceeding the max (5) aggregations using getAggregationFromServer', () => { - const testDocs = [ - { author: 'authorA', title: 'titleA', pages: 100 }, - { author: 'authorB', title: 'titleB', pages: 50 } - ]; - return withTestCollectionAndInitialData(testDocs, async coll => { - const promise = getAggregate(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - count: count(), - totalPagesX: sum('pages'), - averagePagesY: average('pages'), - countZ: count() - }); + it.skipEmulator.skipEnterprise( + 'fails when exceeding the max (5) aggregations using getAggregationFromServer', + () => { + const testDocs = [ + { author: 'authorA', title: 'titleA', pages: 100 }, + { author: 'authorB', title: 'titleB', pages: 50 } + ]; + return withTestCollectionAndInitialData(testDocs, async coll => { + const promise = getAggregate(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count(), + totalPagesX: sum('pages'), + averagePagesY: average('pages'), + countZ: count() + }); - await expect(promise).to.eventually.be.rejectedWith( - /maximum number of aggregations/ - ); - }); - }); + await expect(promise).to.eventually.be.rejectedWith( + /maximum number of aggregations/ + ); + }); + } + ); // Only run tests that require indexes against the emulator, because we don't // have a way to dynamically create the indexes when running the tests. diff --git a/packages/firestore/test/lite/pipeline.test.ts b/packages/firestore/test/lite/pipeline.test.ts new file mode 100644 index 0000000000..477d976eb4 --- /dev/null +++ b/packages/firestore/test/lite/pipeline.test.ts @@ -0,0 +1,4140 @@ +/** + * @license + * Copyright 2024 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. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { FirebaseError } from '@firebase/util'; +import { expect, use } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import { Bytes } from '../../src/lite-api/bytes'; +import { + Firestore, + getFirestore, + terminate +} from '../../src/lite-api/database'; +import { documentId as documentIdFieldPath } from '../../src/lite-api/field_path'; +import { vector } from '../../src/lite-api/field_value_impl'; +import { GeoPoint } from '../../src/lite-api/geo_point'; +import { + pipelineResultEqual, + PipelineSnapshot +} from '../../src/lite-api/pipeline-result'; +import { execute } from '../../src/lite-api/pipeline_impl'; +import { + DocumentData, + CollectionReference, + collection, + doc, + DocumentReference +} from '../../src/lite-api/reference'; +import { addDoc, setDoc, deleteDoc } from '../../src/lite-api/reference_impl'; +import { FindNearestStageOptions } from '../../src/lite-api/stage_options'; +import { Timestamp } from '../../src/lite-api/timestamp'; +import { writeBatch } from '../../src/lite-api/write_batch'; +import { addEqualityMatcher } from '../util/equality_matcher'; +import { describe } from '../util/mocha_extensions'; +import { Deferred } from '../util/promise'; + +import { withTestCollection } from './helpers'; +import { + field, + and, + array, + constant, + add, + subtract, + multiply, + average, + substring, + count, + mapMerge, + mapRemove, + ifError, + isAbsent, + isError, + or, + map, + length, + mod, + documentId, + equal, + notEqual, + lessThan, + countIf, + lessThanOrEqual, + greaterThan, + arrayContains, + arrayContainsAny, + equalAny, + notEqualAny, + xor, + conditional, + logicalMaximum, + logicalMinimum, + exists, + reverse, + like, + regexContains, + regexMatch, + stringContains, + startsWith, + endsWith, + mapGet, + countAll, + minimum, + maximum, + cosineDistance, + dotProduct, + euclideanDistance, + vectorLength, + unixMicrosToTimestamp, + timestampToUnixMicros, + unixMillisToTimestamp, + timestampToUnixMillis, + unixSecondsToTimestamp, + timestampToUnixSeconds, + timestampAdd, + timestampSubtract, + ascending, + descending, + FunctionExpression, + AggregateFunction, + stringConcat, + arrayContainsAll, + arrayLength, + charLength, + divide, + not, + toLower, + toUpper, + trim, + byteLength, + arrayGet, + abs, + sum, + countDistinct, + ceil, + floor, + exp, + pow, + round, + collectionId, + ln, + log, + sqrt, + stringReverse, + log10, + concat, + currentTimestamp, + ifAbsent, + join, + arraySum +} from './pipeline_export'; + +use(chaiAsPromised); + +const timestampDeltaMS = 1000; + +describe.skipClassic('Firestore Pipelines', () => { + addEqualityMatcher(); + + let firestore: Firestore; + let randomCol: CollectionReference; + let beginDocCreation: number = 0; + let endDocCreation: number = 0; + + async function testCollectionWithDocs(docs: { + [id: string]: DocumentData; + }): Promise> { + beginDocCreation = new Date().valueOf(); + for (const id in docs) { + if (docs.hasOwnProperty(id)) { + const ref = doc(randomCol, id); + await setDoc(ref, docs[id]); + } + } + endDocCreation = new Date().valueOf(); + return randomCol; + } + + function expectResults(snapshot: PipelineSnapshot, ...docs: string[]): void; + function expectResults( + snapshot: PipelineSnapshot, + ...data: DocumentData[] + ): void; + + function expectResults( + snapshot: PipelineSnapshot, + ...data: DocumentData[] | string[] + ): void { + const docs = snapshot.results; + + expect(docs.length).to.equal(data.length); + + if (data.length > 0) { + if (typeof data[0] === 'string') { + const actualIds = docs.map(doc => doc.id); + expect(actualIds).to.deep.equal(data); + } else { + docs.forEach(r => { + expect(r.data()).to.deep.equal(data.shift()); + }); + } + } + } + + async function setupBookDocs(): Promise> { + const bookDocs: { [id: string]: DocumentData } = { + book1: { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + embedding: vector([10, 1, 1, 1, 1, 1, 1, 1, 1, 1]) + }, + book2: { + title: 'Pride and Prejudice', + author: 'Jane Austen', + genre: 'Romance', + published: 1813, + rating: 4.5, + tags: ['classic', 'social commentary', 'love'], + awards: { none: true }, + embedding: vector([1, 10, 1, 1, 1, 1, 1, 1, 1, 1]) + }, + book3: { + title: 'One Hundred Years of Solitude', + author: 'Gabriel García Márquez', + genre: 'Magical Realism', + published: 1967, + rating: 4.3, + tags: ['family', 'history', 'fantasy'], + awards: { nobel: true, nebula: false }, + embedding: vector([1, 1, 10, 1, 1, 1, 1, 1, 1, 1]) + }, + book4: { + title: 'The Lord of the Rings', + author: 'J.R.R. Tolkien', + genre: 'Fantasy', + published: 1954, + rating: 4.7, + tags: ['adventure', 'magic', 'epic'], + awards: { hugo: false, nebula: false }, + remarks: null, + cost: NaN, + embedding: vector([1, 1, 1, 10, 1, 1, 1, 1, 1, 1]) + }, + book5: { + title: "The Handmaid's Tale", + author: 'Margaret Atwood', + genre: 'Dystopian', + published: 1985, + rating: 4.1, + tags: ['feminism', 'totalitarianism', 'resistance'], + awards: { 'arthur c. clarke': true, 'booker prize': false }, + embedding: vector([1, 1, 1, 1, 10, 1, 1, 1, 1, 1]) + }, + book6: { + title: 'Crime and Punishment', + author: 'Fyodor Dostoevsky', + genre: 'Psychological Thriller', + published: 1866, + rating: 4.3, + tags: ['philosophy', 'crime', 'redemption'], + awards: { none: true }, + embedding: vector([1, 1, 1, 1, 1, 10, 1, 1, 1, 1]) + }, + book7: { + title: 'To Kill a Mockingbird', + author: 'Harper Lee', + genre: 'Southern Gothic', + published: 1960, + rating: 4.2, + tags: ['racism', 'injustice', 'coming-of-age'], + awards: { pulitzer: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 10, 1, 1, 1]) + }, + book8: { + title: '1984', + author: 'George Orwell', + genre: 'Dystopian', + published: 1949, + rating: 4.2, + tags: ['surveillance', 'totalitarianism', 'propaganda'], + awards: { prometheus: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 1, 10, 1, 1]) + }, + book9: { + title: 'The Great Gatsby', + author: 'F. Scott Fitzgerald', + genre: 'Modernist', + published: 1925, + rating: 4.0, + tags: ['wealth', 'american dream', 'love'], + awards: { none: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 1, 1, 10, 1]) + }, + book10: { + title: 'Dune', + author: 'Frank Herbert', + genre: 'Science Fiction', + published: 1965, + rating: 4.6, + tags: ['politics', 'desert', 'ecology'], + awards: { hugo: true, nebula: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 1, 1, 1, 10]) + } + }; + return testCollectionWithDocs(bookDocs); + } + + let testDeferred: Deferred | undefined; + let withTestCollectionPromise: Promise | undefined; + + beforeEach(async () => { + const setupDeferred = new Deferred(); + testDeferred = new Deferred(); + withTestCollectionPromise = withTestCollection(async collectionRef => { + randomCol = collectionRef; + firestore = collectionRef.firestore; + await setupBookDocs(); + setupDeferred.resolve(); + + return testDeferred?.promise; + }); + + await setupDeferred.promise; + }); + + afterEach(async () => { + testDeferred?.resolve(); + await withTestCollectionPromise; + }); + + describe('pipeline results', () => { + it('empty snapshot as expected', async () => { + const snapshot = await execute( + firestore.pipeline().collection(randomCol.path).limit(0) + ); + expect(snapshot.results.length).to.equal(0); + }); + + it('full snapshot as expected', async () => { + const ppl = firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('__name__')); + const snapshot = await execute(ppl); + expect(snapshot.results.length).to.equal(10); + expectResults( + snapshot, + 'book1', + 'book10', + 'book2', + 'book3', + 'book4', + 'book5', + 'book6', + 'book7', + 'book8', + 'book9' + ); + }); + + it('result equals works', async () => { + const ppl = firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('title')) + .limit(1); + const snapshot1 = await execute(ppl); + const snapshot2 = await execute(ppl); + expect(snapshot1.results.length).to.equal(1); + expect(snapshot2.results.length).to.equal(1); + expect(pipelineResultEqual(snapshot1.results[0], snapshot2.results[0])).to + .be.true; + }); + + it('returns execution time', async () => { + const start = new Date().valueOf(); + const pipeline = firestore.pipeline().collection(randomCol.path); + + const snapshot = await execute(pipeline); + const end = new Date().valueOf(); + + expect(snapshot.executionTime.toDate().valueOf()).to.approximately( + (start + end) / 2, + timestampDeltaMS + ); + }); + + it('returns execution time for an empty query', async () => { + const start = new Date().valueOf(); + const pipeline = firestore.pipeline().collection(randomCol.path).limit(0); + + const snapshot = await execute(pipeline); + const end = new Date().valueOf(); + + expect(snapshot.results.length).to.equal(0); + + expect(snapshot.executionTime.toDate().valueOf()).to.approximately( + (start + end) / 2, + timestampDeltaMS + ); + }); + + it('returns create and update time for each document', async () => { + const pipeline = firestore.pipeline().collection(randomCol.path); + + let snapshot = await execute(pipeline); + expect(snapshot.results.length).to.equal(10); + snapshot.results.forEach(doc => { + expect(doc.createTime).to.not.be.null; + expect(doc.updateTime).to.not.be.null; + + expect(doc.createTime!.toDate().valueOf()).to.approximately( + (beginDocCreation + endDocCreation) / 2, + timestampDeltaMS + ); + expect(doc.updateTime!.toDate().valueOf()).to.approximately( + (beginDocCreation + endDocCreation) / 2, + timestampDeltaMS + ); + expect(doc.createTime?.valueOf()).to.equal(doc.updateTime?.valueOf()); + }); + + const wb = writeBatch(firestore); + snapshot.results.forEach(doc => { + wb.update(doc.ref!, { newField: 'value' }); + }); + await wb.commit(); + + snapshot = await execute(pipeline); + expect(snapshot.results.length).to.equal(10); + snapshot.results.forEach(doc => { + expect(doc.createTime).to.not.be.null; + expect(doc.updateTime).to.not.be.null; + expect(doc.createTime!.toDate().valueOf()).to.be.lessThan( + doc.updateTime!.toDate().valueOf() + ); + }); + }); + + it('returns execution time for an aggregate query', async () => { + const start = new Date().valueOf(); + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .aggregate(average('rating').as('avgRating')); + + const snapshot = await execute(pipeline); + const end = new Date().valueOf(); + + expect(snapshot.results.length).to.equal(1); + + expect(snapshot.executionTime.toDate().valueOf()).to.approximately( + (start + end) / 2, + timestampDeltaMS + ); + }); + + it('returns undefined create and update time for each result in an aggregate query', async () => { + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .aggregate({ + accumulators: [average('rating').as('avgRating')], + groups: ['genre'] + }); + + const snapshot = await execute(pipeline); + + expect(snapshot.results.length).to.equal(8); + + snapshot.results.forEach(doc => { + expect(doc.updateTime).to.be.undefined; + expect(doc.createTime).to.be.undefined; + }); + }); + }); + + describe('pipeline sources', () => { + it('supports CollectionReference as source', async () => { + const snapshot = await execute( + firestore.pipeline().collection(randomCol) + ); + expect(snapshot.results.length).to.equal(10); + }); + + it('supports list of documents as source', async () => { + const collName = randomCol.id; + + const snapshot = await execute( + firestore + .pipeline() + .documents([ + `${collName}/book1`, + doc(randomCol, 'book2'), + doc(randomCol, 'book3').path + ]) + ); + expect(snapshot.results.length).to.equal(3); + }); + + it('reject CollectionReference for another DB', async () => { + const db2 = getFirestore(firestore.app, 'notDefault'); + + expect(() => { + firestore.pipeline().collection(collection(db2, 'foo')); + }).to.throw(/Invalid CollectionReference/); + + await terminate(db2); + }); + + it('reject DocumentReference for another DB', async () => { + const db2 = getFirestore(firestore.app, 'notDefault'); + + expect(() => { + firestore.pipeline().documents([doc(db2, 'foo/bar')]); + }).to.throw(/Invalid DocumentReference/); + + await terminate(db2); + }); + + it('supports collection group as source', async () => { + const randomSubCollectionId = Math.random().toString(16).slice(2); + const doc1 = await addDoc( + collection(randomCol, 'book1', randomSubCollectionId), + { order: 1 } + ); + const doc2 = await addDoc( + collection(randomCol, 'book2', randomSubCollectionId), + { order: 2 } + ); + const snapshot = await execute( + firestore + .pipeline() + .collectionGroup(randomSubCollectionId) + .sort(ascending('order')) + ); + expectResults(snapshot, doc1.id, doc2.id); + }); + + it('supports database as source', async () => { + const randomId = Math.random().toString(16).slice(2); + const doc1 = await addDoc(collection(randomCol, 'book1', 'sub'), { + order: 1, + randomId + }); + const doc2 = await addDoc(collection(randomCol, 'book2', 'sub'), { + order: 2, + randomId + }); + const snapshot = await execute( + firestore + .pipeline() + .database() + .where(equal('randomId', randomId)) + .sort(ascending('order')) + ); + expectResults(snapshot, doc1.id, doc2.id); + }); + + it('can create pipeline from a query', async () => { + const snapshot = await execute( + firestore + .pipeline() + .createFrom(randomCol) + .sort(field('__name__').ascending()) + .limit(1) + ); + expectResults(snapshot, 'book1'); + }); + }); + + describe('supported data types', () => { + it('accepts and returns all data types', async () => { + const refDate = new Date(); + const refTimestamp = Timestamp.now(); + const constants = [ + constant(1).as('number'), + constant('a string').as('string'), + constant(true).as('boolean'), + constant(null).as('null'), + constant(new GeoPoint(0.1, 0.2)).as('geoPoint'), + constant(refTimestamp).as('timestamp'), + constant(refDate).as('date'), + constant( + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])) + ).as('bytes'), + constant(doc(firestore, 'foo', 'bar')).as('documentReference'), + constant(vector([1, 2, 3])).as('vectorValue'), + map({ + 'number': 1, + 'string': 'a string', + 'boolean': true, + 'null': null, + 'geoPoint': new GeoPoint(0.1, 0.2), + 'timestamp': refTimestamp, + 'date': refDate, + 'uint8Array': Bytes.fromUint8Array( + new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]) + ), + 'documentReference': doc(firestore, 'foo', 'bar'), + 'vectorValue': vector([1, 2, 3]), + 'map': { + 'number': 2, + 'string': 'b string' + }, + 'array': [1, 'c string'] + }).as('map'), + array([ + 1, + 'a string', + true, + null, + new GeoPoint(0.1, 0.2), + refTimestamp, + refDate, + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])), + doc(firestore, 'foo', 'bar'), + vector([1, 2, 3]), + { + 'number': 2, + 'string': 'b string' + } + ]).as('array') + ]; + + const snapshots = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(constants[0], ...constants.slice(1)) + ); + + expectResults(snapshots, { + 'number': 1, + 'string': 'a string', + 'boolean': true, + 'null': null, + 'geoPoint': new GeoPoint(0.1, 0.2), + 'timestamp': refTimestamp, + 'date': Timestamp.fromDate(refDate), + 'bytes': Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])), + 'documentReference': doc(firestore, 'foo', 'bar'), + 'vectorValue': vector([1, 2, 3]), + 'map': { + 'number': 1, + 'string': 'a string', + 'boolean': true, + 'null': null, + 'geoPoint': new GeoPoint(0.1, 0.2), + 'timestamp': refTimestamp, + 'date': Timestamp.fromDate(refDate), + 'uint8Array': Bytes.fromUint8Array( + new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]) + ), + 'documentReference': doc(firestore, 'foo', 'bar'), + 'vectorValue': vector([1, 2, 3]), + 'map': { + 'number': 2, + 'string': 'b string' + }, + 'array': [1, 'c string'] + }, + 'array': [ + 1, + 'a string', + true, + null, + new GeoPoint(0.1, 0.2), + refTimestamp, + Timestamp.fromDate(refDate), + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])), + doc(firestore, 'foo', 'bar'), + vector([1, 2, 3]), + { + 'number': 2, + 'string': 'b string' + } + ] + }); + }); + + it('throws on undefined in a map', async () => { + expect(() => { + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + map({ + 'number': 1, + undefined + }).as('foo') + ); + }).to.throw( + 'Function map() called with invalid data. Unsupported field value: undefined' + ); + }); + + it('throws on undefined in an array', async () => { + expect(() => { + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(array([1, undefined]).as('foo')); + }).to.throw( + 'Function array() called with invalid data. Unsupported field value: undefined' + ); + }); + + it('converts arrays and plain objects to functionValues if the customer intent is unspecified', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + 'title', + 'author', + 'genre', + 'rating', + 'published', + 'tags', + 'awards' + ) + .addFields( + array([ + 1, + 2, + field('genre'), + multiply('rating', 10), + [field('title')], + { + published: field('published') + } + ]).as('metadataArray'), + map({ + genre: field('genre'), + rating: multiply('rating', 10), + nestedArray: [field('title')], + nestedMap: { + published: field('published') + } + }).as('metadata') + ) + .where( + and( + equal('metadataArray', [ + 1, + 2, + field('genre'), + multiply('rating', 10), + [field('title')], + { + published: field('published') + } + ]), + equal('metadata', { + genre: field('genre'), + rating: multiply('rating', 10), + nestedArray: [field('title')], + nestedMap: { + published: field('published') + } + }) + ) + ) + ); + + expect(snapshot.results.length).to.equal(1); + + expectResults(snapshot, { + title: 'The Lord of the Rings', + author: 'J.R.R. Tolkien', + genre: 'Fantasy', + published: 1954, + rating: 4.7, + tags: ['adventure', 'magic', 'epic'], + awards: { hugo: false, nebula: false }, + metadataArray: [ + 1, + 2, + 'Fantasy', + 47, + ['The Lord of the Rings'], + { + published: 1954 + } + ], + metadata: { + genre: 'Fantasy', + rating: 47, + nestedArray: ['The Lord of the Rings'], + nestedMap: { + published: 1954 + } + } + }); + }); + + it('supports boolean value constants as a BooleanExpression', async () => { + const snapshots = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + conditional(constant(true), constant('TRUE'), constant('FALSE')).as( + 'true' + ), + conditional( + constant(false), + constant('TRUE'), + constant('FALSE') + ).as('false') + ) + ); + + expectResults(snapshots, { + 'true': 'TRUE', + 'false': 'FALSE' + }); + }); + }); + + describe('stages', () => { + describe('aggregate stage', () => { + it('supports aggregate', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countAll().as('count')) + ); + expectResults(snapshot, { count: 10 }); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('genre', 'Science Fiction')) + .aggregate( + countAll().as('count'), + average('rating').as('avgRating'), + maximum('rating').as('maxRating'), + sum('rating').as('sumRating') + ) + ); + expectResults(snapshot, { + count: 2, + avgRating: 4.4, + maxRating: 4.6, + sumRating: 8.8 + }); + }); + + it('supports aggregate options', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate({ + accumulators: [countAll().as('count')] + }) + ); + expectResults(snapshot, { count: 10 }); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('genre', 'Science Fiction')) + .aggregate( + countAll().as('count'), + average('rating').as('avgRating'), + maximum('rating').as('maxRating'), + sum('rating').as('sumRating') + ) + ); + expectResults(snapshot, { + count: 2, + avgRating: 4.4, + maxRating: 4.6, + sumRating: 8.8 + }); + }); + + it('rejects groups without accumulators', async () => { + await expect( + execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(lessThan('published', 1900)) + .aggregate({ + accumulators: [], + groups: ['genre'] + }) + ) + ).to.be.rejected; + }); + + it('returns group and accumulate results', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(lessThan(field('published'), 1984)) + .aggregate({ + accumulators: [average('rating').as('avgRating')], + groups: ['genre'] + }) + .where(greaterThan('avgRating', 4.3)) + .sort(field('avgRating').descending()) + ); + expectResults( + snapshot, + { avgRating: 4.7, genre: 'Fantasy' }, + { avgRating: 4.5, genre: 'Romance' }, + { avgRating: 4.4, genre: 'Science Fiction' } + ); + }); + + it('returns minimum, maximum, count, and countAll accumulations', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate( + count('cost').as('booksWithCost'), + countAll().as('count'), + maximum('rating').as('maxRating'), + minimum('published').as('minPublished') + ) + ); + expectResults(snapshot, { + booksWithCost: 1, + count: 10, + maxRating: 4.7, + minPublished: 1813 + }); + }); + + it('returns countif accumulation', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countIf(field('rating').greaterThan(4.3)).as('count')) + ); + const expectedResults = { + count: 3 + }; + expectResults(snapshot, expectedResults); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(field('rating').greaterThan(4.3).countIf().as('count')) + ); + expectResults(snapshot, expectedResults); + }); + + it('returns countDistinct accumulation', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countDistinct('genre').as('distinctGenres')) + ); + expectResults(snapshot, { distinctGenres: 8 }); + }); + }); + + describe('distinct stage', () => { + it('returns distinct values as expected', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .distinct('genre', 'author') + .sort(field('genre').ascending(), field('author').ascending()) + ); + expectResults( + snapshot, + { genre: 'Dystopian', author: 'George Orwell' }, + { genre: 'Dystopian', author: 'Margaret Atwood' }, + { genre: 'Fantasy', author: 'J.R.R. Tolkien' }, + { genre: 'Magical Realism', author: 'Gabriel García Márquez' }, + { genre: 'Modernist', author: 'F. Scott Fitzgerald' }, + { genre: 'Psychological Thriller', author: 'Fyodor Dostoevsky' }, + { genre: 'Romance', author: 'Jane Austen' }, + { genre: 'Science Fiction', author: 'Douglas Adams' }, + { genre: 'Science Fiction', author: 'Frank Herbert' }, + { genre: 'Southern Gothic', author: 'Harper Lee' } + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .distinct('genre', 'author') + .sort({ + orderings: [ + field('genre').ascending(), + field('author').ascending() + ] + }) + ); + expectResults( + snapshot, + { genre: 'Dystopian', author: 'George Orwell' }, + { genre: 'Dystopian', author: 'Margaret Atwood' }, + { genre: 'Fantasy', author: 'J.R.R. Tolkien' }, + { genre: 'Magical Realism', author: 'Gabriel García Márquez' }, + { genre: 'Modernist', author: 'F. Scott Fitzgerald' }, + { genre: 'Psychological Thriller', author: 'Fyodor Dostoevsky' }, + { genre: 'Romance', author: 'Jane Austen' }, + { genre: 'Science Fiction', author: 'Douglas Adams' }, + { genre: 'Science Fiction', author: 'Frank Herbert' }, + { genre: 'Southern Gothic', author: 'Harper Lee' } + ); + }); + }); + + describe('select stage', () => { + it('can select fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .sort(field('author').ascending()) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams' + }, + { title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' }, + { title: 'Dune', author: 'Frank Herbert' }, + { title: 'Crime and Punishment', author: 'Fyodor Dostoevsky' }, + { + title: 'One Hundred Years of Solitude', + author: 'Gabriel García Márquez' + }, + { title: '1984', author: 'George Orwell' }, + { title: 'To Kill a Mockingbird', author: 'Harper Lee' }, + { title: 'The Lord of the Rings', author: 'J.R.R. Tolkien' }, + { title: 'Pride and Prejudice', author: 'Jane Austen' }, + { title: "The Handmaid's Tale", author: 'Margaret Atwood' } + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select({ selections: ['title', field('author').as('auth0r')] }) + .sort(field('auth0r').ascending()) + .limit(2) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + auth0r: 'Douglas Adams' + }, + { title: 'The Great Gatsby', auth0r: 'F. Scott Fitzgerald' } + ); + }); + }); + + describe('addField stage', () => { + it('can add fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .addFields(constant('bar').as('foo')) + .sort(field('author').ascending()) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + foo: 'bar' + }, + { + title: 'The Great Gatsby', + author: 'F. Scott Fitzgerald', + foo: 'bar' + }, + { title: 'Dune', author: 'Frank Herbert', foo: 'bar' }, + { + title: 'Crime and Punishment', + author: 'Fyodor Dostoevsky', + foo: 'bar' + }, + { + title: 'One Hundred Years of Solitude', + author: 'Gabriel García Márquez', + foo: 'bar' + }, + { title: '1984', author: 'George Orwell', foo: 'bar' }, + { + title: 'To Kill a Mockingbird', + author: 'Harper Lee', + foo: 'bar' + }, + { + title: 'The Lord of the Rings', + author: 'J.R.R. Tolkien', + foo: 'bar' + }, + { title: 'Pride and Prejudice', author: 'Jane Austen', foo: 'bar' }, + { + title: "The Handmaid's Tale", + author: 'Margaret Atwood', + foo: 'bar' + } + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .addFields({ + fields: [constant('bar').as('foo')] + }) + .sort(field('author').ascending()) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + foo: 'bar' + }, + { + title: 'The Great Gatsby', + author: 'F. Scott Fitzgerald', + foo: 'bar' + }, + { title: 'Dune', author: 'Frank Herbert', foo: 'bar' }, + { + title: 'Crime and Punishment', + author: 'Fyodor Dostoevsky', + foo: 'bar' + }, + { + title: 'One Hundred Years of Solitude', + author: 'Gabriel García Márquez', + foo: 'bar' + }, + { title: '1984', author: 'George Orwell', foo: 'bar' }, + { + title: 'To Kill a Mockingbird', + author: 'Harper Lee', + foo: 'bar' + }, + { + title: 'The Lord of the Rings', + author: 'J.R.R. Tolkien', + foo: 'bar' + }, + { title: 'Pride and Prejudice', author: 'Jane Austen', foo: 'bar' }, + { + title: "The Handmaid's Tale", + author: 'Margaret Atwood', + foo: 'bar' + } + ); + }); + }); + + describe('removeFields stage', () => { + it('can remove fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .sort(field('author').ascending()) + .removeFields(field('author')) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + title: 'The Great Gatsby' + }, + { title: 'Dune' }, + { + title: 'Crime and Punishment' + }, + { + title: 'One Hundred Years of Solitude' + }, + { title: '1984' }, + { + title: 'To Kill a Mockingbird' + }, + { + title: 'The Lord of the Rings' + }, + { title: 'Pride and Prejudice' }, + { + title: "The Handmaid's Tale" + } + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'genre') + .sort(field('author').ascending()) + .removeFields({ + fields: [field('author'), 'genre'] + }) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + title: 'The Great Gatsby' + }, + { title: 'Dune' }, + { + title: 'Crime and Punishment' + }, + { + title: 'One Hundred Years of Solitude' + }, + { title: '1984' }, + { + title: 'To Kill a Mockingbird' + }, + { + title: 'The Lord of the Rings' + }, + { title: 'Pride and Prejudice' }, + { + title: "The Handmaid's Tale" + } + ); + }); + }); + + describe('findNearest stage', () => { + it('can find nearest', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .sort(field('author').ascending()) + .removeFields(field('author')) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + title: 'The Great Gatsby' + }, + { title: 'Dune' }, + { + title: 'Crime and Punishment' + }, + { + title: 'One Hundred Years of Solitude' + }, + { title: '1984' }, + { + title: 'To Kill a Mockingbird' + }, + { + title: 'The Lord of the Rings' + }, + { title: 'Pride and Prejudice' }, + { + title: "The Handmaid's Tale" + } + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'genre') + .sort(field('author').ascending()) + .removeFields({ + fields: [field('author'), 'genre'] + }) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + title: 'The Great Gatsby' + }, + { title: 'Dune' }, + { + title: 'Crime and Punishment' + }, + { + title: 'One Hundred Years of Solitude' + }, + { title: '1984' }, + { + title: 'To Kill a Mockingbird' + }, + { + title: 'The Lord of the Rings' + }, + { title: 'Pride and Prejudice' }, + { + title: "The Handmaid's Tale" + } + ); + }); + }); + + describe('where stage', () => { + it('where with and (2 conditions)', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + and( + greaterThan('rating', 4.5), + equalAny('genre', ['Science Fiction', 'Romance', 'Fantasy']) + ) + ) + ); + expectResults(snapshot, 'book10', 'book4'); + }); + + it('where with and (3 conditions)', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + and( + greaterThan('rating', 4.5), + equalAny('genre', ['Science Fiction', 'Romance', 'Fantasy']), + lessThan('published', 1965) + ) + ) + ); + expectResults(snapshot, 'book4'); + }); + + it('where with or', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + or( + equal('genre', 'Romance'), + equal('genre', 'Dystopian'), + equal('genre', 'Fantasy') + ) + ) + .sort(ascending('title')) + .select('title') + ); + expectResults( + snapshot, + { title: '1984' }, + { title: 'Pride and Prejudice' }, + { title: "The Handmaid's Tale" }, + { title: 'The Lord of the Rings' } + ); + }); + + it('where with xor', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + xor( + equal('genre', 'Romance'), + equal('genre', 'Dystopian'), + equal('genre', 'Fantasy'), + equal('published', 1949) + ) + ) + .select('title') + ); + expectResults( + snapshot, + { title: 'Pride and Prejudice' }, + { title: 'The Lord of the Rings' }, + { title: "The Handmaid's Tale" } + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where({ + condition: and( + greaterThan('rating', 4.5), + equalAny('genre', ['Science Fiction', 'Romance', 'Fantasy']) + ) + }) + ); + expectResults(snapshot, 'book10', 'book4'); + }); + }); + + describe('sort, offset, and limit stages', () => { + it('supports sort, offset, and limits', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('author').ascending()) + .offset(5) + .limit(3) + .select('title', 'author') + ); + expectResults( + snapshot, + { title: '1984', author: 'George Orwell' }, + { title: 'To Kill a Mockingbird', author: 'Harper Lee' }, + { title: 'The Lord of the Rings', author: 'J.R.R. Tolkien' } + ); + }); + + it('sort, offset, and limit stages support options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort({ + orderings: [field('author').ascending()] + }) + .offset({ offset: 5 }) + .limit({ limit: 3 }) + .select('title', 'author') + ); + expectResults( + snapshot, + { title: '1984', author: 'George Orwell' }, + { title: 'To Kill a Mockingbird', author: 'Harper Lee' }, + { title: 'The Lord of the Rings', author: 'J.R.R. Tolkien' } + ); + }); + }); + + describe('raw stage', () => { + it('can select fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .rawStage('select', [ + { + title: field('title'), + metadata: { + author: field('author') + } + } + ]) + .sort(field('author').ascending()) + .limit(1) + ); + expectResults(snapshot, { + metadata: { + author: 'Frank Herbert' + }, + title: 'Dune' + }); + }); + + it('can add fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('author').ascending()) + .limit(1) + .select('title', 'author') + .rawStage('add_fields', [ + { + display: stringConcat('title', ' - ', field('author')) + } + ]) + ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + display: "The Hitchhiker's Guide to the Galaxy - Douglas Adams" + }); + }); + + it('can filter with where', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .rawStage('where', [field('author').equal('Douglas Adams')]) + ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams' + }); + }); + + it('can limit, offset, and sort', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .rawStage('sort', [ + { + direction: 'ascending', + expression: field('author') + } + ]) + .rawStage('offset', [3]) + .rawStage('limit', [1]) + ); + expectResults(snapshot, { + author: 'Fyodor Dostoevsky', + title: 'Crime and Punishment' + }); + }); + + it('can perform aggregate query', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'rating') + .rawStage('aggregate', [ + { averageRating: field('rating').average() }, + {} + ]) + ); + expectResults(snapshot, { + averageRating: 4.3100000000000005 + }); + }); + + it('can perform distinct query', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'rating') + .rawStage('distinct', [{ rating: field('rating') }]) + .sort(field('rating').descending()) + ); + expectResults( + snapshot, + { + rating: 4.7 + }, + { + rating: 4.6 + }, + { + rating: 4.5 + }, + { + rating: 4.3 + }, + { + rating: 4.2 + }, + { + rating: 4.1 + }, + { + rating: 4.0 + } + ); + }); + + it('can perform FindNearest query', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol) + .rawStage( + 'find_nearest', + [ + field('embedding'), + vector([10, 1, 2, 1, 1, 1, 1, 1, 1, 1]), + 'euclidean' + ], + { + 'distance_field': field('computedDistance'), + limit: 2 + } + ) + .select('title', 'computedDistance') + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + computedDistance: 1 + }, + { + title: 'One Hundred Years of Solitude', + computedDistance: 12.041594578792296 + } + ); + }); + }); + + describe('replaceWith stage', () => { + it('run pipeline with replaceWith field name', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .replaceWith('awards') + ); + expectResults(snapshot, { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }); + }); + + it('run pipeline with replaceWith Expr result', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .replaceWith( + map({ + foo: 'bar', + baz: { + title: field('title') + } + }) + ) + ); + expectResults(snapshot, { + foo: 'bar', + baz: { title: "The Hitchhiker's Guide to the Galaxy" } + }); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .replaceWith({ map: 'awards' }) + ); + expectResults(snapshot, { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }); + }); + }); + + describe('sample stage', () => { + it('run pipeline with sample limit of 3', async () => { + const snapshot = await execute( + firestore.pipeline().collection(randomCol.path).sample(3) + ); + expect(snapshot.results.length).to.equal(3); + }); + + it('run pipeline with sample limit of {documents: 3}', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sample({ documents: 3 }) + ); + expect(snapshot.results.length).to.equal(3); + }); + + it('run pipeline with sample limit of {percentage: 0.6}', async () => { + let avgSize = 0; + const numIterations = 30; + for (let i = 0; i < numIterations; i++) { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sample({ percentage: 0.6 }) + ); + + avgSize += snapshot.results.length; + } + avgSize /= numIterations; + expect(avgSize).to.be.closeTo(6, 1); + }); + }); + + describe('union stage', () => { + it('run pipeline with union', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .union(firestore.pipeline().collection(randomCol.path)) + .sort(field(documentIdFieldPath()).ascending()) + ); + expectResults( + snapshot, + 'book1', + 'book1', + 'book10', + 'book10', + 'book2', + 'book2', + 'book3', + 'book3', + 'book4', + 'book4', + 'book5', + 'book5', + 'book6', + 'book6', + 'book7', + 'book7', + 'book8', + 'book8', + 'book9', + 'book9' + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .union({ other: firestore.pipeline().collection(randomCol.path) }) + .sort(field(documentIdFieldPath()).ascending()) + ); + expectResults( + snapshot, + 'book1', + 'book1', + 'book10', + 'book10', + 'book2', + 'book2', + 'book3', + 'book3', + 'book4', + 'book4', + 'book5', + 'book5', + 'book6', + 'book6', + 'book7', + 'book7', + 'book8', + 'book8', + 'book9', + 'book9' + ); + }); + }); + + describe('unnest stage', () => { + it('run pipeline with unnest', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .unnest(field('tags').as('tag')) + .select( + 'title', + 'author', + 'genre', + 'published', + 'rating', + 'tags', + 'tag', + 'awards', + 'nestedField' + ) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'comedy', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'space', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'adventure', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + } + ); + }); + + it('unnest with index field', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .unnest(field('tags').as('tag'), 'tagsIndex') + .select( + 'title', + 'author', + 'genre', + 'published', + 'rating', + 'tags', + 'tag', + 'awards', + 'nestedField', + 'tagsIndex' + ) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'comedy', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 0 + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'space', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 1 + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'adventure', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 2 + } + ); + }); + + it('unnest an expr', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .unnest(array([1, 2, 3]).as('copy')) + .select( + 'title', + 'author', + 'genre', + 'published', + 'rating', + 'tags', + 'copy', + 'awards', + 'nestedField' + ) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + copy: 1, + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + copy: 2, + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + copy: 3, + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + } + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .unnest({ + selectable: field('tags').as('tag'), + indexField: 'tagsIndex' + }) + .select( + 'title', + 'author', + 'genre', + 'published', + 'rating', + 'tags', + 'tag', + 'awards', + 'nestedField', + 'tagsIndex' + ) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'comedy', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 0 + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'space', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 1 + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'adventure', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 2 + } + ); + }); + }); + + describe('findNearest stage', () => { + it('run pipeline with findNearest', async () => { + const measures: Array = [ + 'euclidean', + 'dot_product', + 'cosine' + ]; + for (const measure of measures) { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol) + .findNearest({ + field: 'embedding', + vectorValue: vector([10, 1, 3, 1, 2, 1, 1, 1, 1, 1]), + limit: 3, + distanceMeasure: measure + }) + .select('title') + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + title: 'One Hundred Years of Solitude' + }, + { + title: "The Handmaid's Tale" + } + ); + } + }); + + it('optionally returns the computed distance', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol) + .findNearest({ + field: 'embedding', + vectorValue: vector([10, 1, 2, 1, 1, 1, 1, 1, 1, 1]), + limit: 2, + distanceMeasure: 'euclidean', + distanceField: 'computedDistance' + }) + .select('title', 'computedDistance') + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + computedDistance: 1 + }, + { + title: 'One Hundred Years of Solitude', + computedDistance: 12.041594578792296 + } + ); + }); + }); + }); + + describe('error handling', () => { + it('error properties are propagated from the firestore backend', async () => { + try { + const myPipeline = firestore + .pipeline() + .collection(randomCol.path) + .rawStage('select', [ + // incorrect parameter type + field('title') + ]); + + await execute(myPipeline); + + expect.fail('expected pipeline.execute() to throw'); + } catch (e: unknown) { + expect(e instanceof FirebaseError).to.be.true; + const err = e as FirebaseError; + // Backend returns the code as `failed-precondition` when using the REST transport + expect(err['code']).to.equal('failed-precondition'); + expect(typeof err['message']).to.equal('string'); + + expect(err['message']).to.match( + /Request failed with error: Expected value type of MAP_VALUE when parsing 'fields' but received FIELD_REFERENCE_VALUE instead/ + ); + } + }); + }); + + describe('function expressions', () => { + it('logical max works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + 'title', + logicalMaximum(constant(1960), field('published'), 1961).as( + 'published-safe' + ) + ) + .sort(field('title').ascending()) + .limit(3) + ); + expectResults( + snapshot, + { title: '1984', 'published-safe': 1961 }, + { title: 'Crime and Punishment', 'published-safe': 1961 }, + { title: 'Dune', 'published-safe': 1965 } + ); + }); + + it('logical min works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + 'title', + logicalMinimum(constant(1960), field('published'), 1961).as( + 'published-safe' + ) + ) + .sort(field('title').ascending()) + .limit(3) + ); + expectResults( + snapshot, + { title: '1984', 'published-safe': 1949 }, + { title: 'Crime and Punishment', 'published-safe': 1866 }, + { title: 'Dune', 'published-safe': 1960 } + ); + }); + + it('conditional works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + 'title', + conditional( + lessThan(field('published'), 1960), + constant(1960), + field('published') + ).as('published-safe'), + field('rating') + .greaterThanOrEqual(4.5) + .conditional(constant('great'), constant('good')) + .as('rating') + ) + .sort(field('title').ascending()) + .limit(3) + ); + expectResults( + snapshot, + { title: '1984', 'published-safe': 1960, rating: 'good' }, + { + title: 'Crime and Punishment', + 'published-safe': 1960, + rating: 'good' + }, + { title: 'Dune', 'published-safe': 1965, rating: 'great' } + ); + }); + + it('equalAny works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equalAny('published', [1979, 1999, 1967])) + .sort(descending('title')) + .select('title') + ); + expectResults( + snapshot, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'One Hundred Years of Solitude' } + ); + }); + + it('notEqualAny works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + notEqualAny( + 'published', + [1965, 1925, 1949, 1960, 1866, 1985, 1954, 1967, 1979] + ) + ) + .select('title') + ); + expectResults(snapshot, { title: 'Pride and Prejudice' }); + }); + + it('arrayContains works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(arrayContains('tags', 'comedy')) + .select('title') + ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy" + }); + }); + + it('arrayContainsAny works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(arrayContainsAny('tags', ['comedy', 'classic'])) + .sort(descending('title')) + .select('title') + ); + expectResults( + snapshot, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'Pride and Prejudice' } + ); + }); + + it('arrayContainsAll works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(arrayContainsAll('tags', ['adventure', 'magic'])) + .select('title') + ); + expectResults(snapshot, { title: 'The Lord of the Rings' }); + }); + + it('arrayLength works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select(arrayLength('tags').as('tagsCount')) + .where(equal('tagsCount', 3)) + ); + expect(snapshot.results.length).to.equal(10); + }); + + it('testStrConcat', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('author')) + .select( + field('author').stringConcat(' - ', field('title')).as('bookInfo') + ) + .limit(1) + ); + expectResults(snapshot, { + bookInfo: "Douglas Adams - The Hitchhiker's Guide to the Galaxy" + }); + }); + + it('testStartsWith', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(startsWith('title', 'The')) + .select('title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { title: 'The Great Gatsby' }, + { title: "The Handmaid's Tale" }, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'The Lord of the Rings' } + ); + }); + + it('testEndsWith', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(endsWith('title', 'y')) + .select('title') + .sort(field('title').descending()) + ); + expectResults( + snapshot, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'The Great Gatsby' } + ); + }); + + it('testStrContains', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(stringContains('title', "'s")) + .select('title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { title: "The Handmaid's Tale" }, + { title: "The Hitchhiker's Guide to the Galaxy" } + ); + }); + + it('testLength', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select(charLength('title').as('titleLength'), field('title')) + .where(greaterThan('titleLength', 20)) + .sort(field('title').ascending()) + ); + + expectResults( + snapshot, + + { + titleLength: 29, + title: 'One Hundred Years of Solitude' + }, + { + titleLength: 36, + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + titleLength: 21, + title: 'The Lord of the Rings' + }, + { + titleLength: 21, + title: 'To Kill a Mockingbird' + } + ); + }); + + it('testLike', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(like('title', '%Guide%')) + .select('title') + ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy" + }); + }); + + it('testRegexContains', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(regexContains('title', '(?i)(the|of)')) + ); + expect(snapshot.results.length).to.equal(5); + }); + + it('testRegexMatches', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(regexMatch('title', '.*(?i)(the|of).*')) + ); + expect(snapshot.results.length).to.equal(5); + }); + + it('testArithmeticOperations', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', 'To Kill a Mockingbird')) + .select( + add(field('rating'), 1).as('ratingPlusOne'), + subtract(field('published'), 1900).as('yearsSince1900'), + field('rating').multiply(10).as('ratingTimesTen'), + divide('rating', 2).as('ratingDividedByTwo'), + multiply('rating', 20).as('ratingTimes20'), + add('rating', 3).as('ratingPlus3'), + mod('rating', 2).as('ratingMod2') + ) + .limit(1) + ); + expectResults(snapshot, { + ratingPlusOne: 5.2, + yearsSince1900: 60, + ratingTimesTen: 42, + ratingDividedByTwo: 2.1, + ratingTimes20: 84, + ratingPlus3: 7.2, + ratingMod2: 0.20000000000000018 + }); + }); + + it('testComparisonOperators', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + and( + greaterThan('rating', 4.2), + lessThanOrEqual(field('rating'), 4.5), + notEqual('genre', 'Science Fiction') + ) + ) + .select('rating', 'title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { rating: 4.3, title: 'Crime and Punishment' }, + { + rating: 4.3, + title: 'One Hundred Years of Solitude' + }, + { rating: 4.5, title: 'Pride and Prejudice' } + ); + }); + + it('testLogicalOperators', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + or( + and( + greaterThan('rating', 4.5), + equal('genre', 'Science Fiction') + ), + lessThan('published', 1900) + ) + ) + .select('title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { title: 'Crime and Punishment' }, + { title: 'Dune' }, + { title: 'Pride and Prejudice' } + ); + }); + + it('testChecks', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + equal('rating', null).as('ratingIsNull'), + equal('rating', NaN).as('ratingIsNaN'), + isError(divide(constant(1), constant(0))).as('isError'), + ifError(divide(constant(1), constant(0)), constant('was error')).as( + 'ifError' + ), + ifError( + divide(constant(1), constant(0)).greaterThan(1), + constant(true) + ) + .not() + .as('ifErrorBooleanExpression'), + isAbsent('foo').as('isAbsent'), + notEqual('title', null).as('titleIsNotNull'), + notEqual('cost', NaN).as('costIsNotNan'), + exists('fooBarBaz').as('fooBarBazExists'), + field('title').exists().as('titleExists') + ) + ); + expectResults(snapshot, { + ratingIsNull: false, + ratingIsNaN: false, + isError: true, + ifError: 'was error', + ifErrorBooleanExpression: false, + isAbsent: true, + titleIsNotNull: true, + costIsNotNan: false, + fooBarBazExists: false, + titleExists: true + }); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + field('rating').equal(null).as('ratingIsNull'), + field('rating').equal(NaN).as('ratingIsNaN'), + divide(constant(1), constant(0)).isError().as('isError'), + divide(constant(1), constant(0)) + .ifError(constant('was error')) + .as('ifError'), + divide(constant(1), constant(0)) + .greaterThan(1) + .ifError(constant(true)) + .not() + .as('ifErrorBooleanExpression'), + field('foo').isAbsent().as('isAbsent'), + field('title').notEqual(null).as('titleIsNotNull'), + field('cost').notEqual(NaN).as('costIsNotNan') + ) + ); + expectResults(snapshot, { + ratingIsNull: false, + ratingIsNaN: false, + isError: true, + ifError: 'was error', + ifErrorBooleanExpression: false, + isAbsent: true, + titleIsNotNull: true, + costIsNotNan: false + }); + }); + + it('testMapGet', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('published').descending()) + .select( + field('awards').mapGet('hugo').as('hugoAward'), + field('awards').mapGet('others').as('others'), + field('title') + ) + .where(equal('hugoAward', true)) + ); + expectResults( + snapshot, + { + hugoAward: true, + title: "The Hitchhiker's Guide to the Galaxy", + others: { unknown: { year: 1980 } } + }, + { hugoAward: true, title: 'Dune' } + ); + }); + + it('testDistanceFunctions', async () => { + const sourceVector = vector([0.1, 0.1]); + const targetVector = vector([0.5, 0.8]); + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + cosineDistance(constant(sourceVector), targetVector).as( + 'cosineDistance' + ), + dotProduct(constant(sourceVector), targetVector).as( + 'dotProductDistance' + ), + euclideanDistance(constant(sourceVector), targetVector).as( + 'euclideanDistance' + ) + ) + .limit(1) + ); + + expectResults(snapshot, { + cosineDistance: 0.02560880430538015, + dotProductDistance: 0.13, + euclideanDistance: 0.806225774829855 + }); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + constant(sourceVector) + .cosineDistance(targetVector) + .as('cosineDistance'), + constant(sourceVector) + .dotProduct(targetVector) + .as('dotProductDistance'), + constant(sourceVector) + .euclideanDistance(targetVector) + .as('euclideanDistance') + ) + .limit(1) + ); + + expectResults(snapshot, { + cosineDistance: 0.02560880430538015, + dotProductDistance: 0.13, + euclideanDistance: 0.806225774829855 + }); + }); + + it('testVectorLength', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(vectorLength(constant(vector([1, 2, 3]))).as('vectorLength')) + ); + expectResults(snapshot, { + vectorLength: 3 + }); + }); + + it('testNestedFields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('awards.hugo', true)) + .sort(descending('title')) + .select('title', 'awards.hugo') + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + 'awards.hugo': true + }, + { title: 'Dune', 'awards.hugo': true } + ); + }); + + it('test mapGet with field name including . notation', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .replaceWith( + map({ + title: 'foo', + nested: { + level: { + '1': 'bar' + }, + 'level.1': { + 'level.2': 'baz' + } + } + }) + ) + .select( + 'title', + field('nested.level.1'), + mapGet('nested', 'level.1').mapGet('level.2').as('nested') + ) + ); + expectResults(snapshot, { + title: 'foo', + 'nested.level.`1`': 'bar', + nested: 'baz' + }); + }); + + describe('rawFunction', () => { + it('add selectable', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(descending('rating')) + .limit(1) + .select( + new FunctionExpression('add', [field('rating'), constant(1)]).as( + 'rating' + ) + ) + ); + expectResults(snapshot, { + rating: 5.7 + }); + }); + + it('and (variadic) selectable', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + new FunctionExpression('and', [ + field('rating').greaterThan(0), + field('title').charLength().lessThan(5), + field('tags').arrayContains('propaganda') + ]).asBoolean() + ) + .select('title') + ); + expectResults(snapshot, { + title: '1984' + }); + }); + + it('array contains any', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + new FunctionExpression('array_contains_any', [ + field('tags'), + array(['politics']) + ]).asBoolean() + ) + .select('title') + ); + expectResults(snapshot, { + title: 'Dune' + }); + }); + + it('countif aggregate', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate( + new AggregateFunction('count_if', [ + field('rating').greaterThanOrEqual(4.5) + ]).as('countOfBest') + ) + ); + expectResults(snapshot, { + countOfBest: 3 + }); + }); + + it('sort by char_len', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort( + new FunctionExpression('char_length', [ + field('title') + ]).ascending(), + descending('__name__') + ) + .limit(3) + .select('title') + ); + expectResults( + snapshot, + { + title: '1984' + }, + { + title: 'Dune' + }, + { + title: 'The Great Gatsby' + } + ); + }); + }); + + it('supports array', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(array([1, 2, 3, 4]).as('metadata')) + ); + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: [1, 2, 3, 4] + }); + }); + + it('evaluates expression in array', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + array([1, 2, field('genre'), multiply('rating', 10)]).as('metadata') + ) + ); + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: [1, 2, 'Fantasy', 47] + }); + }); + + it('supports arrayGet', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(3) + .select(arrayGet('tags', 0).as('firstTag')) + ); + const expectedResults = [ + { + firstTag: 'adventure' + }, + { + firstTag: 'politics' + }, + { + firstTag: 'classic' + } + ]; + expectResults(snapshot, ...expectedResults); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(3) + .select(field('tags').arrayGet(0).as('firstTag')) + ); + expectResults(snapshot, ...expectedResults); + }); + + it('supports map', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + map({ + foo: 'bar' + }).as('metadata') + ) + ); + + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: { + foo: 'bar' + } + }); + }); + + it('evaluates expression in map', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + map({ + genre: field('genre'), + rating: field('rating').multiply(10) + }).as('metadata') + ) + ); + + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: { + genre: 'Fantasy', + rating: 47 + } + }); + }); + + it('supports mapRemove', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(mapRemove('awards', 'hugo').as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false } + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('awards').mapRemove('hugo').as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false } + }); + }); + + it('supports mapMerge', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(mapMerge('awards', { fakeAward: true }).as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false, hugo: false, fakeAward: true } + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('awards').mapMerge({ fakeAward: true }).as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false, hugo: false, fakeAward: true } + }); + }); + + it('supports timestamp conversions', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + unixSecondsToTimestamp(constant(1741380235)).as( + 'unixSecondsToTimestamp' + ), + unixMillisToTimestamp(constant(1741380235123)).as( + 'unixMillisToTimestamp' + ), + unixMicrosToTimestamp(constant(1741380235123456)).as( + 'unixMicrosToTimestamp' + ), + timestampToUnixSeconds( + constant(new Timestamp(1741380235, 123456789)) + ).as('timestampToUnixSeconds'), + timestampToUnixMicros( + constant(new Timestamp(1741380235, 123456789)) + ).as('timestampToUnixMicros'), + timestampToUnixMillis( + constant(new Timestamp(1741380235, 123456789)) + ).as('timestampToUnixMillis') + ) + ); + expectResults(snapshot, { + unixMicrosToTimestamp: new Timestamp(1741380235, 123456000), + unixMillisToTimestamp: new Timestamp(1741380235, 123000000), + unixSecondsToTimestamp: new Timestamp(1741380235, 0), + timestampToUnixSeconds: 1741380235, + timestampToUnixMicros: 1741380235123456, + timestampToUnixMillis: 1741380235123 + }); + }); + + it('supports timestamp math', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(constant(new Timestamp(1741380235, 0)).as('timestamp')) + .select( + timestampAdd('timestamp', 'day', 10).as('plus10days'), + timestampAdd('timestamp', 'hour', 10).as('plus10hours'), + timestampAdd('timestamp', 'minute', 10).as('plus10minutes'), + timestampAdd('timestamp', 'second', 10).as('plus10seconds'), + timestampAdd('timestamp', 'microsecond', 10).as('plus10micros'), + timestampAdd('timestamp', 'millisecond', 10).as('plus10millis'), + timestampSubtract('timestamp', 'day', 10).as('minus10days'), + timestampSubtract('timestamp', 'hour', 10).as('minus10hours'), + timestampSubtract('timestamp', 'minute', 10).as('minus10minutes'), + timestampSubtract('timestamp', 'second', 10).as('minus10seconds'), + timestampSubtract('timestamp', 'microsecond', 10).as( + 'minus10micros' + ), + timestampSubtract('timestamp', 'millisecond', 10).as( + 'minus10millis' + ) + ) + ); + expectResults(snapshot, { + plus10days: new Timestamp(1742244235, 0), + plus10hours: new Timestamp(1741416235, 0), + plus10minutes: new Timestamp(1741380835, 0), + plus10seconds: new Timestamp(1741380245, 0), + plus10micros: new Timestamp(1741380235, 10000), + plus10millis: new Timestamp(1741380235, 10000000), + minus10days: new Timestamp(1740516235, 0), + minus10hours: new Timestamp(1741344235, 0), + minus10minutes: new Timestamp(1741379635, 0), + minus10seconds: new Timestamp(1741380225, 0), + minus10micros: new Timestamp(1741380234, 999990000), + minus10millis: new Timestamp(1741380234, 990000000) + }); + }).timeout(10000); + + it('supports byteLength', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol) + .limit(1) + .select( + constant( + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])) + ).as('bytes') + ) + .select(byteLength('bytes').as('byteLength')) + ); + + expectResults(snapshot, { + byteLength: 8 + }); + }); + + it('supports not', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol) + .limit(1) + .select(constant(true).as('trueField')) + .select('trueField', not(equal('trueField', true)).as('falseField')) + ); + + expectResults(snapshot, { + trueField: true, + falseField: false + }); + }); + + it('can reverse an array', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('tags').arrayReverse().as('reversedTags')) + ); + expectResults(snapshot, { + reversedTags: ['adventure', 'space', 'comedy'] + }); + }); + + it('can reverse an array with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(reverse('tags').as('reversedTags')) + ); + expectResults(snapshot, { + reversedTags: ['adventure', 'space', 'comedy'] + }); + }); + + it('can compute the ceiling of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').ceil().as('ceilingRating')) + ); + expectResults(snapshot, { + ceilingRating: 5 + }); + }); + + it('can compute the ceiling of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(ceil('rating').as('ceilingRating')) + ); + expectResults(snapshot, { + ceilingRating: 5 + }); + }); + + it('can compute the floor of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').floor().as('floorRating')) + ); + expectResults(snapshot, { + floorRating: 4 + }); + }); + + it('can compute the floor of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(floor('rating').as('floorRating')) + ); + expectResults(snapshot, { + floorRating: 4 + }); + }); + + it('can compute e to the power of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(field('rating').exp().as('expRating')) + ); + expect(snapshot.results[0].get('expRating')).to.be.approximately( + 109.94717245212352, + 0.00001 + ); + }); + + it('can compute e to the power of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(exp('rating').as('expRating')) + ); + expect(snapshot.results[0].get('expRating')).to.be.approximately( + 109.94717245212351, + 0.000001 + ); + }); + + it('can compute the power of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').pow(2).as('powerRating')) + ); + expect(snapshot.results[0].get('powerRating')).to.be.approximately( + 17.64, + 0.0001 + ); + }); + + it('can compute the power of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(pow('rating', 2).as('powerRating')) + ); + expect(snapshot.results[0].get('powerRating')).to.be.approximately( + 17.64, + 0.0001 + ); + }); + + it('can round a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').round().as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: 4 + }); + }); + + it('can round a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(round('rating').as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: 4 + }); + }); + + it('can round a numeric value away from zero for positive half-way values', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .addFields(constant(1.5).as('positiveHalf')) + .select(field('positiveHalf').round().as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: 2 + }); + }); + + it('can round a numeric value away from zero for negative half-way values', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .addFields(constant(-1.5).as('negativeHalf')) + .select(field('negativeHalf').round().as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: -2 + }); + }); + + it('can round a numeric value to specified precision', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .replaceWith( + map({ + foo: 4.123456 + }) + ) + .select( + field('foo').round(0).as('0'), + round('foo', 1).as('1'), + round('foo', constant(2)).as('2'), + round(field('foo'), 4).as('4') + ) + ); + expectResults(snapshot, { + '0': 4, + '1': 4.1, + '2': 4.12, + '4': 4.1235 + }); + }); + + it('can get the collectionId from a path', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(field('__name__').collectionId().as('collectionId')) + ); + expectResults(snapshot, { + collectionId: randomCol.id + }); + }); + + it('can get the collectionId from a path with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(collectionId('__name__').as('collectionId')) + ); + expectResults(snapshot, { + collectionId: randomCol.id + }); + }); + + it('can compute the length of a string value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('title').length().as('titleLength')) + ); + expectResults(snapshot, { + titleLength: 36 + }); + }); + + it('can compute the length of a string value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(length('title').as('titleLength')) + ); + expectResults(snapshot, { + titleLength: 36 + }); + }); + + it('can compute the length of an array value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('tags').length().as('tagsLength')) + ); + expectResults(snapshot, { + tagsLength: 3 + }); + }); + + it('can compute the length of an array value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(length('tags').as('tagsLength')) + ); + expectResults(snapshot, { + tagsLength: 3 + }); + }); + + it('can compute the length of a map value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('awards').length().as('awardsLength')) + ); + expectResults(snapshot, { + awardsLength: 3 + }); + }); + + it('can compute the length of a vector value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('embedding').length().as('embeddingLength')) + ); + expectResults(snapshot, { + embeddingLength: 10 + }); + }); + + it('can compute the length of a bytes value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select(constant('12é').as('value')) + .limit(1) + .select(field('value').byteLength().as('valueLength')) + ); + expectResults(snapshot, { + valueLength: 4 + }); + }); + + it('can compute the natural logarithm of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').ln().as('lnRating')) + ); + expect(snapshot.results[0]!.data().lnRating).to.be.closeTo(1.435, 0.001); + }); + + it('can compute the natural logarithm of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(ln('rating').as('lnRating')) + ); + expect(snapshot.results[0]!.data().lnRating).to.be.closeTo(1.435, 0.001); + }); + + it('can compute the natural logarithm of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(ln('rating').as('lnRating')) + ); + expectResults(snapshot, { + lnRating: 1.4350845252893227 + }); + }); + + it('can compute the logarithm of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(log(field('rating'), 10).as('logRating')) + ); + expectResults(snapshot, { + logRating: 0.6232492903979004 + }); + }); + + it('can compute the logarithm of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(log('rating', 10).as('logRating')) + ); + expectResults(snapshot, { + logRating: 0.6232492903979004 + }); + }); + + it('can round a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').round().as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: 4 + }); + }); + + it('can round a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(round('rating').as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: 4 + }); + }); + + it('can compute the square root of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').sqrt().as('sqrtRating')) + ); + expectResults(snapshot, { + sqrtRating: 2.04939015319192 + }); + }); + + it('can compute the square root of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(sqrt('rating').as('sqrtRating')) + ); + expectResults(snapshot, { + sqrtRating: 2.04939015319192 + }); + }); + + it('can reverse a string', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('title').reverse().as('reversedTitle')) + ); + expectResults(snapshot, { + reversedTitle: "yxalaG eht ot ediuG s'rekihhctiH ehT" + }); + }); + + it('can reverse a string with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(stringReverse('title').as('reversedTitle')) + ); + expectResults(snapshot, { + reversedTitle: "yxalaG eht ot ediuG s'rekihhctiH ehT" + }); + }); + + it('supports Document_id', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + documentId(field('__name__')).as('docId'), + documentId(field('__path__')).as('noDocId') + ) + ); + expectResults(snapshot, { + docId: 'book4', + noDocId: null + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('__name__').documentId().as('docId')) + ); + expectResults(snapshot, { + docId: 'book4' + }); + }); + + it('supports substring', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(substring('title', 9, 2).as('of')) + ); + expectResults(snapshot, { + of: 'of' + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('title').substring(9, 2).as('of')) + ); + expectResults(snapshot, { + of: 'of' + }); + }); + + it('supports substring without length', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(substring('title', 9).as('of')) + ); + expectResults(snapshot, { + of: 'of the Rings' + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('title').substring(9).as('of')) + ); + expectResults(snapshot, { + of: 'of the Rings' + }); + }); + + it('test toLower', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('title')) + .select(toLower('author').as('lowercaseAuthor')) + .limit(1) + ); + expectResults(snapshot, { + lowercaseAuthor: 'george orwell' + }); + }); + + it('test toUpper', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('title')) + .select(toUpper('author').as('uppercaseAuthor')) + .limit(1) + ); + expectResults(snapshot, { uppercaseAuthor: 'GEORGE ORWELL' }); + }); + + it('testTrim', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .addFields( + constant(" The Hitchhiker's Guide to the Galaxy ").as('spacedTitle') + ) + .select(trim('spacedTitle').as('trimmedTitle'), field('spacedTitle')) + .limit(1) + ); + expectResults(snapshot, { + spacedTitle: " The Hitchhiker's Guide to the Galaxy ", + trimmedTitle: "The Hitchhiker's Guide to the Galaxy" + }); + }); + + it('test reverse', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', '1984')) + .limit(1) + .select(reverse('title').as('reverseTitle')) + ); + expectResults(snapshot, { reverseTitle: '4891' }); + }); + + it('testAbs', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + constant(-10).as('neg10'), + constant(-22.22).as('neg22'), + constant(1).as('pos1') + ) + .select( + abs('neg10').as('10'), + abs(field('neg22')).as('22'), + field('pos1').as('1') + ) + ); + expectResults(snapshot, { + '10': 10, + '22': 22.22, + '1': 1 + }); + }); + + it('can compute the base-10 logarithm of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(field('rating').log10().as('log10Rating')) + ); + expect(snapshot.results[0]!.data().log10Rating).to.be.closeTo( + 0.672, + 0.001 + ); + }); + + it('can compute the base-10 logarithm of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(log10('rating').as('log10Rating')) + ); + expect(snapshot.results[0]!.data().log10Rating).to.be.closeTo( + 0.672, + 0.001 + ); + }); + + it('can concat fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .addFields( + concat('author', ' ', field('title')).as('display'), + field('author').concat(': ', field('title')).as('display2') + ) + .where(equal('author', 'Douglas Adams')) + .select('display', 'display2') + ); + expectResults(snapshot, { + display: "Douglas Adams The Hitchhiker's Guide to the Galaxy", + display2: "Douglas Adams: The Hitchhiker's Guide to the Galaxy" + }); + }); + + it('supports currentTimestamp', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .addFields(currentTimestamp().as('now')) + .select('now') + ); + const now = snapshot.results[0].get('now') as Timestamp; + expect(now).instanceof(Timestamp); + expect( + now.toDate().getUTCSeconds() - new Date().getUTCSeconds() + ).lessThan(5000); + }); + + it('supports ifAbsent', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .replaceWith( + map({ + title: 'foo' + }) + ) + .select( + ifAbsent('title', 'default title').as('title'), + field('name').ifAbsent('default name').as('name'), + field('name').ifAbsent(field('title')).as('nameOrTitle') + ) + ); + + expectResults(snapshot, { + title: 'foo', + name: 'default name', + nameOrTitle: 'foo' + }); + }); + + it('supports join', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .replaceWith( + map({ + tags: ['foo', 'bar', 'baz'], + delimeter: '|' + }) + ) + .select(join('tags', ',').as('csv'), field('tags').join('|').as('or')) + ); + + expectResults(snapshot, { + csv: 'foo,bar,baz', + or: 'foo|bar|baz' + }); + }); + + it('can compute the sum of the elements in an array', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .addFields(array([150, 200]).as('sales')) + .select(field('sales').arraySum().as('totalSales')) + ); + expectResults(snapshot, { + totalSales: 350 + }); + }); + + it('can compute the sum of the elements in an array with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .addFields(array([150, 200]).as('sales')) + .select(arraySum('sales').as('totalSales')) + ); + expectResults(snapshot, { + totalSales: 350 + }); + }); + + // TODO(new-expression): Add new expression tests above this line + }); + + describe('pagination', () => { + let addedDocs: DocumentReference[] = []; + + /** + * Adds several books to the test collection. These + * additional books support pagination test scenarios + * that would otherwise not be possible with the original + * set of books. + * @param collectionReference + */ + async function addBooks( + collectionReference: CollectionReference + ): Promise { + let docRef = doc(collectionReference, 'book11'); + addedDocs.push(docRef); + await setDoc(docRef, { + title: 'Jonathan Strange & Mr Norrell', + author: 'Susanna Clarke', + genre: 'Fantasy', + published: 2004, + rating: 4.6, + tags: ['historical fantasy', 'magic', 'alternate history', 'england'], + awards: { hugo: false, nebula: false } + }); + docRef = doc(collectionReference, 'book12'); + addedDocs.push(docRef); + await setDoc(docRef, { + title: 'The Master and Margarita', + author: 'Mikhail Bulgakov', + genre: 'Satire', + published: 1967, // Though written much earlier + rating: 4.6, + tags: [ + 'russian literature', + 'supernatural', + 'philosophy', + 'dark comedy' + ], + awards: {} + }); + docRef = doc(collectionReference, 'book13'); + addedDocs.push(docRef); + await setDoc(docRef, { + title: 'A Long Way to a Small, Angry Planet', + author: 'Becky Chambers', + genre: 'Science Fiction', + published: 2014, + rating: 4.6, + tags: ['space opera', 'found family', 'character-driven', 'optimistic'], + awards: { hugo: false, nebula: false, kitschies: true } + }); + } + + afterEach(async () => { + for (let i = 0; i < addedDocs.length; i++) { + await deleteDoc(addedDocs[i]); + } + addedDocs = []; + }); + + it('supports pagination with filters', async () => { + await addBooks(randomCol); + const pageSize = 2; + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'rating', '__name__') + .sort(field('rating').descending(), field('__name__').ascending()); + + let snapshot = await execute(pipeline.limit(pageSize)); + expectResults( + snapshot, + { title: 'The Lord of the Rings', rating: 4.7 }, + { title: 'Dune', rating: 4.6 } + ); + + const lastDoc = snapshot.results[snapshot.results.length - 1]; + + snapshot = await execute( + pipeline + .where( + or( + and( + field('rating').equal(lastDoc.get('rating')), + field('__name__').greaterThan(lastDoc.ref) + ), + field('rating').lessThan(lastDoc.get('rating')) + ) + ) + .limit(pageSize) + ); + expectResults( + snapshot, + { title: 'Jonathan Strange & Mr Norrell', rating: 4.6 }, + { title: 'The Master and Margarita', rating: 4.6 } + ); + }); + + it('supports pagination with offsets', async () => { + await addBooks(randomCol); + + const secondFilterField = '__name__'; + + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'rating', secondFilterField) + .sort( + field('rating').descending(), + field(secondFilterField).ascending() + ); + + const pageSize = 2; + let currPage = 0; + + let snapshot = await execute( + pipeline.offset(currPage++ * pageSize).limit(pageSize) + ); + + expectResults( + snapshot, + { + title: 'The Lord of the Rings', + rating: 4.7 + }, + { title: 'Dune', rating: 4.6 } + ); + + snapshot = await execute( + pipeline.offset(currPage++ * pageSize).limit(pageSize) + ); + expectResults( + snapshot, + { + title: 'Jonathan Strange & Mr Norrell', + rating: 4.6 + }, + { title: 'The Master and Margarita', rating: 4.6 } + ); + + snapshot = await execute( + pipeline.offset(currPage++ * pageSize).limit(pageSize) + ); + expectResults( + snapshot, + { + title: 'A Long Way to a Small, Angry Planet', + rating: 4.6 + }, + { + title: 'Pride and Prejudice', + rating: 4.5 + } + ); + }); + }); + + describe('stage options', () => { + describe('forceIndex', () => { + // SKIP: requires pre-existing index + // eslint-disable-next-line no-restricted-properties + it.skip('Collection Stage', async () => { + const snapshot = await execute( + firestore.pipeline().collection({ + collection: randomCol, + forceIndex: 'unknown' + }) + ); + expect(snapshot.results.length).to.equal(10); + }); + + // SKIP: requires pre-existing index + // eslint-disable-next-line no-restricted-properties + it.skip('CollectionGroup Stage', async () => { + const snapshot = await execute( + firestore.pipeline().collectionGroup({ + collectionId: randomCol.id, + forceIndex: 'unknown' + }) + ); + expect(snapshot.results.length).to.equal(10); + }); + }); + }); +}); diff --git a/packages/firestore/test/lite/pipeline_export.ts b/packages/firestore/test/lite/pipeline_export.ts new file mode 100644 index 0000000000..d5f60db9d3 --- /dev/null +++ b/packages/firestore/test/lite/pipeline_export.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2025 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. + */ + +// Imports firebase via the raw sources and re-exports it. The +// "/integration/firestore" test suite replaces this file with a +// reference to the minified sources. If you change any exports in this file, +// you need to also adjust "integration/firestore/pipeline_export.ts". + +// @ts-ignore +export * from '../../lite/pipelines/pipelines.ts'; diff --git a/packages/firestore/test/unit/api/document_change.test.ts b/packages/firestore/test/unit/api/document_change.test.ts index faae8b4d4c..8ce40f599b 100644 --- a/packages/firestore/test/unit/api/document_change.test.ts +++ b/packages/firestore/test/unit/api/document_change.test.ts @@ -18,8 +18,8 @@ import { expect } from 'chai'; import { Query } from '../../../src/api/reference'; -import { ExpUserDataWriter } from '../../../src/api/reference_impl'; import { QuerySnapshot } from '../../../src/api/snapshot'; +import { ExpUserDataWriter } from '../../../src/api/user_data_writer'; import { Query as InternalQuery } from '../../../src/core/query'; import { View } from '../../../src/core/view'; import { documentKeySet } from '../../../src/model/collections'; diff --git a/packages/firestore/test/unit/api/pipeline_impl.test.ts b/packages/firestore/test/unit/api/pipeline_impl.test.ts new file mode 100644 index 0000000000..f11db5a427 --- /dev/null +++ b/packages/firestore/test/unit/api/pipeline_impl.test.ts @@ -0,0 +1,202 @@ +/** + * @license + * Copyright 2025 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 { expect } from 'chai'; +import * as sinon from 'sinon'; + +import { Timestamp } from '../../../src'; +import { Firestore } from '../../../src/api/database'; +import { execute } from '../../../src/api/pipeline_impl'; +import { + MemoryOfflineComponentProvider, + OnlineComponentProvider +} from '../../../src/core/component_provider'; +import { + ExecutePipelineRequest as ProtoExecutePipelineRequest, + ExecutePipelineResponse as ProtoExecutePipelineResponse +} from '../../../src/protos/firestore_proto_api'; +import { newTestFirestore } from '../../util/api_helpers'; + +const FIRST_CALL = 0; +const EXECUTE_PIPELINE_REQUEST = 3; + +function fakePipelineResponse( + firestore: Firestore, + response?: ProtoExecutePipelineResponse[] +): sinon.SinonSpy { + response = response ?? [ + { + executionTime: Timestamp.now().toDate().toISOString(), + results: [] + } + ]; + const fake = sinon.fake.resolves(response); + + firestore._componentsProvider = { + _offline: { + build: () => new MemoryOfflineComponentProvider() + }, + _online: { + build: () => { + const provider = new OnlineComponentProvider(); + const ogCreateDatastore = provider.createDatastore.bind(provider); + provider.createDatastore = config => { + const datastore = ogCreateDatastore(config); + // @ts-ignore + datastore.invokeStreamingRPC = fake; + return datastore; + }; + return provider; + } + } + }; + + return fake; +} + +describe('execute(Pipeline|PipelineOptions)', () => { + it('returns execution time with empty results', async () => { + const firestore = newTestFirestore(); + + const executeTime = Timestamp.now(); + const spy = fakePipelineResponse(firestore, [ + { + executionTime: executeTime.toDate().toISOString(), + results: [] + } + ]); + + const pipelineSnapshot = await execute( + firestore.pipeline().collection('foo') + ); + + expect(pipelineSnapshot.results.length).to.equal(0); + expect(spy.calledOnce); + + expect(pipelineSnapshot.executionTime.toJSON()).to.deep.equal( + executeTime.toJSON() + ); + }); + + it('serializes the pipeline', async () => { + const firestore = newTestFirestore(); + const spy = fakePipelineResponse(firestore); + + await execute({ + pipeline: firestore.pipeline().collection('foo') + }); + + const executePipelineRequest: ProtoExecutePipelineRequest = { + database: 'projects/new-project/databases/(default)', + structuredPipeline: { + 'options': {}, + 'pipeline': { + 'stages': [ + { + 'args': [ + { + 'referenceValue': '/foo' + } + ], + 'name': 'collection', + 'options': {} + } + ] + } + } + }; + expect(spy.args[FIRST_CALL][EXECUTE_PIPELINE_REQUEST]).to.deep.equal( + executePipelineRequest + ); + }); + + it('serializes the pipeline options', async () => { + const firestore = newTestFirestore(); + const spy = fakePipelineResponse(firestore); + + await execute({ + pipeline: firestore.pipeline().collection('foo'), + indexMode: 'recommended' + }); + + const executePipelineRequest: ProtoExecutePipelineRequest = { + database: 'projects/new-project/databases/(default)', + structuredPipeline: { + 'options': { + 'index_mode': { + 'stringValue': 'recommended' + } + }, + 'pipeline': { + 'stages': [ + { + 'args': [ + { + 'referenceValue': '/foo' + } + ], + 'name': 'collection', + 'options': {} + } + ] + } + } + }; + expect(spy.args[FIRST_CALL][EXECUTE_PIPELINE_REQUEST]).to.deep.equal( + executePipelineRequest + ); + }); + + it('serializes the pipeline raw options', async () => { + const firestore = newTestFirestore(); + const spy = fakePipelineResponse(firestore); + + await execute({ + pipeline: firestore.pipeline().collection('foo'), + rawOptions: { + 'foo': 'bar' + } + }); + + const executePipelineRequest: ProtoExecutePipelineRequest = { + database: 'projects/new-project/databases/(default)', + structuredPipeline: { + 'options': { + 'foo': { + 'stringValue': 'bar' + } + }, + 'pipeline': { + 'stages': [ + { + 'args': [ + { + 'referenceValue': '/foo' + } + ], + 'name': 'collection', + 'options': {} + } + ] + } + } + }; + expect(spy.args[FIRST_CALL][EXECUTE_PIPELINE_REQUEST]).to.deep.equal( + executePipelineRequest + ); + }); +}); diff --git a/packages/firestore/test/unit/core/options_util.test.ts b/packages/firestore/test/unit/core/options_util.test.ts new file mode 100644 index 0000000000..0549c2c8a9 --- /dev/null +++ b/packages/firestore/test/unit/core/options_util.test.ts @@ -0,0 +1,224 @@ +/** + * @license + * Copyright 2025 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 { expect } from 'chai'; + +import { ParseContext } from '../../../src/api/parse_context'; +import { OptionsUtil } from '../../../src/core/options_util'; +import { UserDataSource } from '../../../src/lite-api/user_data_reader'; +import { testUserDataReader } from '../../util/helpers'; + +describe('OptionsUtil', () => { + let context: ParseContext | undefined; + beforeEach(async () => { + context = testUserDataReader(false).createContext( + UserDataSource.Argument, + 'beforeEach' + ); + }); + + afterEach(async () => { + context = undefined; + }); + + it('should support known options', () => { + const optionsUtil = new OptionsUtil({ + fooBar: { + serverName: 'foo_bar' + } + }); + const proto = optionsUtil.getOptionsProto(context!, { + fooBar: 'recommended' + }); + + expect(proto).deep.equal({ + 'foo_bar': { + stringValue: 'recommended' + } + }); + }); + + it('should support unknown options', () => { + const optionsUtil = new OptionsUtil({}); + const proto = optionsUtil.getOptionsProto(context!, {}, { baz: 'foo' }); + + expect(proto).to.deep.equal({ + baz: { + stringValue: 'foo' + } + }); + }); + + it('should support unknown nested options', () => { + const optionsUtil = new OptionsUtil({}); + const proto = optionsUtil.getOptionsProto( + context!, + {}, + { 'foo.bar': 'baz' } + ); + + expect(proto).to.deep.equal({ + foo: { + mapValue: { + fields: { + bar: { stringValue: 'baz' } + } + } + } + }); + }); + + it('should support options override', () => { + const optionsUtil = new OptionsUtil({ + indexMode: { + serverName: 'index_mode' + } + }); + const proto = optionsUtil.getOptionsProto( + context!, + { + indexMode: 'recommended' + }, + { + 'index_mode': 'baz' + } + ); + + expect(proto).to.deep.equal({ + 'index_mode': { + stringValue: 'baz' + } + }); + }); + + it('should support options override of nested field', () => { + const optionsUtil = new OptionsUtil({ + foo: { + serverName: 'foo', + nestedOptions: { + bar: { + serverName: 'bar' + }, + waldo: { + serverName: 'waldo' + } + } + } + }); + const proto = optionsUtil.getOptionsProto( + context!, + { + foo: { bar: 'yep', waldo: 'found' } + }, + { + 'foo.bar': 123, + 'foo.baz': true + } + ); + + expect(proto).to.deep.equal({ + foo: { + mapValue: { + fields: { + bar: { + integerValue: '123' + }, + waldo: { + stringValue: 'found' + }, + baz: { + booleanValue: true + } + } + } + } + }); + }); + + it('will replace a nested object if given a new object', () => { + const optionsUtil = new OptionsUtil({ + foo: { + serverName: 'foo', + nestedOptions: { + bar: { + serverName: 'bar' + }, + waldo: { + serverName: 'waldo' + } + } + } + }); + const proto = optionsUtil.getOptionsProto( + context!, + { + foo: { bar: 'yep', waldo: 'found' } + }, + { + foo: { + bar: 123 + } + } + ); + + expect(proto).to.deep.equal({ + foo: { + mapValue: { + fields: { + bar: { + integerValue: '123' + } + } + } + } + }); + }); + + it('will replace a top level property that is not an object if given a nested field with dot notation', () => { + const optionsUtil = new OptionsUtil({ + foo: { + serverName: 'foo' + } + }); + + const proto = optionsUtil.getOptionsProto( + context!, + { + foo: 'bar' + }, + { + 'foo.bar': '123', + 'foo.waldo': true + } + ); + + expect(proto).to.deep.equal({ + foo: { + mapValue: { + fields: { + bar: { + stringValue: '123' + }, + waldo: { + booleanValue: true + } + } + } + } + }); + }); +}); diff --git a/packages/firestore/test/unit/core/structured_pipeline.test.ts b/packages/firestore/test/unit/core/structured_pipeline.test.ts new file mode 100644 index 0000000000..759dfecea4 --- /dev/null +++ b/packages/firestore/test/unit/core/structured_pipeline.test.ts @@ -0,0 +1,187 @@ +/** + * @license + * Copyright 2025 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 { expect } from 'chai'; +import * as sinon from 'sinon'; + +import { DatabaseId } from '../../../src/core/database_info'; +import { + StructuredPipeline, + StructuredPipelineOptions +} from '../../../src/core/structured_pipeline'; +import { UserDataSource } from '../../../src/lite-api/user_data_reader'; +import { Pipeline as PipelineProto } from '../../../src/protos/firestore_proto_api'; +import { + JsonProtoSerializer, + ProtoSerializable +} from '../../../src/remote/serializer'; +import { testUserDataReader } from '../../util/helpers'; + +describe('StructuredPipeline', () => { + it('should serialize the pipeline argument', () => { + const pipeline: ProtoSerializable = { + _toProto: sinon.fake.returns({} as PipelineProto) + }; + const structuredPipelineOptions = new StructuredPipelineOptions(); + structuredPipelineOptions._readUserData( + testUserDataReader(false).createContext(UserDataSource.Argument, 'test') + ); + const structuredPipeline = new StructuredPipeline( + pipeline, + structuredPipelineOptions + ); + + const proto = structuredPipeline._toProto( + new JsonProtoSerializer(DatabaseId.empty(), false) + ); + + expect(proto).to.deep.equal({ + pipeline: {}, + options: {} + }); + + expect((pipeline._toProto as sinon.SinonSpy).calledOnce).to.be.true; + }); + + it('should support known options', () => { + const pipeline: ProtoSerializable = { + _toProto: sinon.fake.returns({} as PipelineProto) + }; + + const options = new StructuredPipelineOptions({ + indexMode: 'recommended' + }); + options._readUserData( + testUserDataReader(false).createContext(UserDataSource.Argument, 'test') + ); + const structuredPipeline = new StructuredPipeline(pipeline, options); + + const proto = structuredPipeline._toProto( + new JsonProtoSerializer(DatabaseId.empty(), false) + ); + + expect(proto).to.deep.equal({ + pipeline: {}, + options: { + 'index_mode': { + stringValue: 'recommended' + } + } + }); + + expect((pipeline._toProto as sinon.SinonSpy).calledOnce).to.be.true; + }); + + it('should support unknown options', () => { + const pipeline: ProtoSerializable = { + _toProto: sinon.fake.returns({} as PipelineProto) + }; + const options = new StructuredPipelineOptions( + {}, + { + 'foo_bar': 'baz' + } + ); + options._readUserData( + testUserDataReader(false).createContext(UserDataSource.Argument, 'test') + ); + const structuredPipeline = new StructuredPipeline(pipeline, options); + + const proto = structuredPipeline._toProto( + new JsonProtoSerializer(DatabaseId.empty(), false) + ); + + expect(proto).to.deep.equal({ + pipeline: {}, + options: { + 'foo_bar': { + stringValue: 'baz' + } + } + }); + + expect((pipeline._toProto as sinon.SinonSpy).calledOnce).to.be.true; + }); + + it('should support unknown nested options', () => { + const pipeline: ProtoSerializable = { + _toProto: sinon.fake.returns({} as PipelineProto) + }; + const options = new StructuredPipelineOptions( + {}, + { + 'foo.bar': 'baz' + } + ); + options._readUserData( + testUserDataReader(false).createContext(UserDataSource.Argument, 'test') + ); + const structuredPipeline = new StructuredPipeline(pipeline, options); + + const proto = structuredPipeline._toProto( + new JsonProtoSerializer(DatabaseId.empty(), false) + ); + + expect(proto).to.deep.equal({ + pipeline: {}, + options: { + 'foo': { + mapValue: { + fields: { + 'bar': { stringValue: 'baz' } + } + } + } + } + }); + + expect((pipeline._toProto as sinon.SinonSpy).calledOnce).to.be.true; + }); + + it('should support options override', () => { + const pipeline: ProtoSerializable = { + _toProto: sinon.fake.returns({} as PipelineProto) + }; + const options = new StructuredPipelineOptions( + { + indexMode: 'recommended' + }, + { + 'index_mode': 'baz' + } + ); + options._readUserData( + testUserDataReader(false).createContext(UserDataSource.Argument, 'test') + ); + const structuredPipeline = new StructuredPipeline(pipeline, options); + + const proto = structuredPipeline._toProto( + new JsonProtoSerializer(DatabaseId.empty(), false) + ); + + expect(proto).to.deep.equal({ + pipeline: {}, + options: { + 'index_mode': { + stringValue: 'baz' + } + } + }); + + expect((pipeline._toProto as sinon.SinonSpy).calledOnce).to.be.true; + }); +}); diff --git a/packages/firestore/test/unit/remote/fetch_connection.test.ts b/packages/firestore/test/unit/remote/fetch_connection.test.ts index 5a9aa67436..4de0ba5a72 100644 --- a/packages/firestore/test/unit/remote/fetch_connection.test.ts +++ b/packages/firestore/test/unit/remote/fetch_connection.test.ts @@ -43,6 +43,7 @@ describe('Fetch Connection', () => { DatabaseId.empty(), '', '', + '', new FirestoreSettingsImpl({ host: 'abc.cloudworkstations.dev' }) diff --git a/packages/firestore/test/unit/remote/grpc_connection.node.test.ts b/packages/firestore/test/unit/remote/grpc_connection.node.test.ts new file mode 100644 index 0000000000..8364cbbd5e --- /dev/null +++ b/packages/firestore/test/unit/remote/grpc_connection.node.test.ts @@ -0,0 +1,81 @@ +/** + * @license + * Copyright 2025 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 { Metadata } from '@grpc/grpc-js'; +import { expect } from 'chai'; + +import { DatabaseId, DatabaseInfo } from '../../../src/core/database_info'; +import { ResourcePath } from '../../../src/model/path'; +import { GrpcConnection } from '../../../src/platform/node/grpc_connection'; + +export class TestGrpcConnection extends GrpcConnection { + mockStub = { + lastMetadata: null, + mockRpc( + req: unknown, + metadata: Metadata, + callback: (err: unknown, resp: unknown) => void + ) { + this.lastMetadata = metadata; + callback(null, null); + } + } as { + lastMetadata: null | Metadata; + [index: string]: unknown; + }; + + protected ensureActiveStub(): unknown { + return this.mockStub; + } +} + +describe('GrpcConnection', () => { + const testDatabaseInfo = new DatabaseInfo( + new DatabaseId('testproject'), + 'test-app-id', + 'persistenceKey', + 'example.com', + /*ssl=*/ false, + /*forceLongPolling=*/ false, + /*autoDetectLongPolling=*/ false, + /*longPollingOptions=*/ {}, + /*useFetchStreams=*/ false, + /*isUsingEmulator=*/ false, + 'grpc-connection-test-api-key' + ); + const connection = new TestGrpcConnection( + { google: { firestore: { v1: {} } } }, + testDatabaseInfo + ); + + it('Passes the API Key from DatabaseInfo to the grpc stub', async () => { + const request = { + database: 'projects/testproject/databases/(default)', + writes: [] + }; + await connection.invokeRPC( + 'mockRpc', + ResourcePath.emptyPath(), + request, + null, + null + ); + expect( + connection.mockStub.lastMetadata?.get('x-goog-api-key') + ).to.deep.equal(['grpc-connection-test-api-key']); + }); +}); diff --git a/packages/firestore/test/unit/remote/rest_connection.test.ts b/packages/firestore/test/unit/remote/rest_connection.test.ts index 100b8b8368..3501a910f6 100644 --- a/packages/firestore/test/unit/remote/rest_connection.test.ts +++ b/packages/firestore/test/unit/remote/rest_connection.test.ts @@ -68,7 +68,8 @@ describe('RestConnection', () => { /*autoDetectLongPolling=*/ false, /*longPollingOptions=*/ {}, /*useFetchStreams=*/ false, - /*isUsingEmulator=*/ false + /*isUsingEmulator=*/ false, + 'rest-connection-test-api-key' ); const connection = new TestRestConnection(testDatabaseInfo); @@ -83,7 +84,7 @@ describe('RestConnection', () => { null ); expect(connection.lastUrl).to.equal( - 'http://example.com/v1/projects/testproject/databases/(default)/documents:commit' + 'http://example.com/v1/projects/testproject/databases/(default)/documents:commit?key=rest-connection-test-api-key' ); }); diff --git a/packages/firestore/test/unit/remote/serializer.helper.ts b/packages/firestore/test/unit/remote/serializer.helper.ts index d523c8fab8..451f7ddf7a 100644 --- a/packages/firestore/test/unit/remote/serializer.helper.ts +++ b/packages/firestore/test/unit/remote/serializer.helper.ts @@ -28,7 +28,7 @@ import { serverTimestamp, Timestamp } from '../../../src'; -import { ExpUserDataWriter } from '../../../src/api/reference_impl'; +import { ExpUserDataWriter } from '../../../src/api/user_data_writer'; import { DatabaseId } from '../../../src/core/database_info'; import { ArrayContainsAnyFilter, diff --git a/packages/firestore/test/unit/remote/web_channel_connection.browser.test.ts b/packages/firestore/test/unit/remote/web_channel_connection.browser.test.ts new file mode 100644 index 0000000000..6558fb84de --- /dev/null +++ b/packages/firestore/test/unit/remote/web_channel_connection.browser.test.ts @@ -0,0 +1,70 @@ +/** + * @license + * Copyright 2025 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 { + WebChannelOptions, + WebChannelTransport +} from '@firebase/webchannel-wrapper'; +import { expect } from 'chai'; + +import { DatabaseId, DatabaseInfo } from '../../../src/core/database_info'; +import { WebChannelConnection } from '../../../src/platform/browser/webchannel_connection'; + +export class TestWebChannelConnection extends WebChannelConnection { + transport: { lastOptions?: WebChannelOptions } & WebChannelTransport = { + lastOptions: undefined, + createWebChannel(url: string, options: WebChannelOptions): never { + this.lastOptions = options; + + // Throw here so we don't have to mock out any more of Web Channel + throw new Error('Not implemented for test'); + } + }; + protected createWebChannelTransport(): WebChannelTransport { + return this.transport; + } +} + +describe('WebChannelConnection', () => { + const testDatabaseInfo = new DatabaseInfo( + new DatabaseId('testproject'), + 'test-app-id', + 'persistenceKey', + 'example.com', + /*ssl=*/ false, + /*forceLongPolling=*/ false, + /*autoDetectLongPolling=*/ false, + /*longPollingOptions=*/ {}, + /*useFetchStreams=*/ false, + /*isUsingEmulator=*/ false, + 'wc-connection-test-api-key' + ); + + it('Passes the API Key from DatabaseInfo to makeHeaders for openStream', async () => { + const connection = new TestWebChannelConnection(testDatabaseInfo); + + expect(() => connection.openStream('mockRpc', null, null)).to.throw( + 'Not implemented for test' + ); + + const headers = connection.transport.lastOptions + ?.initMessageHeaders as unknown as { [key: string]: string }; + expect(headers['x-goog-api-key']).to.deep.equal( + 'wc-connection-test-api-key' + ); + }); +}); diff --git a/packages/firestore/test/unit/specs/spec_builder.ts b/packages/firestore/test/unit/specs/spec_builder.ts index 3e52c5873b..afc6791dbd 100644 --- a/packages/firestore/test/unit/specs/spec_builder.ts +++ b/packages/firestore/test/unit/specs/spec_builder.ts @@ -16,7 +16,7 @@ */ import { IndexConfiguration } from '../../../src/api/index_configuration'; -import { ExpUserDataWriter } from '../../../src/api/reference_impl'; +import { ExpUserDataWriter } from '../../../src/api/user_data_writer'; import { ListenOptions, ListenerDataSource as Source diff --git a/packages/firestore/test/unit/specs/spec_test_runner.ts b/packages/firestore/test/unit/specs/spec_test_runner.ts index 51d2229b8a..50806cb2a4 100644 --- a/packages/firestore/test/unit/specs/spec_test_runner.ts +++ b/packages/firestore/test/unit/specs/spec_test_runner.ts @@ -283,7 +283,8 @@ abstract class TestRunner { /*autoDetectLongPolling=*/ false, /*longPollingOptions=*/ {}, /*useFetchStreams=*/ false, - /*isUsingEmulator=*/ false + /*isUsingEmulator=*/ false, + 'test-api-key' ); // TODO(mrschmidt): During client startup in `firestore_client`, we block diff --git a/packages/firestore/test/util/api_helpers.ts b/packages/firestore/test/util/api_helpers.ts index d248c9213b..dc66a70a85 100644 --- a/packages/firestore/test/util/api_helpers.ts +++ b/packages/firestore/test/util/api_helpers.ts @@ -32,7 +32,7 @@ import { EmptyAppCheckTokenProvider, EmptyAuthCredentialsProvider } from '../../src/api/credentials'; -import { ExpUserDataWriter } from '../../src/api/reference_impl'; +import { ExpUserDataWriter } from '../../src/api/user_data_writer'; import { DatabaseId } from '../../src/core/database_info'; import { newQueryForPath, Query as InternalQuery } from '../../src/core/query'; import { diff --git a/packages/firestore/test/util/mocha_extensions.ts b/packages/firestore/test/util/mocha_extensions.ts new file mode 100644 index 0000000000..6ea22cbc36 --- /dev/null +++ b/packages/firestore/test/util/mocha_extensions.ts @@ -0,0 +1,98 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable no-restricted-properties */ + +import { USE_EMULATOR } from '../integration/util/settings'; + +// Helper to make a type itselt (T) and optionally union that with (T['skip']) +type tOrSkipT = T | (T extends { skip: unknown } ? T['skip'] : T); + +interface ExtendMochaTypeWithHelpers { + // Declare helpers + skipEmulator: tOrSkipT; + skipEnterprise: tOrSkipT; + skipClassic: tOrSkipT; +} + +declare module 'mocha' { + // TODO add mocha types that must be extended + interface TestFunction extends ExtendMochaTypeWithHelpers {} + interface PendingTestFunction + extends ExtendMochaTypeWithHelpers {} + interface SuiteFunction extends ExtendMochaTypeWithHelpers {} + interface PendingSuiteFunction + extends ExtendMochaTypeWithHelpers {} +} + +// Define helpers +export function mixinSkipImplementations(obj: unknown): void { + Object.defineProperty(obj, 'skipEmulator', { + get(): unknown { + if (this === it.skip) { + return this; + } + if (this === describe.skip) { + return this; + } + if (USE_EMULATOR) { + return this.skip; + } + return this; + } + }); + + Object.defineProperty(obj, 'skipEnterprise', { + get(): unknown { + if (this === it.skip) { + return this; + } + if (this === describe.skip) { + return this; + } + if (process.env.RUN_ENTERPRISE_TESTS) { + return this.skip; + } + return this; + } + }); + + Object.defineProperty(obj, 'skipClassic', { + get(): unknown { + if (this === it.skip) { + return this; + } + if (this === describe.skip) { + return this; + } + if (!process.env.RUN_ENTERPRISE_TESTS) { + return this.skip; + } + return this; + } + }); +} + +// TODO add mocha functions that must be extended +[global.it, global.it.skip, global.describe, global.describe.skip].forEach( + mixinSkipImplementations +); + +// Export modified it and describe. +const it = global.it; +const describe = global.describe; +export { it, describe }; diff --git a/repo-scripts/prune-dts/extract-public-api.ts b/repo-scripts/prune-dts/extract-public-api.ts index c751739956..4e3c0c3c1f 100644 --- a/repo-scripts/prune-dts/extract-public-api.ts +++ b/repo-scripts/prune-dts/extract-public-api.ts @@ -141,7 +141,8 @@ export async function generateApi( typescriptDtsPath: string, rollupDtsPath: string, untrimmedRollupDtsPath: string, - publicDtsPath: string + publicDtsPath: string, + otherExportDtsPaths: string[] ): Promise { console.log(`Configuring API Extractor for ${packageName}`); writeTypeScriptConfig(packageRoot); @@ -160,7 +161,7 @@ export async function generateApi( }); console.log('Generated rollup DTS'); - pruneDts(rollupDtsPath, publicDtsPath); + pruneDts(rollupDtsPath, publicDtsPath, otherExportDtsPaths); console.log('Pruned DTS file'); await addBlankLines(publicDtsPath); console.log('Added blank lines after imports'); @@ -221,6 +222,13 @@ const argv = yargs 'The output file for the customer-facing .d.ts file that only ' + 'includes the public APIs', require: true + }, + otherExportsPublicDtsFiles: { + type: 'string', + desc: + 'Optional. A comma-separated list of customer-facing of .d.ts' + + 'files for other exports from this package.', + require: false } }) .parseSync(); @@ -231,5 +239,10 @@ void generateApi( path.resolve(argv.typescriptDts), path.resolve(argv.rollupDts), path.resolve(argv.untrimmedRollupDts), - path.resolve(argv.publicDts) + path.resolve(argv.publicDts), + argv.otherExportsPublicDtsFiles + ? argv.otherExportsPublicDtsFiles + .split(',') + .map(filePath => path.resolve(filePath)) + : [] ); diff --git a/repo-scripts/prune-dts/prune-dts.ts b/repo-scripts/prune-dts/prune-dts.ts index 70cfc2933b..bbfad0a3f3 100644 --- a/repo-scripts/prune-dts/prune-dts.ts +++ b/repo-scripts/prune-dts/prune-dts.ts @@ -18,6 +18,7 @@ import * as yargs from 'yargs'; import * as ts from 'typescript'; import * as fs from 'fs'; +import * as path from 'path'; import { ESLint } from 'eslint'; /** @@ -33,16 +34,32 @@ import { ESLint } from 'eslint'; * @param inputLocation The file path to the .d.ts produced by API explorer. * @param outputLocation The output location for the pruned .d.ts file. */ -export function pruneDts(inputLocation: string, outputLocation: string): void { +export function pruneDts( + inputLocation: string, + outputLocation: string, + otherExportFileLocations: string[] = [] +): void { const compilerOptions = {}; const host = ts.createCompilerHost(compilerOptions); - const program = ts.createProgram([inputLocation], compilerOptions, host); + const program = ts.createProgram( + [inputLocation, ...otherExportFileLocations], + compilerOptions, + host + ); const printer: ts.Printer = ts.createPrinter(); const sourceFile = program.getSourceFile(inputLocation)!; + const otherExportSourceFiles = otherExportFileLocations + .map(otherFileLocation => program.getSourceFile(otherFileLocation)) + .filter(value => value !== undefined) as ts.SourceFile[]; const result: ts.TransformationResult = ts.transform(sourceFile, [ - dropPrivateApiTransformer.bind(null, program, host) + dropPrivateApiTransformer.bind( + null, + program, + host, + otherExportSourceFiles + ) ]); const transformedSourceFile: ts.SourceFile = result.transformed[0]; let content = printer.printFile(transformedSourceFile); @@ -503,14 +520,74 @@ function extractExportedSymbol( return undefined; } +function findExternalExport( + typeChecker: ts.TypeChecker, + sourceFile: ts.SourceFile, + node: + | ts.InterfaceDeclaration + | ts.ClassDeclaration + | ts.TypeAliasDeclaration + | ts.EnumDeclaration, + otherExportSourceFiles: ts.SourceFile[] +): ts.SourceFile | undefined { + if (!node.name) return undefined; + + const localSymbolName = node.name.text; + + for (const otherExportSourceFile of otherExportSourceFiles) { + const otherExportedSymbols = typeChecker.getExportsOfModule( + typeChecker.getSymbolAtLocation(otherExportSourceFile)! + ); + + for (const symbol of otherExportedSymbols) { + // TODO: ideally this would compare definitions to handle the case + // of name collisions with different definitions. However, this + // implementation currently does not handle function exports, + // which is the only place we expect name collisions. + if (symbol.name === localSymbolName) { + return otherExportSourceFile; + } + } + } + + return undefined; +} + function dropPrivateApiTransformer( program: ts.Program, host: ts.CompilerHost, + otherExportSourceFiles: ts.SourceFile[], context: ts.TransformationContext ): ts.Transformer { const typeChecker = program.getTypeChecker(); return (sourceFile: ts.SourceFile) => { + const imports: Record> = {}; + + // Get exported symbols + const directExportedSymbols = typeChecker.getExportsOfModule( + typeChecker.getSymbolAtLocation(sourceFile)! + ); + // Map exported symbols to aliases. + // For the statement `export { X as Y };`, this list would contain a symbol + // for `X`. + const aliasedExportedSymbols = directExportedSymbols + .map(symbol => + symbol.flags & ts.SymbolFlags.Alias + ? typeChecker.getAliasedSymbol(symbol) + : undefined + ) + .filter(symbol => symbol !== undefined); + + function ensureImportsForFile(filename: string): Array { + let importsForFile = imports[filename]; + if (!importsForFile) { + importsForFile = []; + imports[filename] = importsForFile; + } + return importsForFile; + } + function visit(node: ts.Node): ts.Node { if ( ts.isInterfaceDeclaration(node) || @@ -522,11 +599,50 @@ function dropPrivateApiTransformer( ts.isEnumDeclaration(node) ) { // Remove any types that are not exported. + // First we check the modifiers for the symbol `export function X`. If + // the export keyword is found, the symbol is modified. + // Second we check if the symbol has an alias that is exported elsewhere, + // for example: `function X; export { X as Y }`. If the alias is + // exported elsewhere, then we also have to keep the symbol. if ( !ts .getModifiers(node) ?.find(m => m.kind === ts.SyntaxKind.ExportKeyword) ) { + // Try to get a symbol for this node. + const symbol = + 'name' in node && node.name + ? typeChecker.getSymbolAtLocation(node.name) + : undefined; + // Check if that symbol is in the list of aliased exported symbols. + // If it is, we keep the symbol. Otherwise, we remove the symbol. + if (!symbol || !aliasedExportedSymbols.includes(symbol)) { + // NO-OP block to keep the condition readable + return ts.factory.createNotEmittedStatement(node); + } + } + } + + if ( + ts.isInterfaceDeclaration(node) || + ts.isClassDeclaration(node) || + ts.isTypeAliasDeclaration(node) || + ts.isEnumDeclaration(node) + ) { + // Remove any types that are exported externally + const externalExportFile = findExternalExport( + typeChecker, + sourceFile, + node, + otherExportSourceFiles + ); + if (externalExportFile && node.name) { + ensureImportsForFile( + path.relative( + path.dirname(sourceFile.fileName), + externalExportFile.fileName + ) + ).push(node.name.text); return ts.factory.createNotEmittedStatement(node); } } @@ -580,7 +696,40 @@ function dropPrivateApiTransformer( context ) as T; } - return visitNodeAndChildren(sourceFile); + const result = visitNodeAndChildren(sourceFile); + + const moreImports: ts.ImportDeclaration[] = []; + for (let filename in imports) { + const importSpecifiers: ts.ImportSpecifier[] = []; + for (let identifier of imports[filename]) { + importSpecifiers.push( + ts.factory.createImportSpecifier( + false, + undefined, + ts.factory.createIdentifier(identifier) + ) + ); + } + let outFileName = filename.startsWith('.') ? filename : `./${filename}`; + outFileName = outFileName.replace('.d.ts', ''); + const importDeclaration = ts.factory.createImportDeclaration( + [], + ts.factory.createImportClause( + true, + undefined, + ts.factory.createNamedImports(importSpecifiers) + ), + ts.factory.createStringLiteral(outFileName, true) + ); + + moreImports.push(importDeclaration); + } + + return ts.factory.updateSourceFile( + result, + [...moreImports, ...result.statements], + true + ); }; } diff --git a/repo-scripts/size-analysis/bundle-definitions/firestore.json b/repo-scripts/size-analysis/bundle-definitions/firestore.json index f5ddafd167..4f53991cec 100644 --- a/repo-scripts/size-analysis/bundle-definitions/firestore.json +++ b/repo-scripts/size-analysis/bundle-definitions/firestore.json @@ -128,6 +128,134 @@ } ] }, + { + "name": "Pipeline Query with lt filter (execute)", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore", + "imports": [ + "getFirestore" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore/pipelines", + "imports": [ + "lessThan", + "Field", + "execute" + ] + } + ] + } + ] + }, + { + "name": "Pipeline Query with lt filter (useFirestorePipelines)", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore", + "imports": [ + "getFirestore" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore/pipelines", + "imports": [ + "lessThan", + "Field" + ] + } + ] + } + ] + }, + { + "name": "Pipeline Query with lt plus and function", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore", + "imports": [ + "getFirestore" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore/pipelines", + "imports": [ + "lessThan", + "Field", + "and" + ] + } + ] + } + ] + }, { "name": "Query Cursors", "dependencies": [ diff --git a/scripts/build/rollup_replace_declare_module.js b/scripts/build/rollup_replace_declare_module.js new file mode 100644 index 0000000000..0fa45b8aed --- /dev/null +++ b/scripts/build/rollup_replace_declare_module.js @@ -0,0 +1,141 @@ +/** + * @license + * Copyright 2025 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. + */ + +/** + * Returns a rollup plugin to replace any `declare module X { Y }` code blocks with `Y`. + * This was developed to support the generation of `global_index.d.ts` + * used for the Google3 import of `firebase/firestore` + * + * @param fileName perform the replace in this file + * @param moduleName search for and replace this module declaration + */ +export function replaceDeclareModule(fileName, moduleName) { + return { + name: 'replace-declare-module', + generateBundle(options, bundle) { + if (!bundle[fileName]) { + console.warn( + `[replace-declare-module] File not found in bundle: ${fileName}` + ); + return; + } + + const chunk = bundle[fileName]; + if (chunk.type === 'chunk') { + do { + const originalString = chunk.code; + const blockInfo = findDeclareModuleBlock(originalString, moduleName); + const fullBlock = blockInfo?.fullBlock; + const innerContent = blockInfo?.innerContent; + + if (!fullBlock || !innerContent) break; + + // 1. Get the segment of the string BEFORE the full block starts. + const beforeBlock = originalString.substring(0, fullBlock.start); + + // 2. Extract the inner content string based on its start and length. + // We use innerContent indices here to get the actual content inside the braces. + const innerContentString = originalString.substring( + innerContent.start, + innerContent.start + innerContent.length + ); + + // 3. Get the segment of the string AFTER the full block ends. + // The start index of the 'after' segment is the end index of the full block. + const afterBlockStart = fullBlock.start + fullBlock.length; + const afterBlock = originalString.substring(afterBlockStart); + + // 4. Concatenate the three parts: Before + Inner Content + After + chunk.code = beforeBlock + innerContentString + afterBlock; + } while (true); + } + } + }; +} + +/** + * Searches a multi-line string for a TypeScript module declaration pattern, + * finds the matching closing brace while respecting nested braces, and + * returns the start and length information for the full block and its inner content. + * + * @param inputString The multi-line string content to search within. + * @param moduleName The module name of the declare module block to search for. + * @returns An object containing the BlockInfo for the full block and inner content, or null if not found. + */ +function findDeclareModuleBlock(inputString, moduleName) { + // We escape potential regex characters in the module name to ensure it matches literally. + const escapedModuleName = moduleName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + + // Construct the RegExp object dynamically. It searches for: + // 'declare module ' + single quote + escaped module name + single quote + space + '{' + const searchRegex = new RegExp(`declare module '${escapedModuleName}' {`); + const match = inputString.match(searchRegex); + + if (!match || match.index === undefined) { + console.log('No matching module declaration found.'); + return { fullBlock: null, innerContent: null }; + } + + const fullBlockStartIndex = match.index; + + // 2. Determine the exact index of the opening brace '{' + // The match[0] gives the text that matched the regex, e.g., "declare module './my-module' {" + const matchText = match[0]; + const openBraceOffset = matchText.lastIndexOf('{'); + const openBraceIndex = fullBlockStartIndex + openBraceOffset; + + let braceCount = 1; + let closeBraceIndex = -1; + + // 3. Iterate from the character *after* the opening brace to find the matching '}' + for (let i = openBraceIndex + 1; i < inputString.length; i++) { + const char = inputString[i]; + + if (char === '{') { + braceCount++; + } else if (char === '}') { + braceCount--; + } + + // 4. Check if we found the outer closing brace + if (braceCount === 0) { + closeBraceIndex = i; + break; + } + } + + if (closeBraceIndex === -1) { + console.log('Found opening brace but no matching closing brace.'); + return null; + } + + // 5. Calculate results + + // Full Block: from 'declare module...' to matching '}' + const fullBlock = { + start: fullBlockStartIndex, + length: closeBraceIndex - fullBlockStartIndex + 1 + }; + + // Inner Content: from char after '{' to char before '}' + const innerContent = { + start: openBraceIndex + 1, + length: closeBraceIndex - (openBraceIndex + 1) + }; + + return { fullBlock, innerContent }; +} diff --git a/scripts/size_report/report_binary_size.ts b/scripts/size_report/report_binary_size.ts index da1ad16670..34dce01924 100644 --- a/scripts/size_report/report_binary_size.ts +++ b/scripts/size_report/report_binary_size.ts @@ -57,7 +57,7 @@ function generateReportForCDNScripts(): Report[] { ...special_files.map((file: string) => `${firebaseRoot}/${file}`), ...pkgJson.components.map( (component: string) => - `${firebaseRoot}/firebase-${component.replace('/', '-')}.js` + `${firebaseRoot}/firebase-${component.replaceAll('/', '-')}.js` ), ...compatPkgJson.components.map( (component: string) => `${firebaseRoot}/firebase-${component}-compat.js` diff --git a/yarn.lock b/yarn.lock index 213ecd4d2c..caa33e3d21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -223,11 +223,21 @@ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + "@babel/helper-validator-identifier@^7.25.9": version "7.25.9" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + "@babel/helper-validator-option@^7.25.9": version "7.25.9" resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" @@ -257,6 +267,13 @@ dependencies: "@babel/types" "^7.26.7" +"@babel/parser@^7.20.15": + version "7.28.4" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" + integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== + dependencies: + "@babel/types" "^7.28.4" + "@babel/parser@^7.26.8": version "7.26.8" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.26.8.tgz#deca2b4d99e5e1b1553843b99823f118da6107c2" @@ -1013,6 +1030,14 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" +"@babel/types@^7.28.4": + version "7.28.4" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" + integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@bazel/runfiles@^6.3.1": version "6.3.1" resolved "https://registry.npmjs.org/@bazel/runfiles/-/runfiles-6.3.1.tgz#3f8824b2d82853377799d42354b4df78ab0ace0b" @@ -1588,6 +1613,13 @@ resolved "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== +"@jsdoc/salty@^0.2.1": + version "0.2.9" + resolved "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz#4d8c147f7ca011532681ce86352a77a0178f1dec" + integrity sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw== + dependencies: + lodash "^4.17.21" + "@kwsites/file-exists@^1.1.1": version "1.1.1" resolved "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" @@ -3094,6 +3126,11 @@ dependencies: "@types/node" "*" +"@types/linkify-it@^5": + version "5.0.0" + resolved "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz#21413001973106cda1c3a9b91eedd4ccd5469d76" + integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q== + "@types/listr@0.14.9": version "0.14.9" resolved "https://registry.npmjs.org/@types/listr/-/listr-0.14.9.tgz#736581cfdfcdb821bace0a3e5b05e91182e00c85" @@ -3107,6 +3144,19 @@ resolved "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== +"@types/markdown-it@^14.1.1": + version "14.1.2" + resolved "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz#57f2532a0800067d9b934f3521429a2e8bfb4c61" + integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog== + dependencies: + "@types/linkify-it" "^5" + "@types/mdurl" "^2" + +"@types/mdurl@^2": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd" + integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg== + "@types/mime@^1": version "1.3.5" resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" @@ -4617,7 +4667,7 @@ blocking-proxy@^1.0.0: dependencies: minimist "^1.2.0" -bluebird@3.7.2: +bluebird@3.7.2, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -5030,6 +5080,13 @@ caseless@~0.12.0: resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== +catharsis@^0.9.0: + version "0.9.0" + resolved "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz#40382a168be0e6da308c277d3a2b3eb40c7d2121" + integrity sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A== + dependencies: + lodash "^4.17.15" + chai-as-promised@7.1.2: version "7.1.2" resolved "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.2.tgz#70cd73b74afd519754161386421fb71832c6d041" @@ -6146,7 +6203,7 @@ deep-freeze@0.0.1: resolved "https://registry.npmjs.org/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84" integrity sha512-Z+z8HiAvsGwmjqlphnHW5oz6yWlOwu6EQfFTjmeTWlDeda3FS2yv3jhq35TX/ewmsnqB+RX2IdsIOyjJCQN5tg== -deep-is@^0.1.3: +deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== @@ -6613,6 +6670,11 @@ ent@~2.2.0: punycode "^1.4.1" safe-regex-test "^1.1.0" +entities@^4.4.0: + version "4.5.0" + resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + env-paths@^2.2.0: version "2.2.1" resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" @@ -6841,6 +6903,18 @@ escape-string-regexp@^2.0.0: resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== +escodegen@^1.13.0: + version "1.14.3" + resolved "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + escodegen@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" @@ -6980,7 +7054,7 @@ esniff@^2.0.1: event-emitter "^0.3.5" type "^2.7.2" -espree@^9.6.0, espree@^9.6.1: +espree@^9.0.0, espree@^9.6.0, espree@^9.6.1: version "9.6.1" resolved "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== @@ -7008,7 +7082,7 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.1: +estraverse@^4.1.1, estraverse@^4.2.0: version "4.3.0" resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== @@ -7336,7 +7410,7 @@ fast-levenshtein@^1.0.0: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz#e6a754cc8f15e58987aa9cbd27af66fd6f4e5af9" integrity sha512-Ia0sQNrMPXXkqVFt6w6M1n1oKo3NfKs+mvaV811Jwir7vAk9a6PVV9VPYf6X3BU97QiLEmuW3uXH9u87zDFfdw== -fast-levenshtein@^2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== @@ -8253,6 +8327,17 @@ glob@^10.0.0, glob@^10.2.2, glob@^10.3.10, glob@^10.4.1: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" +glob@^8.0.0: + version "8.1.0" + resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + global-dirs@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" @@ -8414,7 +8499,7 @@ graceful-fs@4.2.10: resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== -graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.5, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.5, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -10064,6 +10149,13 @@ js-yaml@~3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js2xmlparser@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz#2a1fdf01e90585ef2ae872a01bc169c6a8d5e60a" + integrity sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA== + dependencies: + xmlcreate "^2.0.4" + jsbn@1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" @@ -10074,6 +10166,27 @@ jsbn@~0.1.0: resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== +jsdoc@^4.0.0: + version "4.0.4" + resolved "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz#86565a9e39cc723a3640465b3fb189a22d1206ca" + integrity sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw== + dependencies: + "@babel/parser" "^7.20.15" + "@jsdoc/salty" "^0.2.1" + "@types/markdown-it" "^14.1.1" + bluebird "^3.7.2" + catharsis "^0.9.0" + escape-string-regexp "^2.0.0" + js2xmlparser "^4.0.2" + klaw "^3.0.0" + markdown-it "^14.1.0" + markdown-it-anchor "^8.6.7" + marked "^4.0.10" + mkdirp "^1.0.4" + requizzle "^0.2.3" + strip-json-comments "^3.1.0" + underscore "~1.13.2" + jsesc@^3.0.2: version "3.1.0" resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" @@ -10432,6 +10545,13 @@ klaw-sync@^6.0.0: dependencies: graceful-fs "^4.1.11" +klaw@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz#b11bec9cf2492f06756d6e809ab73a2910259146" + integrity sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g== + dependencies: + graceful-fs "^4.1.9" + kuler@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" @@ -10518,6 +10638,14 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + libnpmaccess@^4.0.1: version "4.0.3" resolved "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-4.0.3.tgz#dfb0e5b0a53c315a2610d300e46b4ddeb66e7eec" @@ -10577,6 +10705,13 @@ lines-and-columns@^1.1.6: resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +linkify-it@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz#9ef238bfa6dc70bd8e7f9572b52d369af569b421" + integrity sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ== + dependencies: + uc.micro "^2.0.0" + listr-silent-renderer@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" @@ -11099,6 +11234,23 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +markdown-it-anchor@^8.6.7: + version "8.6.7" + resolved "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz#ee6926daf3ad1ed5e4e3968b1740eef1c6399634" + integrity sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA== + +markdown-it@^14.1.0: + version "14.1.0" + resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz#3c3c5992883c633db4714ccb4d7b5935d98b7d45" + integrity sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg== + dependencies: + argparse "^2.0.1" + entities "^4.4.0" + linkify-it "^5.0.0" + mdurl "^2.0.0" + punycode.js "^2.3.1" + uc.micro "^2.1.0" + marked-terminal@^7.0.0: version "7.2.1" resolved "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.2.1.tgz#9c1ae073a245a03c6a13e3eeac6f586f29856068" @@ -11122,6 +11274,11 @@ marked@^13.0.2: resolved "https://registry.npmjs.org/marked/-/marked-13.0.3.tgz#5c5b4a5d0198060c7c9bc6ef9420a7fed30f822d" integrity sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA== +marked@^4.0.10: + version "4.3.0" + resolved "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" + integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== + matchdep@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz#c6f34834a0d8dbc3b37c27ee8bbcb27c7775582e" @@ -11146,6 +11303,11 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +mdurl@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0" + integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -11345,7 +11507,7 @@ minimatch@^3.0.0, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatc dependencies: brace-expansion "^1.1.7" -minimatch@^5.1.0: +minimatch@^5.0.1, minimatch@^5.1.0: version "5.1.6" resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== @@ -12335,6 +12497,18 @@ optimist@~0.6.0: minimist "~0.0.1" wordwrap "~0.0.2" +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + optionator@^0.9.3: version "0.9.4" resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" @@ -13126,6 +13300,11 @@ prelude-ls@^1.2.1: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== + prettier@2.8.8, prettier@^2.7.1: version "2.8.8" resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" @@ -13214,6 +13393,22 @@ proto3-json-serializer@^2.0.2: dependencies: protobufjs "^7.2.5" +protobufjs-cli@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.3.tgz#c58b8566784f0fa1aff11e8d875a31de999637fe" + integrity sha512-MqD10lqF+FMsOayFiNOdOGNlXc4iKDCf0ZQPkPR+gizYh9gqUeGTWulABUCdI+N67w5RfJ6xhgX4J8pa8qmMXQ== + dependencies: + chalk "^4.0.0" + escodegen "^1.13.0" + espree "^9.0.0" + estraverse "^5.1.0" + glob "^8.0.0" + jsdoc "^4.0.0" + minimist "^1.2.0" + semver "^7.1.2" + tmp "^0.2.1" + uglify-js "^3.7.7" + protobufjs@7.4.0, protobufjs@^7.2.5, protobufjs@^7.3.2: version "7.4.0" resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz#7efe324ce9b3b61c82aae5de810d287bc08a248a" @@ -13344,6 +13539,11 @@ pumpify@^1.3.5: inherits "^2.0.3" pump "^2.0.0" +punycode.js@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7" + integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA== + punycode@^1.3.2, punycode@^1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -13909,6 +14109,13 @@ requires-port@^1.0.0: resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== +requizzle@^0.2.3: + version "0.2.4" + resolved "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz#319eb658b28c370f0c20f968fa8ceab98c13d27c" + integrity sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw== + dependencies: + lodash "^4.17.21" + resolve-alpn@^1.0.0: version "1.2.1" resolved "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" @@ -14374,6 +14581,11 @@ semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.3.2, semver@^7.3.4, semve resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== +semver@^7.1.2: + version "7.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + semver@~7.3.0: version "7.3.8" resolved "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" @@ -15298,7 +15510,7 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" -strip-json-comments@3.1.1, strip-json-comments@^3.1.1, strip-json-comments@~3.1.1: +strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1, strip-json-comments@~3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -15935,6 +16147,13 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== + dependencies: + prelude-ls "~1.1.2" + type-detect@4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" @@ -16111,7 +16330,12 @@ ua-parser-js@^0.7.30: resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.40.tgz#c87d83b7bb25822ecfa6397a0da5903934ea1562" integrity sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ== -uglify-js@^3.1.4, uglify-js@^3.4.9: +uc.micro@^2.0.0, uc.micro@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee" + integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== + +uglify-js@^3.1.4, uglify-js@^3.4.9, uglify-js@^3.7.7: version "3.19.3" resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== @@ -16141,7 +16365,7 @@ unc-path-regex@^0.1.2: resolved "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg== -underscore@>=1.8.3, underscore@^1.9.1: +underscore@>=1.8.3, underscore@^1.9.1, underscore@~1.13.2: version "1.13.7" resolved "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz#970e33963af9a7dda228f17ebe8399e5fbe63a10" integrity sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g== @@ -16909,7 +17133,7 @@ winston@^3.0.0: triple-beam "^1.3.0" winston-transport "^4.9.0" -word-wrap@^1.2.5: +word-wrap@^1.2.5, word-wrap@~1.2.3: version "1.2.5" resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== @@ -17088,6 +17312,11 @@ xmlbuilder@~11.0.0: resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== +xmlcreate@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz#0c5ab0f99cdd02a81065fa9cd8f8ae87624889be" + integrity sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg== + xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"