diff --git a/.eslintrc.json b/.eslintrc.json index 1b299f5b8..ed46c46a7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,7 +3,11 @@ "overrides": [ { "files": [ - "dev/src/*.ts" + "dev/src/**/*.ts" + ], + "excludedFiles": [ + "dev/src/v1/*.ts", + "dev/src/v1beta1/*.ts" ], "parser": "@typescript-eslint/parser", "rules": { @@ -14,7 +18,14 @@ "allowTypedFunctionExpressions": true } ], - "no-console": ["error", {"allow": ["error"]}] + "no-console": ["error", {"allow": ["error"]}], + "@typescript-eslint/no-unused-vars": [ + "warn", + { + // Ignore args that are underscore only + "argsIgnorePattern": "^_$" + } + ] } }, { @@ -35,8 +46,28 @@ "property": "only" } ], + "@typescript-eslint/no-unused-vars": [ + "warn", + { + // Ignore args that are underscore only + "argsIgnorePattern": "^_$" + } + ], "@typescript-eslint/no-floating-promises": "warn" } + }, + { + "files": [ + "dev/src/v1/**/*.ts", + "dev/src/v1beta1/**/*.ts", + "dev/test/gapic_firestore_v1.ts", + "dev/test/gapic_firestore_admin_v1.ts", + "dev/test/gapic_firestore_admin_v1.ts" + ], + "rules": { + "@typescript-eslint/no-explicit-any": ["off"], + "@typescript-eslint/no-floating-promises": ["off"] + } } ] } diff --git a/.idea/runConfigurations/Unit_Tests.xml b/.idea/runConfigurations/Unit_Tests.xml index 43fc6f798..3a84e9d10 100644 --- a/.idea/runConfigurations/Unit_Tests.xml +++ b/.idea/runConfigurations/Unit_Tests.xml @@ -11,7 +11,7 @@ bdd --require ts-node/register/type-check --no-cache --no-timeout PATTERN - $PROJECT_DIR$/dev/test/*.js $PROJECT_DIR$/dev/test/*.ts + $PROJECT_DIR$/dev/test/*.js $PROJECT_DIR$/dev/test/**/*.ts \ No newline at end of file diff --git a/.readme-partials.yaml b/.readme-partials.yaml index bebbd9745..60b13dae8 100644 --- a/.readme-partials.yaml +++ b/.readme-partials.yaml @@ -8,3 +8,59 @@ introduction: |- Applications that use Google's Server SDKs should not be used in end-user environments, such as on phones or on publicly hosted websites. If you are developing a Web or Node.js application that accesses Cloud Firestore on behalf of end users, use the firebase Client SDK. **Note:** This Cloud Firestore Server SDK does not support Firestore databases created in [Datastore mode](https://cloud.google.com/datastore/docs/firestore-or-datastore#in_datastore_mode). To access these databases, use the [Datastore SDK](https://www.npmjs.com/package/@google-cloud/datastore). + +body: |- + + ### Using the client library with Pipelines + + ```javascript + + const {Firestore} = require('@google-cloud/firestore'); + + // Require/import Pipelines from '@google-cloud/firestore/pipelines' + const {field} = require('@google-cloud/firestore/pipelines'); + + // Create a new client + const firestore = new Firestore({ + projectId: 'firestore-sdk-nightly', + databaseId: 'enterprise' + }); + + async function pipelinesQuickstart() { + // Obtain a collection reference. + const collection = firestore.collection('books'); + + // Enter new documents into the document. + await collection.add({ + "title": "Whispers of the Cobalt Sea", + "price": 12.99, + "author": "Elara Vance", + "yearPublished": 2023 + }); + await collection.add({ + "title": "The Antigravity Cat's Guide to Napping", + "price": 24.50, + "author": "Mittens the IV", + "yearPublished": 2026 + }); + console.log('Entered new documents into the collection.'); + + // Define a Pipeline query that selects books published this century, + // orders them by price, and computes a discounted price (20% off). + const pipeline = firestore.pipeline().collection('books') + .where(field('yearPublished').greaterThanOrEqual(2000)) + .sort(field('price').ascending()) + .select('title', 'author', field('price').multiply(0.8).as('discountedPrice')); + + // Execute the pipeline + const pipelineSnapshot = await pipeline.execute(); + console.log('Executed the Pipeline.'); + + console.log('Results:'); + pipelineSnapshot.results.forEach(pipelineResult=> { + console.log(pipelineResult.data()); + }); + } + pipelinesQuickstart(); + + ``` diff --git a/README.md b/README.md index 0742ec880..712270010 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,59 @@ quickstart(); ``` +### Using the client library with Pipelines + +```javascript + +const {Firestore} = require('@google-cloud/firestore'); + +// Require/import Pipelines from '@google-cloud/firestore/pipelines' +const {field} = require('@google-cloud/firestore/pipelines'); + +// Create a new client +const firestore = new Firestore({ + projectId: 'firestore-sdk-nightly', + databaseId: 'enterprise' +}); + +async function pipelinesQuickstart() { + // Obtain a collection reference. + const collection = firestore.collection('books'); + + // Enter new documents into the document. + await collection.add({ + "title": "Whispers of the Cobalt Sea", + "price": 12.99, + "author": "Elara Vance", + "yearPublished": 2023 + }); + await collection.add({ + "title": "The Antigravity Cat's Guide to Napping", + "price": 24.50, + "author": "Mittens the IV", + "yearPublished": 2026 + }); + console.log('Entered new documents into the collection.'); + + // Define a Pipeline query that selects books published this century, + // orders them by price, and computes a discounted price (20% off). + const pipeline = firestore.pipeline().collection('books') + .where(field('yearPublished').greaterThanOrEqual(2000)) + .sort(field('price').ascending()) + .select('title', 'author', field('price').multiply(0.8).as('discountedPrice')); + + // Execute the pipeline + const pipelineSnapshot = await pipeline.execute(); + console.log('Executed the Pipeline.'); + + console.log('Results:'); + pipelineSnapshot.results.forEach(pipelineResult=> { + console.log(pipelineResult.data()); + }); +} +pipelinesQuickstart(); + +``` ## Samples @@ -107,6 +160,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-firestore/t | Sample | Source Code | Try it | | --------------------------- | --------------------------------- | ------ | | Limit-to-last-query | [source code](https://github.com/googleapis/nodejs-firestore/blob/main/samples/limit-to-last-query.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-firestore&page=editor&open_in_editor=samples/limit-to-last-query.js,samples/README.md) | +| Pipelines-quickstart | [source code](https://github.com/googleapis/nodejs-firestore/blob/main/samples/pipelines-quickstart.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-firestore&page=editor&open_in_editor=samples/pipelines-quickstart.js,samples/README.md) | | Quickstart | [source code](https://github.com/googleapis/nodejs-firestore/blob/main/samples/quickstart.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-firestore&page=editor&open_in_editor=samples/quickstart.js,samples/README.md) | | Solution-counters | [source code](https://github.com/googleapis/nodejs-firestore/blob/main/samples/solution-counters.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-firestore&page=editor&open_in_editor=samples/solution-counters.js,samples/README.md) | diff --git a/api-report/firestore.api.md b/api-report/firestore.api.md index 2715c31f1..1b166c2a3 100644 --- a/api-report/firestore.api.md +++ b/api-report/firestore.api.md @@ -4,16 +4,46 @@ ```ts -import { DocumentData } from '@google-cloud/firestore'; import { Duplex } from 'stream'; import * as firestore from '@google-cloud/firestore'; import { google } from '../protos/firestore_v1_proto_api'; +import { google as google_2 } from '../../protos/firestore_v1_proto_api'; import { GoogleError } from 'google-gax'; import * as proto from '../protos/firestore_v1_proto_api'; import * as protos from '../../protos/firestore_v1_proto_api'; import { Readable } from 'stream'; import { Span as Span_2 } from '@opentelemetry/api'; +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function abs(expr: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function abs(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function add(first: Expression, second: Expression | unknown): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function add(fieldName: string, second: Expression | unknown): FunctionExpression; + // @public export class Aggregate { constructor(alias: string, aggregateType: AggregateType, fieldPath?: (string | FieldPath) | undefined); @@ -47,6 +77,31 @@ export class AggregateField implements firestore.AggregateField { // @public export type AggregateFieldType = ReturnType | ReturnType | ReturnType; +// Warning: (ae-forgotten-export) The symbol "HasUserData" needs to be exported by the entry point index.d.ts +// +// @beta +class AggregateFunction implements AggregateFunction, HasUserData { + constructor(name: string, params: Expression[]); + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + as(name: string): AliasedAggregate; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + _createdFromLiteral: boolean; + // (undocumented) + expressionType: firestore.Pipelines.ExpressionType; + // (undocumented) + _protoValueType: "ProtoValue"; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // Warning: (ae-forgotten-export) The symbol "Serializer" needs to be exported by the entry point index.d.ts + // + // (undocumented) + _toProto(serializer: Serializer): api.IValue; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + _validateUserData(ignoreUndefinedProperties: boolean): void; +} + // @public export class AggregateQuery implements firestore.AggregateQuery { // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen @@ -73,6 +128,10 @@ export class AggregateQuery): boolean; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // @internal (undocumented) + _pipeline(): Pipeline; get query(): Query; // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen @@ -111,6 +170,284 @@ export interface AggregateSpec { // @public export type AggregateType = 'count' | 'avg' | 'sum'; +// @beta +class AliasedAggregate implements AliasedAggregate, HasUserData { + constructor(_aggregate: AggregateFunction, _alias: string); + // (undocumented) + readonly _aggregate: AggregateFunction; + // (undocumented) + readonly _alias: string; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + _createdFromLiteral: boolean; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + _validateUserData(ignoreUndefinedProperties: boolean): void; +} + +// @beta +class AliasedExpression implements firestore.Pipelines.Selectable, HasUserData { + constructor(_expr: Expression, _alias: string); + // (undocumented) + readonly _alias: string; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + _createdFromLiteral: boolean; + // (undocumented) + readonly _expr: Expression; + // (undocumented) + expressionType: firestore.Pipelines.ExpressionType; + // (undocumented) + selectable: true; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + _validateUserData(ignoreUndefinedProperties: boolean): void; +} + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function and(first: BooleanExpression, second: BooleanExpression, ...more: BooleanExpression[]): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function array(elements: unknown[]): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayConcat(firstArray: Expression, secondArray: Expression | unknown[], ...otherArrays: Array): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayConcat(firstArrayField: string, secondArray: Expression | unknown[], ...otherArrays: Array): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayContains(array: Expression, element: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayContains(array: Expression, element: unknown): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayContains(fieldName: string, element: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayContains(fieldName: string, element: unknown): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayContainsAll(array: Expression, values: Array): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayContainsAll(fieldName: string, values: Array): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayContainsAll(array: Expression, arrayExpression: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayContainsAll(fieldName: string, arrayExpression: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayContainsAny(array: Expression, values: Array): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayContainsAny(fieldName: string, values: Array): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayContainsAny(array: Expression, values: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayContainsAny(fieldName: string, values: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function arrayGet(arrayField: string, index: number): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function arrayGet(arrayField: string, indexExpr: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function arrayGet(arrayExpression: Expression, index: number): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function arrayGet(arrayExpression: Expression, indexExpr: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayLength(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayLength(array: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayReverse(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function arrayReverse(arrayExpression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function arraySum(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function arraySum(expression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function ascending(expr: Expression): Ordering; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function ascending(fieldName: string): Ordering; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function average(expression: Expression): AggregateFunction; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function average(fieldName: string): AggregateFunction; + +// @beta +class BooleanExpression extends FunctionExpression implements firestore.Pipelines.BooleanExpression { + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + conditional(thenExpr: Expression, elseExpr: Expression): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + countIf(): AggregateFunction; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + ifError(catchValue: BooleanExpression): BooleanExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + not(): BooleanExpression; + // (undocumented) + returnType: 'boolean'; +} + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration // // @public @@ -229,6 +566,48 @@ export class BundleBuilder { readonly bundleId: string; } +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function byteLength(expr: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function byteLength(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function ceil(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function ceil(expression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function charLength(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function charLength(stringExpression: Expression): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration // // @public @@ -246,11 +625,23 @@ export class CollectionGroup(converter: firestore.FirestoreDataConverter): CollectionGroup; + withConverter(converter: firestore.FirestoreDataConverter | null): CollectionGroup; } +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function collectionId(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function collectionId(expression: Expression): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration // Warning: (tsdoc-undefined-tag) The TSDoc tag "@extends" is not defined in this configuration // @@ -302,62 +693,304 @@ export class CollectionReference(converter: firestore.FirestoreDataConverter): CollectionReference; + // (undocumented) + withConverter(converter: null): CollectionReference; } -// @public (undocumented) -export const DEFAULT_MAX_IDLE_CHANNELS = 1; +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function concat(first: Expression, second: Expression | unknown, ...others: Array): FunctionExpression; -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration // -// @public -export const DEFAULT_MAX_TRANSACTION_ATTEMPTS = 5; +// @beta +function concat(fieldName: string, second: Expression | unknown, ...others: Array): FunctionExpression; -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration // -// @public -export class DocumentChange implements firestore.DocumentChange { +// @beta +function conditional(condition: BooleanExpression, thenExpr: Expression, elseExpr: Expression): FunctionExpression; + +// @beta +class Constant extends Expression { // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@hideconstructor" is not defined in this configuration // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - constructor(type: DocumentChangeType, document: QueryDocumentSnapshot, oldIndex: number, newIndex: number); - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration - get doc(): QueryDocumentSnapshot; - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration - isEqual(other: firestore.DocumentChange): boolean; - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration - get newIndex(): number; - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration - get oldIndex(): number; - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration - get type(): DocumentChangeType; -} - -// @public (undocumented) -export type DocumentChangeType = 'added' | 'removed' | 'modified'; - + constructor(value: unknown); + // (undocumented) + readonly expressionType: firestore.Pipelines.ExpressionType; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + static _fromProto(value: api.IValue): Constant; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + _toProto(serializer: Serializer): api.IValue; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + _validateUserData(ignoreUndefinedProperties: boolean): void; +} + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function constant(value: number): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function constant(value: string): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function constant(value: boolean): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function constant(value: null): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function constant(value: firestore.GeoPoint): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function constant(value: firestore.Timestamp): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function constant(value: Date): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function constant(value: Buffer | Uint8Array): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function constant(value: firestore.DocumentReference): Expression; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function constant(value: api.IValue): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function constant(value: firestore.VectorValue): Expression; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// +// @beta (undocumented) +function constant(value: unknown): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function cosineDistance(fieldName: string, vector: number[] | VectorValue): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function cosineDistance(fieldName: string, vectorExpression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function cosineDistance(vectorExpression: Expression, vector: number[] | VectorValue): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function cosineDistance(vectorExpression: Expression, otherVectorExpression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function count(expression: Expression): AggregateFunction; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function count(fieldName: string): AggregateFunction; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function countAll(): AggregateFunction; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function countDistinct(expr: Expression | string): AggregateFunction; + +// @beta +function countIf(booleanExpr: BooleanExpression): AggregateFunction; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function currentTimestamp(): FunctionExpression; + +// @public (undocumented) +export const DEFAULT_MAX_IDLE_CHANNELS = 1; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration +// +// @public +export const DEFAULT_MAX_TRANSACTION_ATTEMPTS = 5; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function descending(expr: Expression): Ordering; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function descending(fieldName: string): Ordering; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function divide(dividend: Expression, divisort: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function divide(dividend: Expression, divisor: unknown): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function divide(dividend: string, divisor: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function divide(dividend: string, divisor: unknown): FunctionExpression; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration +// +// @public +export class DocumentChange implements firestore.DocumentChange { + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + constructor(type: DocumentChangeType, document: QueryDocumentSnapshot, oldIndex: number, newIndex: number); + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration + get doc(): QueryDocumentSnapshot; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + isEqual(other: firestore.DocumentChange): boolean; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration + get newIndex(): number; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration + get oldIndex(): number; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration + get type(): DocumentChangeType; +} + +// @public (undocumented) +export type DocumentChangeType = 'added' | 'removed' | 'modified'; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function documentId(documentPath: string | firestore.DocumentReference): FunctionExpression; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function documentId(documentPathExpr: Expression): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration // Warning: (ae-forgotten-export) The symbol "Serializable" needs to be exported by the entry point index.d.ts // @@ -474,9 +1107,7 @@ export class DocumentReference | string | firestore.FieldPath, ...preconditionOrValues: Array): Promise; // (undocumented) - withConverter(converter: null): DocumentReference; - // (undocumented) - withConverter(converter: firestore.FirestoreDataConverter): DocumentReference; + withConverter(converter: firestore.FirestoreDataConverter | null): DocumentReference; } // Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration @@ -578,6 +1209,162 @@ export class DocumentSnapshot): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function equalAny(expression: Expression, arrayExpression: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function equalAny(fieldName: string, values: Array): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function equalAny(fieldName: string, arrayExpression: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function euclideanDistance(fieldName: string, vector: number[] | VectorValue): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function euclideanDistance(fieldName: string, vectorExpression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function euclideanDistance(vectorExpression: Expression, vector: number[] | VectorValue): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function euclideanDistance(vectorExpression: Expression, otherVectorExpression: Expression): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration // // @public @@ -591,7 +1378,6 @@ export class ExecutionStats implements firestore.ExecutionStats { // (undocumented) readonly executionDuration: firestore.Duration; // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // Warning: (ae-forgotten-export) The symbol "Serializer" needs to be exported by the entry point index.d.ts // // @internal (undocumented) static _fromProto(stats: IExecutionStats | null | undefined, serializer: Serializer): ExecutionStats | null; @@ -601,6 +1387,32 @@ export class ExecutionStats implements firestore.ExecutionStats { readonly resultsReturned: number; } +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function exists(value: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function exists(fieldName: string): BooleanExpression; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function exp(expression: Expression): FunctionExpression; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function exp(fieldName: string): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration // // @public @@ -633,11 +1445,435 @@ export class ExplainResults implements firestore.ExplainResults { readonly snapshot: T | null; } -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration -// Warning: (ae-forgotten-export) The symbol "Path" needs to be exported by the entry point index.d.ts -// -// @public -export class FieldPath extends Path implements firestore.FieldPath { +// @beta +abstract class Expression implements firestore.Pipelines.Expression, HasUserData { + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + abs(): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + add(second: firestore.Pipelines.Expression | unknown, ...others: Array): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + arrayConcat(secondArray: Expression | unknown[], ...otherArrays: Array): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + arrayContains(expression: Expression): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + arrayContains(value: unknown): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + arrayContainsAll(values: Array): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + arrayContainsAll(arrayExpression: Expression): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + arrayContainsAny(values: Array): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + arrayContainsAny(arrayExpression: Expression): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + arrayGet(index: number): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + arrayGet(indexExpr: Expression): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + arrayLength(): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + arrayReverse(): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + arraySum(): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + as(name: string): AliasedExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + ascending(): Ordering; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + average(): AggregateFunction; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + byteLength(): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + ceil(): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + charLength(): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + collectionId(): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + concat(second: Expression | unknown, ...others: Array): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + cosineDistance(vectorExpression: Expression): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + cosineDistance(vector: firestore.VectorValue | number[]): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + count(): AggregateFunction; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + countDistinct(): AggregateFunction; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + _createdFromLiteral: boolean; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + descending(): Ordering; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + divide(divisor: Expression): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + divide(divisor: number): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + documentId(): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + dotProduct(vectorExpression: Expression): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + dotProduct(vector: firestore.VectorValue | number[]): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + endsWith(suffix: string): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + endsWith(suffix: Expression): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + equal(expression: Expression): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + equal(value: unknown): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + equalAny(values: Array): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + equalAny(arrayExpression: Expression): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + euclideanDistance(vectorExpression: Expression): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + euclideanDistance(vector: firestore.VectorValue | number[]): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + exists(): BooleanExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + exp(): FunctionExpression; + // (undocumented) + abstract expressionType: firestore.Pipelines.ExpressionType; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + floor(): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + greaterThan(expression: Expression): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + greaterThan(value: unknown): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + greaterThanOrEqual(expression: Expression): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + greaterThanOrEqual(value: unknown): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + ifAbsent(elseValue: unknown): Expression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + ifAbsent(elseExpression: unknown): Expression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + ifError(catchExpr: Expression): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + ifError(catchValue: unknown): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + isAbsent(): BooleanExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + isError(): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + join(delimiterExpression: Expression): Expression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + join(delimiter: string): Expression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + length(): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + lessThan(experession: Expression): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + lessThan(value: unknown): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + lessThanOrEqual(expression: Expression): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + lessThanOrEqual(value: unknown): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + like(pattern: string): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + like(pattern: Expression): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + ln(): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + log10(): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + logicalMaximum(second: Expression | unknown, ...others: Array): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + logicalMinimum(second: Expression | unknown, ...others: Array): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + mapGet(subfield: string): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + mapMerge(secondMap: Record | Expression, ...otherMaps: Array | Expression>): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + mapRemove(key: string): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + mapRemove(keyExpr: Expression): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + maximum(): AggregateFunction; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + minimum(): AggregateFunction; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + mod(expression: Expression): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + mod(value: number): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + multiply(second: Expression | number, ...others: Array): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + notEqual(expression: Expression): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + notEqual(value: unknown): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + notEqualAny(values: Array): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + notEqualAny(arrayExpression: Expression): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + pow(exponent: Expression): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + pow(exponent: number): FunctionExpression; + // (undocumented) + _protoValueType: "ProtoValue"; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + regexContains(pattern: string): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + regexContains(pattern: Expression): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + regexMatch(pattern: string): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + regexMatch(pattern: Expression): BooleanExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + reverse(): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + round(): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + round(decimalPlaces: number): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + round(decimalPlaces: Expression): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + split(delimiter: string): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + split(delimiter: Expression): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + sqrt(): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + startsWith(prefix: string): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + startsWith(prefix: Expression): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + stringConcat(secondString: Expression | string, ...otherStrings: Array): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + stringContains(substring: string): BooleanExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + stringContains(expr: Expression): BooleanExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + stringReverse(): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + substring(position: number, length?: number): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + substring(position: Expression, length?: Expression): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + subtract(subtrahend: firestore.Pipelines.Expression): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + subtract(subtrahend: number): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + sum(): AggregateFunction; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + timestampAdd(unit: Expression, amount: Expression): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + timestampSubtract(unit: Expression, amount: Expression): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + timestampSubtract(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + timestampToUnixMicros(): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + timestampToUnixMillis(): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + timestampToUnixSeconds(): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + timestampTruncate(granularity: firestore.Pipelines.TimeGranularity, timezone?: string | Expression): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + timestampTruncate(granularity: Expression, timezone?: string | Expression): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + toLower(): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + abstract _toProto(serializer: Serializer): api.IValue; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + toUpper(): FunctionExpression; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + trim(valueToTrim?: string | Expression | Uint8Array | Buffer): FunctionExpression; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + type(): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + unixMicrosToTimestamp(): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + unixMillisToTimestamp(): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + unixSecondsToTimestamp(): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + abstract _validateUserData(ignoreUndefinedProperties: boolean): void; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + vectorLength(): FunctionExpression; +} + +// @beta +class Field extends Expression implements firestore.Pipelines.Selectable { + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@hideconstructor" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + constructor(fieldPath: FieldPath); + // (undocumented) + get _alias(): string; + // (undocumented) + get _expr(): Expression; + // (undocumented) + readonly expressionType: firestore.Pipelines.ExpressionType; + // (undocumented) + get fieldName(): string; + // (undocumented) + selectable: true; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + _toProto(_: Serializer): api.IValue; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + _validateUserData(_: boolean): void; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function field(field: string | firestore.FieldPath): Field; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration +// Warning: (ae-forgotten-export) The symbol "Path" needs to be exported by the entry point index.d.ts +// +// @public +export class FieldPath extends Path implements firestore.FieldPath { // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' constructor(...segments: string[]); @@ -653,6 +1889,12 @@ export class FieldPath extends Path implements firestore.FieldPath { // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" // + // @internal + static _emptyPath(): FieldPath; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // // @internal @override get formattedName(): string; // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration @@ -670,8 +1912,12 @@ export class FieldPath extends Path implements firestore.FieldPath { // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration isEqual(other: FieldPath): boolean; // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // + // @internal + get _minNumSegments(): number; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" // @@ -728,263 +1974,1217 @@ export class FieldValue implements firestore.FieldValue { // @public export abstract class Filter { // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - static and(...filters: Filter[]): Filter; + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + static and(...filters: Filter[]): Filter; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + static or(...filters: Filter[]): Filter; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + static where(fieldPath: string | firestore.FieldPath, opStr: firestore.WhereFilterOp, value: unknown): Filter; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration +// +// @public +class Firestore implements firestore.Firestore { + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + constructor(settings?: firestore.Settings); + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + batch(): WriteBatch; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + bulkWriter(options?: firestore.BulkWriterOptions): BulkWriter; + // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters + bundle(name?: string): BundleBuilder; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + collection(collectionPath: string): CollectionReference; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + collectionGroup(collectionId: string): CollectionGroup; + get databaseId(): string; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // @internal + _decrementBulkWritersCount(): void; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + doc(documentPath: string): DocumentReference; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // @internal + get formattedName(): string; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + getAll(...documentRefsOrReadOptions: Array | firestore.ReadOptions>): Promise>>; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // @internal + _incrementBulkWritersCount(): void; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // + // @internal + initializeIfNeeded(requestTag: string): Promise; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + listCollections(): Promise; + // @beta + pipeline(): PipelineSource; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // @internal + get projectId(): string; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + recursiveDelete(ref: firestore.CollectionReference | firestore.DocumentReference, bulkWriter?: BulkWriter): Promise; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // @internal + _recursiveDelete(ref: firestore.CollectionReference | firestore.DocumentReference, maxPendingOps: number, minPendingOps: number, bulkWriter?: BulkWriter): Promise; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // @internal + registerListener(): void; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (ae-forgotten-export) The symbol "FirestoreUnaryMethod" needs to be exported by the entry point index.d.ts + // + // @internal + request(methodName: FirestoreUnaryMethod, request: Req, requestTag: string, retryCodes?: number[]): Promise; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (ae-forgotten-export) The symbol "FirestoreStreamingMethod" needs to be exported by the entry point index.d.ts + // + // @internal + requestStream(methodName: FirestoreStreamingMethod, bidrectional: boolean, request: {}, requestTag: string): Promise; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@template" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + runTransaction(updateFunction: (transaction: Transaction) => Promise, transactionOptions?: firestore.ReadWriteTransactionOptions | firestore.ReadOnlyTransactionOptions): Promise; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // @internal + _serializer: Serializer | null; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + settings(settings: firestore.Settings): void; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // @internal + _settings: firestore.Settings; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + snapshot_(documentName: string, readTime?: google.protobuf.ITimestamp, encoding?: 'protobufJS'): DocumentSnapshot; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + snapshot_(documentName: string, readTime: string, encoding: 'json'): DocumentSnapshot; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + snapshot_(document: api.IDocument, readTime: google.protobuf.ITimestamp, encoding?: 'protobufJS'): QueryDocumentSnapshot; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + snapshot_(document: { + [k: string]: unknown; + }, readTime: string, encoding: 'json'): QueryDocumentSnapshot; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + terminate(): Promise; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + toJSON(): object; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // Warning: (ae-forgotten-export) The symbol "TraceUtil" needs to be exported by the entry point index.d.ts + // + // @internal + _traceUtil: TraceUtil; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // @internal + unregisterListener(): void; +} +export { Firestore } +export default Firestore; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function floor(expr: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function floor(fieldName: string): FunctionExpression; + +// @beta +class FunctionExpression extends Expression { + constructor(name: string, params: Expression[]); + // (undocumented) + readonly expressionType: firestore.Pipelines.ExpressionType; + // (undocumented) + protected name: string; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + _toProto(serializer: Serializer): api.IValue; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + _validateUserData(ignoreUndefinedProperties: boolean): void; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration +// +// @public +export class GeoPoint implements Serializable, firestore.GeoPoint { + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + constructor(latitude: number, longitude: number); + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // @internal + static fromProto(proto: google.type.ILatLng): GeoPoint; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + isEqual(other: firestore.GeoPoint): boolean; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration + get latitude(): number; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration + get longitude(): number; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // @internal + toProto(): api.IValue; +} + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function greaterThan(left: Expression, right: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function greaterThan(expression: Expression, value: unknown): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function greaterThan(fieldName: string, expression: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function greaterThan(fieldName: string, value: unknown): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function greaterThanOrEqual(left: Expression, right: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function greaterThanOrEqual(expression: Expression, value: unknown): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function greaterThanOrEqual(fieldName: string, value: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function greaterThanOrEqual(fieldName: string, value: unknown): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function ifAbsent(ifExpr: Expression, elseExpr: Expression): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function ifAbsent(ifExpr: Expression, elseValue: unknown): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function ifAbsent(ifFieldName: string, elseExpr: Expression): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function ifAbsent(ifFieldName: string | Expression, elseValue: Expression | unknown): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function ifError(tryExpr: BooleanExpression, catchExpr: BooleanExpression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function ifError(tryExpr: Expression, catchExpr: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function ifError(tryExpr: Expression, catchValue: unknown): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function isAbsent(value: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function isAbsent(field: string): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function isError(value: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function join(arrayFieldName: string, delimiter: string): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function join(arrayExpression: Expression, delimiterExpression: Expression): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function join(arrayExpression: Expression, delimiter: string): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function join(arrayFieldName: string, delimiterExpression: Expression): Expression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function length_2(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function length_2(expression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function lessThan(left: Expression, right: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function lessThan(expression: Expression, value: unknown): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function lessThan(fieldName: string, expression: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function lessThan(fieldName: string, value: unknown): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function lessThanOrEqual(left: Expression, right: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function lessThanOrEqual(expression: Expression, value: unknown): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function lessThanOrEqual(fieldName: string, expression: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function lessThanOrEqual(fieldName: string, value: unknown): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function like(fieldName: string, pattern: string): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function like(fieldName: string, pattern: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function like(stringExpression: Expression, pattern: string): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function like(stringExpression: Expression, pattern: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function ln(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function ln(expression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function log10(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function log10(expression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function logicalMaximum(first: Expression, second: Expression | unknown, ...others: Array): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function logicalMaximum(fieldName: string, second: Expression | unknown, ...others: Array): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function logicalMinimum(first: Expression, second: Expression | unknown, ...others: Array): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function logicalMinimum(fieldName: string, second: Expression | unknown, ...others: Array): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function map(elements: Record): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function mapGet(fieldName: string, subField: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function mapGet(mapExpression: Expression, subField: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// +// @beta +function mapMerge(mapField: string, secondMap: Record | Expression, ...otherMaps: Array | Expression>): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// +// @beta +function mapMerge(firstMap: Record | Expression, secondMap: Record | Expression, ...otherMaps: Array | Expression>): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// +// @beta +function mapRemove(mapField: string, key: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// +// @beta +function mapRemove(mapExpr: Expression, key: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// +// @beta +function mapRemove(mapField: string, keyExpr: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// +// @beta +function mapRemove(mapExpr: Expression, keyExpr: Expression): FunctionExpression; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration +// +// @public +export const MAX_REQUEST_RETRIES = 5; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function maximum(expression: Expression): AggregateFunction; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function maximum(fieldName: string): AggregateFunction; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function minimum(expression: Expression): AggregateFunction; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function minimum(fieldName: string): AggregateFunction; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function mod(left: Expression, right: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function mod(expression: Expression, value: unknown): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function mod(fieldName: string, expression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function mod(fieldName: string, value: unknown): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function multiply(first: Expression, second: Expression | unknown): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function multiply(fieldName: string, second: Expression | unknown): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function not(booleanExpr: BooleanExpression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function notEqual(left: Expression, right: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function notEqual(expression: Expression, value: unknown): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function notEqual(fieldName: string, expression: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function notEqual(fieldName: string, value: unknown): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function notEqualAny(element: Expression, values: Array): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function notEqualAny(fieldName: string, values: Array): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function notEqualAny(element: Expression, arrayExpression: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function notEqualAny(fieldName: string, arrayExpression: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function or(first: BooleanExpression, second: BooleanExpression, ...more: BooleanExpression[]): BooleanExpression; + +// @beta +class Ordering implements HasUserData { + constructor(expr: Expression, direction: 'ascending' | 'descending'); + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + _createdFromLiteral: boolean; + // (undocumented) + readonly direction: 'ascending' | 'descending'; + // (undocumented) + readonly expr: Expression; + // (undocumented) + _protoValueType: 'ProtoValue'; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + _toProto(serializer: Serializer): api.IValue; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // (undocumented) + _validateUserData(ignoreUndefinedProperties: boolean): void; +} + +// @beta +class Pipeline implements firestore.Pipelines.Pipeline { + // Warning: (ae-forgotten-export) The symbol "Stage" needs to be exported by the entry point index.d.ts + constructor(db: Firestore, stages: Stage[]); + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@FunctionExpression" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + addFields(field: firestore.Pipelines.Selectable, ...additionalFields: firestore.Pipelines.Selectable[]): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@FunctionExpression" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + addFields(options: firestore.Pipelines.AddFieldsStageOptions): Pipeline; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + aggregate(accumulator: firestore.Pipelines.AliasedAggregate, ...additionalAccumulators: firestore.Pipelines.AliasedAggregate[]): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + aggregate(options: firestore.Pipelines.AggregateStageOptions): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + distinct(group: string | firestore.Pipelines.Selectable, ...additionalGroups: Array): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + distinct(options: firestore.Pipelines.DistinctStageOptions): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + execute(pipelineExecuteOptions?: firestore.Pipelines.PipelineExecuteOptions): Promise; + // Warning: (ae-forgotten-export) The symbol "PipelineResponse" needs to be exported by the entry point index.d.ts + // + // (undocumented) + _execute(transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, pipelineExecuteOptions?: firestore.Pipelines.PipelineExecuteOptions): Promise; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + findNearest(options: firestore.Pipelines.FindNearestStageOptions): Pipeline; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + limit(limit: number): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + limit(options: firestore.Pipelines.LimitStageOptions): Pipeline; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + offset(offset: number): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + offset(options: firestore.Pipelines.OffsetStageOptions): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + rawStage(name: string, params: unknown[], options?: { + [key: string]: Expression | unknown; + }): Pipeline; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - static or(...filters: Filter[]): Filter; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + removeFields(fieldValue: firestore.Pipelines.Field | string, ...additionalFields: Array): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + removeFields(options: firestore.Pipelines.RemoveFieldsStageOptions): Pipeline; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + replaceWith(fieldName: string): Pipeline; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - static where(fieldPath: string | firestore.FieldPath, opStr: firestore.WhereFilterOp, value: unknown): Filter; -} - -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration -// -// @public -class Firestore implements firestore.Firestore { + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + replaceWith(expr: firestore.Pipelines.Expression): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + replaceWith(options: firestore.Pipelines.ReplaceWithStageOptions): Pipeline; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - constructor(settings?: firestore.Settings); + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + sample(documents: number): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - batch(): WriteBatch; - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - bulkWriter(options?: firestore.BulkWriterOptions): BulkWriter; - // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters - bundle(name?: string): BundleBuilder; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + sample(options: firestore.Pipelines.SampleStageOptions): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - collection(collectionPath: string): CollectionReference; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - collectionGroup(collectionId: string): CollectionGroup; - get databaseId(): string; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // - // @internal - _decrementBulkWritersCount(): void; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + select(selection: firestore.Pipelines.Selectable | string, ...additionalSelections: Array): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + select(options: firestore.Pipelines.SelectStageOptions): Pipeline; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - doc(documentPath: string): DocumentReference; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // - // @internal - get formattedName(): string; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + sort(ordering: firestore.Pipelines.Ordering, ...additionalOrderings: firestore.Pipelines.Ordering[]): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + sort(options: firestore.Pipelines.SortStageOptions): Pipeline; // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - getAll(...documentRefsOrReadOptions: Array | firestore.ReadOptions>): Promise>>; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + stream(): NodeJS.ReadableStream; + // (undocumented) + _toProto(): api.IPipeline; + // Warning: (ae-forgotten-export) The symbol "StructuredPipeline" needs to be exported by the entry point index.d.ts // - // @internal - _incrementBulkWritersCount(): void; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // (undocumented) + _toStructuredPipeline(pipelineExecuteOptions?: firestore.Pipelines.PipelineExecuteOptions): StructuredPipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration - // - // @internal - initializeIfNeeded(requestTag: string): Promise; - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - listCollections(): Promise; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // - // @internal - get projectId(): string; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + union(other: firestore.Pipelines.Pipeline): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + union(options: firestore.Pipelines.UnionStageOptions): Pipeline; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration - recursiveDelete(ref: firestore.CollectionReference | firestore.DocumentReference, bulkWriter?: BulkWriter): Promise; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // - // @internal - _recursiveDelete(ref: firestore.CollectionReference | firestore.DocumentReference, maxPendingOps: number, minPendingOps: number, bulkWriter?: BulkWriter): Promise; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // - // @internal - registerListener(): void; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + unnest(selectable: firestore.Pipelines.Selectable, indexField?: string): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration + unnest(options: firestore.Pipelines.UnnestStageOptions): Pipeline; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + _validateUserData | HasUserData[] | HasUserData>(_: string, val: T): T; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (ae-forgotten-export) The symbol "FirestoreUnaryMethod" needs to be exported by the entry point index.d.ts - // - // @internal - request(methodName: FirestoreUnaryMethod, request: Req, requestTag: string, retryCodes?: number[]): Promise; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + where(condition: firestore.Pipelines.BooleanExpression): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + where(options: firestore.Pipelines.WhereStageOptions): Pipeline; +} + +// @beta +class PipelineResult implements firestore.Pipelines.PipelineResult { // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (ae-forgotten-export) The symbol "FirestoreStreamingMethod" needs to be exported by the entry point index.d.ts - // - // @internal - requestStream(methodName: FirestoreStreamingMethod, bidrectional: boolean, request: {}, requestTag: string): Promise; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@template" is not defined in this configuration // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + constructor(serializer: Serializer, + _fieldsProto: ApiMapValue, ref?: DocumentReference, readTime?: Timestamp, createTime?: Timestamp, updateTime?: Timestamp); // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - runTransaction(updateFunction: (transaction: Transaction) => Promise, transactionOptions?: firestore.ReadWriteTransactionOptions | firestore.ReadOnlyTransactionOptions): Promise; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // - // @internal - _serializer: Serializer | null; - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - settings(settings: firestore.Settings): void; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // - // @internal - _settings: firestore.Settings; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - snapshot_(documentName: string, readTime?: google.protobuf.ITimestamp, encoding?: 'protobufJS'): DocumentSnapshot; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + get createTime(): Timestamp | undefined; // (undocumented) - snapshot_(documentName: string, readTime: string, encoding: 'json'): DocumentSnapshot; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // + readonly _createTime: Timestamp | undefined; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + data(): firestore.DocumentData; // (undocumented) - snapshot_(document: api.IDocument, readTime: google.protobuf.ITimestamp, encoding?: 'protobufJS'): QueryDocumentSnapshot; + readonly _executionTime: Timestamp | undefined; // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration // // (undocumented) - snapshot_(document: { - [k: string]: unknown; - }, readTime: string, encoding: 'json'): QueryDocumentSnapshot; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration - terminate(): Promise; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration - toJSON(): object; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // Warning: (ae-forgotten-export) The symbol "TraceUtil" needs to be exported by the entry point index.d.ts - // - // @internal - _traceUtil: TraceUtil; - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // - // @internal - unregisterListener(): void; -} -export { Firestore } -export default Firestore; - -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration -// -// @public -export class GeoPoint implements Serializable, firestore.GeoPoint { - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + readonly _fieldsProto: ApiMapValue; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - constructor(latitude: number, longitude: number); - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // - // @internal - static fromProto(proto: google.type.ILatLng): GeoPoint; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + get(fieldPath: string | FieldPath): any; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + get id(): string | undefined; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration - isEqual(other: firestore.GeoPoint): boolean; + isEqual(other: PipelineResult): boolean; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + protoField(field: string | FieldPath): api.IValue | undefined; + get ref(): DocumentReference | undefined; // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration - get latitude(): number; + get updateTime(): Timestamp | undefined; + // (undocumented) + readonly _updateTime: Timestamp | undefined; +} + +declare namespace Pipelines { + export { + Pipeline, + PipelineResult, + PipelineSnapshot, + PipelineSource, + and, + arrayContains, + arrayContainsAny, + arrayReverse, + average, + equal, + ceil, + exp, + floor, + greaterThan, + like, + lessThan, + notEqual, + ascending, + not, + or, + regexContains, + regexMatch, + startsWith, + stringConcat, + subtract, + cosineDistance, + countDistinct, + dotProduct, + euclideanDistance, + mapGet, + lessThanOrEqual, + equalAny, + map, + array, + field, + xor, + AggregateFunction, + arrayGet, + add, + BooleanExpression, + Expression, + FunctionExpression, + minimum, + count, + countIf, + arrayLength, + stringContains, + charLength, + divide, + mod, + reverse, + trim, + toUpper, + toLower, + vectorLength, + exists, + isAbsent, + ifError, + isError, + substring, + documentId, + arrayContainsAll, + constant, + Field, + Constant, + sum, + maximum, + descending, + greaterThanOrEqual, + multiply, + conditional, + Ordering, + AliasedAggregate, + endsWith, + AliasedExpression, + mapMerge, + mapRemove, + byteLength, + logicalMaximum, + logicalMinimum, + notEqualAny, + countAll, + timestampAdd, + timestampSubtract, + timestampToUnixMicros, + timestampToUnixSeconds, + unixMicrosToTimestamp, + timestampToUnixMillis, + unixSecondsToTimestamp, + unixMillisToTimestamp, + pow, + collectionId, + length_2 as length, + ln, + round, + sqrt, + stringReverse, + abs, + arraySum, + ifAbsent, + log10, + concat, + join, + currentTimestamp, + arrayConcat, + type, + timestampTruncate, + split + } +} +export { Pipelines } + +// @beta +class PipelineSnapshot implements firestore.Pipelines.PipelineSnapshot { + // Warning: (ae-forgotten-export) The symbol "ExplainStats" needs to be exported by the entry point index.d.ts + constructor(pipeline: Pipeline, results: PipelineResult[], executionTime?: Timestamp, explainStats?: ExplainStats); // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration - get longitude(): number; + get executionTime(): Timestamp; + get explainStats(): ExplainStats | undefined; + get pipeline(): Pipeline; + get results(): PipelineResult[]; +} + +// @beta +class PipelineSource implements firestore.Pipelines.PipelineSource { + constructor(db: Firestore); + collection(collection: string | firestore.CollectionReference): Pipeline; + collection(options: firestore.Pipelines.CollectionStageOptions): Pipeline; + collectionGroup(collectionId: string): Pipeline; + collectionGroup(options: firestore.Pipelines.CollectionGroupStageOptions): Pipeline; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@FirestoreError" is not defined in this configuration + createFrom(query: firestore.VectorQuery): Pipeline; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@FirestoreError" is not defined in this configuration + createFrom(query: firestore.Query): Pipeline; + database(): Pipeline; + database(options: firestore.Pipelines.DatabaseStageOptions): Pipeline; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@FirestoreError" is not defined in this configuration + documents(docs: Array): Pipeline; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@FirestoreError" is not defined in this configuration + documents(options: firestore.Pipelines.DocumentsStageOptions): Pipeline; + // (undocumented) + _validateReference(reference: firestore.CollectionReference | firestore.DocumentReference): reference is CollectionReference | DocumentReference; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration +// +// @public +export class PlanSummary implements firestore.PlanSummary { // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration // // @internal - toProto(): api.IValue; + constructor(indexesUsed: Record[]); + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // + // @internal (undocumented) + static _fromProto(plan: IPlanSummary | null | undefined, serializer: Serializer): PlanSummary; + // (undocumented) + readonly indexesUsed: Record[]; } -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration // -// @public -export const MAX_REQUEST_RETRIES = 5; +// @beta +function pow(base: Expression, exponent: Expression): FunctionExpression; -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function pow(base: Expression, exponent: number): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function pow(base: string, exponent: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration // -// @public -export class PlanSummary implements firestore.PlanSummary { - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // - // @internal - constructor(indexesUsed: Record[]); - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // - // @internal (undocumented) - static _fromProto(plan: IPlanSummary | null | undefined, serializer: Serializer): PlanSummary; - // (undocumented) - readonly indexesUsed: Record[]; -} +// @beta +function pow(base: string, exponent: number): FunctionExpression; // Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration // @@ -1072,6 +3272,7 @@ export class Query; // (undocumented) - withConverter(converter: null): Query; - // (undocumented) - withConverter(converter: firestore.FirestoreDataConverter): Query; + withConverter(converter: firestore.FirestoreDataConverter | null): Query; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@extends" is not defined in this configuration +// +// @public +export class QueryDocumentSnapshot extends DocumentSnapshot implements firestore.QueryDocumentSnapshot { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration + // + // @override + get createTime(): Timestamp; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // + // @override + data(): AppModelType; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration + // + // @override + get updateTime(): Timestamp; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration +// +// @public +export class QueryPartition implements firestore.QueryPartition { + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + constructor(_firestore: Firestore, _collectionId: string, _converter: firestore.FirestoreDataConverter, _startAt: api.IValue[] | undefined, _endBefore: api.IValue[] | undefined); + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + get endBefore(): unknown[] | undefined; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + get startAt(): unknown[] | undefined; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + toQuery(): Query; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration +// +// @public +export class QuerySnapshot implements firestore.QuerySnapshot { + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + constructor(_query: Query, _readTime: Timestamp, _size: number, docs: () => Array>, changes: () => Array>); + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + docChanges(): Array>; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration + get docs(): Array>; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration + get empty(): boolean; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + forEach(callback: (result: firestore.QueryDocumentSnapshot) => void, thisArg?: unknown): void; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + isEqual(other: firestore.QuerySnapshot): boolean; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration + get query(): Query; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration + get readTime(): Timestamp; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration + get size(): number; } -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@extends" is not defined in this configuration +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function regexContains(fieldName: string, pattern: string): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function regexContains(fieldName: string, pattern: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function regexContains(stringExpression: Expression, pattern: string): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function regexContains(stringExpression: Expression, pattern: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function regexMatch(fieldName: string, pattern: string): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function regexMatch(fieldName: string, pattern: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function regexMatch(stringExpression: Expression, pattern: string): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function regexMatch(stringExpression: Expression, pattern: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function reverse(stringExpression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function reverse(field: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function round(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function round(expression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function round(fieldName: string, decimalPlaces: number | Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function round(expression: Expression, decimalPlaces: number | Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// +// @public +export function setLogFunction(logger: ((msg: string) => void) | null): void; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function split(fieldName: string, delimiter: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function split(fieldName: string, delimiter: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function split(expression: Expression, delimiter: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function split(expression: Expression, delimiter: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function sqrt(expression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function sqrt(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function startsWith(fieldName: string, prefix: string): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function startsWith(fieldName: string, prefix: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function startsWith(stringExpression: Expression, prefix: string): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function startsWith(stringExpression: Expression, prefix: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function stringConcat(fieldName: string, secondString: Expression | string, ...otherStrings: Array): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function stringConcat(firstString: Expression, secondString: Expression | string, ...otherStrings: Array): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function stringContains(fieldName: string, substring: string): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function stringContains(fieldName: string, substring: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function stringContains(stringExpression: Expression, substring: string): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function stringContains(stringExpression: Expression, substring: Expression): BooleanExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function stringReverse(stringExpression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function stringReverse(field: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// +// @beta +function substring(field: string, position: number, length?: number): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// +// @beta +function substring(input: Expression, position: number, length?: number): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // -// @public -export class QueryDocumentSnapshot extends DocumentSnapshot implements firestore.QueryDocumentSnapshot { - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration - // - // @override - get createTime(): Timestamp; - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // - // @override - data(): AppModelType; - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration - // - // @override - get updateTime(): Timestamp; -} +// @beta +function substring(field: string, position: Expression, length?: Expression): FunctionExpression; -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // -// @public -export class QueryPartition implements firestore.QueryPartition { - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - constructor(_firestore: Firestore, _collectionId: string, _converter: firestore.FirestoreDataConverter, _startAt: api.IValue[] | undefined, _endBefore: api.IValue[] | undefined); - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration - get endBefore(): unknown[] | undefined; - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration - get startAt(): unknown[] | undefined; - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration - toQuery(): Query; -} +// @beta +function substring(input: Expression, position: Expression, length?: Expression): FunctionExpression; -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration // -// @public -export class QuerySnapshot implements firestore.QuerySnapshot { - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - constructor(_query: Query, _readTime: Timestamp, _size: number, docs: () => Array>, changes: () => Array>); - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration - docChanges(): Array>; - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration - get docs(): Array>; - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration - get empty(): boolean; - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - forEach(callback: (result: firestore.QueryDocumentSnapshot) => void, thisArg?: unknown): void; - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration - isEqual(other: firestore.QuerySnapshot): boolean; - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration - get query(): Query; - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration - get readTime(): Timestamp; - // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag - // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@name" is not defined in this configuration - get size(): number; -} +// @beta +function subtract(minuend: Expression, subtrahend: Expression): FunctionExpression; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration // -// @public -export function setLogFunction(logger: ((msg: string) => void) | null): void; +// @beta +function subtract(minuend: Expression, subtrahend: unknown): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function subtract(minuendFieldName: string, subtrahend: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function subtract(minuendFieldName: string, subtrahend: unknown): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function sum(expression: Expression): AggregateFunction; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function sum(fieldName: string): AggregateFunction; // @public export class Timestamp implements firestore.Timestamp { @@ -1366,6 +3889,186 @@ export class Timestamp implements firestore.Timestamp { valueOf(): string; } +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function timestampAdd(timestamp: Expression, unit: Expression, amount: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function timestampAdd(timestamp: Expression, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function timestampAdd(fieldName: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function timestampSubtract(timestamp: Expression, unit: Expression, amount: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function timestampSubtract(timestamp: Expression, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function timestampSubtract(fieldName: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function timestampToUnixMicros(expr: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function timestampToUnixMicros(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function timestampToUnixMillis(expr: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function timestampToUnixMillis(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function timestampToUnixSeconds(expr: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function timestampToUnixSeconds(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +function timestampTruncate(fieldName: string, granularity: firestore.Pipelines.TimeGranularity, timezone?: string | Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +function timestampTruncate(fieldName: string, granularity: Expression, timezone?: string | Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +function timestampTruncate(timestampExpression: Expression, granularity: firestore.Pipelines.TimeGranularity, timezone?: string | Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "timestampTruncate" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +function timestampTruncate(timestampExpression: Expression, granularity: Expression, timezone?: string | Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function toLower(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function toLower(stringExpression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function toUpper(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function toUpper(stringExpression: Expression): FunctionExpression; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration // // @public @@ -1401,6 +4104,10 @@ export class Transaction implements firestore.Transaction { // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" delete(documentRef: DocumentReference, precondition?: firestore.Precondition): this; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + // Warning: (ae-incompatible-release-tags) The symbol "execute" is marked as @public, but its signature references "Pipeline" which is marked as @beta + // Warning: (ae-incompatible-release-tags) The symbol "execute" is marked as @public, but its signature references "PipelineSnapshot" which is marked as @beta + execute(pipeline: Pipeline): Promise; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag @@ -1451,6 +4158,92 @@ export class Transaction implements firestore.Transaction { update(documentRef: firestore.DocumentReference, dataOrField: firestore.UpdateData | string | firestore.FieldPath, ...preconditionOrValues: Array): Transaction; } +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function trim(fieldName: string, valueToTrim?: string | Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function trim(stringExpression: Expression, valueToTrim?: string | Expression): FunctionExpression; + +// Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function type(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// +// @beta +function type(expression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function unixMicrosToTimestamp(expr: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function unixMicrosToTimestamp(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function unixMillisToTimestamp(expr: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function unixMillisToTimestamp(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function unixSecondsToTimestamp(expr: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function unixSecondsToTimestamp(fieldName: string): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function vectorLength(vectorExpression: Expression): FunctionExpression; + +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@code" is not defined in this configuration +// +// @beta +function vectorLength(fieldName: string): FunctionExpression; + // @public export class VectorQuery implements firestore.VectorQuery { // Warning: (tsdoc-undefined-tag) The TSDoc tag "@private" is not defined in this configuration @@ -1482,6 +4275,10 @@ export class VectorQuery, + > | null, ): CollectionGroup; withConverter< NewAppModelType, diff --git a/dev/src/index.ts b/dev/src/index.ts index 2129ca121..178fc187e 100644 --- a/dev/src/index.ts +++ b/dev/src/index.ts @@ -94,6 +94,10 @@ import { } from './telemetry/trace-util'; import {DisabledTraceUtil} from './telemetry/disabled-trace-util'; import {EnabledTraceUtil} from './telemetry/enabled-trace-util'; +import {PipelineSource} from './pipelines'; + +import * as Pipelines from './pipelines'; +export {Pipelines}; export {CollectionReference} from './reference/collection-reference'; export {DocumentReference} from './reference/document-reference'; @@ -944,6 +948,19 @@ export class Firestore implements firestore.Firestore { return new CollectionGroup(this, collectionId, /* converter= */ undefined); } + /** + * @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 { + return new PipelineSource(this); + } + /** * Creates a [WriteBatch]{@link WriteBatch}, used for performing * multiple writes as a single atomic operation. diff --git a/dev/src/path.ts b/dev/src/path.ts index d67a26790..0b250b161 100644 --- a/dev/src/path.ts +++ b/dev/src/path.ts @@ -43,8 +43,7 @@ export const DEFAULT_DATABASE_ID = '(default)'; * @type {RegExp} */ const RESOURCE_PATH_RE = - // Note: [\s\S] matches all characters including newlines. - /^projects\/([^/]*)\/databases\/([^/]*)(?:\/documents\/)?([\s\S]*)$/; + /^projects\/([^/]+)\/databases\/([^/]+)(?:\/documents(?:\/([^/]+(?:\/[^/]+)*))?)?$/; /*! * A regular expression to verify whether a field name can be passed to the @@ -236,8 +235,45 @@ abstract class Path { * @returns The newly created Path. */ popLast(): T { - this.segments.pop(); - return this.construct(this.segments); + return this.construct(this.segments.slice(0, -1)); + } + + /** + * Returns the last segment from this `Path`; + * + * @private + * @internal + * @returns The last segment of this Path. + */ + lastSegment(): string { + return this.get(this.size - 1); + } + + /** + * Returns the path segment at the specified index. + * @param index + */ + get(index: number): string { + return this.segments[index]; + } + + /** + * Returns `true` if this path is the immediate + * parent of the given `potentialChild` path. + * @param potentialChild + */ + isImmediateParentOf(potentialChild: this): boolean { + if (this.size + 1 !== potentialChild.size) { + return false; + } + + for (let i = 0; i < this.size; i++) { + if (this.get(i) !== potentialChild.get(i)) { + return false; + } + } + + return true; } /** @@ -418,8 +454,13 @@ export class QualifiedResourcePath extends ResourcePath { if (elements) { const project = elements[1]; const database = elements[2]; - const path = elements[3]; - return new QualifiedResourcePath(project, database).append(path); + const root = new QualifiedResourcePath(project, database); + + if (!elements[3]) { + return root; + } else { + return root.append(elements[3]); + } } throw new Error(`Resource name '${absolutePath}' is not valid.`); @@ -585,6 +626,15 @@ export class FieldPath extends Path implements firestore.FieldPath { */ private static _DOCUMENT_ID = new FieldPath('__name__'); + /** + * @private + * @internal + * Defines the minimum number of segments allowed in a FieldPath + */ + get _minNumSegments(): number { + return 1; + } + /** * Constructs a Firestore Field Path. * @@ -610,8 +660,6 @@ export class FieldPath extends Path implements firestore.FieldPath { ); } - validateMinNumberOfArguments('FieldPath', segments, 1); - for (let i = 0; i < segments.length; ++i) { validateString(i, segments[i]); if (segments[i].length === 0) { @@ -620,6 +668,8 @@ export class FieldPath extends Path implements firestore.FieldPath { } super(segments); + + validateMinNumberOfArguments('FieldPath', segments, this._minNumSegments); } /** @@ -632,6 +682,19 @@ export class FieldPath extends Path implements firestore.FieldPath { return FieldPath._DOCUMENT_ID; } + /** + * Create an empty FieldPath. + * @private + * @internal + * + * @returns {FieldPath} + */ + static _emptyPath(): FieldPath { + const result = new FieldPath('empty'); + result.segments.pop(); + return result; + } + /** * Turns a field path argument into a [FieldPath]{@link FieldPath}. * Supports FieldPaths as input (which are passed through) and dot-separated @@ -717,6 +780,46 @@ export class FieldPath extends Path implements firestore.FieldPath { return super.isEqual(other); } } +/** + * A dot-separated path for navigating ObjectValues. + * This type is like its parent, FieldPath, except + * it allows for zero length segments. + * + * @private + * @internal + * @class + */ +export class ObjectValueFieldPath extends FieldPath { + /** + * Zero length field paths are allowed in object value traverse. + */ + get _minNumSegments(): number { + return 0; + } + + constructor(...segments: string[]) { + super(...segments); + } + + static fromDotNotation(path: string): ObjectValueFieldPath { + return new ObjectValueFieldPath(...path.split('.')); + } + + /** + * Constructs a new instance of FieldPath. We need this instead of using + * the normal constructor because polymorphic 'this' doesn't work on static + * methods. + * + * @private + * @internal + * @override + * @param segments Sequence of field names. + * @returns The newly created FieldPath. + */ + construct(segments: string[]): FieldPath { + return new ObjectValueFieldPath(...segments); + } +} /** * Validates that the provided value can be used as a field path argument. diff --git a/dev/src/pipelines/expression.ts b/dev/src/pipelines/expression.ts new file mode 100644 index 000000000..e095cdd5d --- /dev/null +++ b/dev/src/pipelines/expression.ts @@ -0,0 +1,7766 @@ +// 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 * as protos from '../../protos/firestore_v1_proto_api'; +import api = protos.google.firestore.v1; + +import type * as firestore from '@google-cloud/firestore'; + +import {VectorValue} from '../field-value'; +import {FieldPath} from '../path'; +import { + fieldOrExpression, + isFirestoreValue, + isString, + valueToDefaultExpr, + vectorToExpr, +} from './pipeline-util'; +import {HasUserData, Serializer, validateUserInput} from '../serializer'; +import {cast} from '../util'; + +/** + * @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 `Expression` class provides a fluent API for building expressions. You can chain together + * method calls to create complex expressions. + */ +export abstract class Expression + implements firestore.Pipelines.Expression, HasUserData +{ + abstract expressionType: firestore.Pipelines.ExpressionType; + + /** + * @beta + * @internal + * @private + * Indicates if this expression was created from a literal value passed + * by the caller. + */ + _createdFromLiteral = false; + + /** + * @beta + * @private + * @internal + */ + abstract _toProto(serializer: Serializer): api.IValue; + _protoValueType = 'ProtoValue' as const; + + /** + * @beta + * @private + * @internal + */ + abstract _validateUserData(ignoreUndefinedProperties: boolean): void; + + /** + * @beta + * 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 `Expression` representing the addition operation. + */ + add( + second: firestore.Pipelines.Expression | unknown, + ...others: Array + ): FunctionExpression { + const values = [second, ...others]; + return new FunctionExpression('add', [ + this, + ...values.map(value => valueToDefaultExpr(value)), + ]); + } + + /** + * @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 Error( + `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 `Expression` representing the subtraction operation. + */ + subtract(subtrahend: firestore.Pipelines.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 `Expression` representing the subtraction operation. + */ + subtract(subtrahend: number): FunctionExpression; + subtract( + subtrahend: number | firestore.Pipelines.Expression, + ): FunctionExpression { + return new FunctionExpression('subtract', [ + this, + valueToDefaultExpr(subtrahend), + ]); + } + + /** + * @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 `Expression` representing the multiplication operation. + */ + multiply( + second: Expression | number, + ...others: Array + ): FunctionExpression { + return new FunctionExpression('multiply', [ + this, + valueToDefaultExpr(second), + ...others.map(value => valueToDefaultExpr(value)), + ]); + } + + /** + * @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 `Expression` 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 `Expression` representing the division operation. + */ + divide(divisor: number): FunctionExpression; + divide(divisor: number | Expression): FunctionExpression { + return new FunctionExpression('divide', [ + this, + valueToDefaultExpr(divisor), + ]); + } + + /** + * @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 `Expression` 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 `Expression` representing the modulo operation. + */ + mod(value: number): FunctionExpression; + mod(other: number | Expression): FunctionExpression { + return new FunctionExpression('mod', [this, valueToDefaultExpr(other)]); + } + + /** + * @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 `Expression` 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 `Expression` representing the equality comparison. + */ + equal(value: unknown): BooleanExpression; + equal(other: unknown): BooleanExpression { + return new FunctionExpression('equal', [ + this, + valueToDefaultExpr(other), + ]).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 `Expression` 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 `Expression` representing the inequality comparison. + */ + notEqual(value: unknown): BooleanExpression; + notEqual(other: unknown): BooleanExpression { + return new FunctionExpression('not_equal', [ + this, + valueToDefaultExpr(other), + ]).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 `Expression` 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 `Expression` representing the less than comparison. + */ + lessThan(value: unknown): BooleanExpression; + lessThan(other: unknown): BooleanExpression { + return new FunctionExpression('less_than', [ + this, + valueToDefaultExpr(other), + ]).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").lessThanOrEqual(constant(20)); + * ``` + * + * @param expression The expression to compare for less than or equal to. + * @return A new `Expression` 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").lessThanOrEqual(70); + * ``` + * + * @param value The constant value to compare for less than or equal to. + * @return A new `Expression` 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), + ]).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 `Expression` 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 `Expression` representing the greater than comparison. + */ + greaterThan(value: unknown): BooleanExpression; + greaterThan(other: unknown): BooleanExpression { + return new FunctionExpression('greater_than', [ + this, + valueToDefaultExpr(other), + ]).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 `Expression` 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 `Expression` 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), + ]).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]); + } + + /** + * @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 `Expression` 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 `Expression` representing the 'array_contains' comparison. + */ + arrayContains(value: unknown): BooleanExpression; + arrayContains(element: unknown): BooleanExpression { + return new FunctionExpression('array_contains', [ + this, + valueToDefaultExpr(element), + ]).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 `Expression` 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 `Expression` 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)) + : cast(values); + return new FunctionExpression('array_contains_all', [ + this, + normalizedExpr, + ]).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 `Expression` 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 `Expression` 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)) + : cast(values); + return new FunctionExpression('array_contains_any', [ + this, + normalizedExpr, + ]).asBoolean(); + } + + /** + * @beta + * Creates an expression that reverses an array. + * + * ```typescript + * // Reverse the value of the 'myArray' field. + * field("myArray").arrayReverse(); + * ``` + * + * @return A new {@code Expression} 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 `Expression` representing the length of the array. + */ + arrayLength(): FunctionExpression { + return new FunctionExpression('array_length', [this]); + } + + /** + * @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 `Expression` 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 `Expression` representing the 'IN' comparison. + */ + equalAny(arrayExpression: Expression): BooleanExpression; + equalAny(others: unknown[] | Expression): BooleanExpression { + const exprOthers = Array.isArray(others) + ? new ListOfExprs(others.map(valueToDefaultExpr)) + : cast(others); + return new FunctionExpression('equal_any', [this, exprOthers]).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 `Expression` representing the 'NotEqAny' 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 `Expression` representing the 'NotEqAny' comparison. + */ + notEqualAny(arrayExpression: Expression): BooleanExpression; + notEqualAny(others: unknown[] | Expression): BooleanExpression { + const exprOthers = Array.isArray(others) + ? new ListOfExprs(others.map(valueToDefaultExpr)) + : cast(others); + return new FunctionExpression('not_equal_any', [ + this, + exprOthers, + ]).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 `Expression` representing the 'exists' check. + */ + exists(): BooleanExpression { + return new FunctionExpression('exists', [this]).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 `Expression` representing the length of the string. + */ + charLength(): FunctionExpression { + return new FunctionExpression('char_length', [this]); + } + + /** + * @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 `Expression` 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 `Expression` representing the 'like' comparison. + */ + like(pattern: Expression): BooleanExpression; + like(stringOrExpr: string | Expression): BooleanExpression { + return new FunctionExpression('like', [ + this, + valueToDefaultExpr(stringOrExpr), + ]).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 `Expression` 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 `Expression` representing the 'contains' comparison. + */ + regexContains(pattern: Expression): BooleanExpression; + regexContains(stringOrExpr: string | Expression): BooleanExpression { + return new FunctionExpression('regex_contains', [ + this, + valueToDefaultExpr(stringOrExpr), + ]).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 `Expression` 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 `Expression` representing the regular expression match. + */ + regexMatch(pattern: Expression): BooleanExpression; + regexMatch(stringOrExpr: string | Expression): BooleanExpression { + return new FunctionExpression('regex_match', [ + this, + valueToDefaultExpr(stringOrExpr), + ]).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 `Expression` 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 `Expression` representing the 'contains' comparison. + */ + stringContains(expr: Expression): BooleanExpression; + stringContains(stringOrExpr: string | Expression): BooleanExpression { + return new FunctionExpression('string_contains', [ + this, + valueToDefaultExpr(stringOrExpr), + ]).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 `Expression` 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 `Expression` representing the 'starts with' comparison. + */ + startsWith(prefix: Expression): BooleanExpression; + startsWith(stringOrExpr: string | Expression): BooleanExpression { + return new FunctionExpression('starts_with', [ + this, + valueToDefaultExpr(stringOrExpr), + ]).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 `Expression` 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 `Expression` representing the 'ends with' comparison. + */ + endsWith(suffix: Expression): BooleanExpression; + endsWith(stringOrExpr: string | Expression): BooleanExpression { + return new FunctionExpression('ends_with', [ + this, + valueToDefaultExpr(stringOrExpr), + ]).asBoolean(); + } + + /** + * @beta + * Creates an expression that converts a string to lowercase. + * + * ```typescript + * // Convert the 'name' field to lowercase + * field("name").toLower(); + * ``` + * + * @return A new `Expression` representing the lowercase string. + */ + toLower(): FunctionExpression { + return new FunctionExpression('to_lower', [this]); + } + + /** + * @beta + * Creates an expression that converts a string to uppercase. + * + * ```typescript + * // Convert the 'title' field to uppercase + * field("title").toUpper(); + * ``` + * + * @return A new `Expression` representing the uppercase string. + */ + toUpper(): FunctionExpression { + return new FunctionExpression('to_upper', [this]); + } + + /** + * @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 | Uint8Array | Buffer, + ): FunctionExpression { + const args: Expression[] = [this]; + if (valueToTrim) { + args.push(valueToDefaultExpr(valueToTrim)); + } + return new FunctionExpression('trim', args); + } + + /** + * @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 `Expression` 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]); + } + + /** + * @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]); + } + + /** + * @beta + * Creates an expression that reverses this string or bytes expression. + * + * ```typescript + * // Reverse the value of the 'myString' field. + * field("myString").reverse(); + * ``` + * + * @return A new {@code Expression} representing the reversed string or bytes. + */ + reverse(): FunctionExpression { + return new FunctionExpression('reverse', [this]); + } + + /** + * @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 Expression} representing the length of the string in bytes. + */ + byteLength(): FunctionExpression { + return new FunctionExpression('byte_length', [this]); + } + + /** + * @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 Expression} 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 Expression} 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 Expression} 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 `Expression` representing the value associated with the given key in the map. + */ + mapGet(subfield: string): FunctionExpression { + return new FunctionExpression('map_get', [this, constant(subfield)]); + } + + /** + * @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 new AggregateFunction('count', [this]); + } + + /** + * @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 new AggregateFunction('sum', [this]); + } + + /** + * @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 new AggregateFunction('average', [this]); + } + + /** + * @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 'min' aggregation. + */ + minimum(): AggregateFunction { + return new AggregateFunction('minimum', [this]); + } + + /** + * @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 'max' aggregation. + */ + maximum(): AggregateFunction { + return new AggregateFunction('maximum', [this]); + } + + /** + * @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 new AggregateFunction('count_distinct', [this]); + } + + /** + * @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 Expression} representing the logical max operation. + */ + logicalMaximum( + second: Expression | unknown, + ...others: Array + ): FunctionExpression { + const values = [second, ...others]; + return new FunctionExpression('maximum', [ + this, + ...values.map(valueToDefaultExpr), + ]); + } + + /** + * @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 Expression} representing the logical min operation. + */ + logicalMinimum( + second: Expression | unknown, + ...others: Array + ): FunctionExpression { + const values = [second, ...others]; + return new FunctionExpression('minimum', [ + this, + ...values.map(valueToDefaultExpr), + ]); + } + + /** + * @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 Expression} representing the length of the vector. + */ + vectorLength(): FunctionExpression { + return new FunctionExpression('vector_length', [this]); + } + + /** + * @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 Expression) to compare against. + * @return A new `Expression` 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 `Expression` representing the Cosine* distance between the two vectors. + */ + cosineDistance(vector: firestore.VectorValue | number[]): FunctionExpression; + cosineDistance( + other: Expression | firestore.VectorValue | number[], + ): FunctionExpression { + return new FunctionExpression('cosine_distance', [ + this, + vectorToExpr(other), + ]); + } + + /** + * @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 `Expression` 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 `Expression` representing the dot product between the two vectors. + */ + dotProduct(vector: firestore.VectorValue | number[]): FunctionExpression; + dotProduct( + other: Expression | firestore.VectorValue | number[], + ): FunctionExpression { + return new FunctionExpression('dot_product', [this, vectorToExpr(other)]); + } + + /** + * @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 `Expression` 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 `Expression` representing the Euclidean distance between the two vectors. + */ + euclideanDistance( + vector: firestore.VectorValue | number[], + ): FunctionExpression; + euclideanDistance( + other: Expression | firestore.VectorValue | number[], + ): FunctionExpression { + return new FunctionExpression('euclidean_distance', [ + this, + vectorToExpr(other), + ]); + } + + /** + * @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 Expression} representing the timestamp. + */ + unixMicrosToTimestamp(): FunctionExpression { + return new FunctionExpression('unix_micros_to_timestamp', [this]); + } + + /** + * @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 Expression} representing the number of microseconds since epoch. + */ + timestampToUnixMicros(): FunctionExpression { + return new FunctionExpression('timestamp_to_unix_micros', [this]); + } + + /** + * @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 Expression} representing the timestamp. + */ + unixMillisToTimestamp(): FunctionExpression { + return new FunctionExpression('unix_millis_to_timestamp', [this]); + } + + /** + * @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 Expression} representing the number of milliseconds since epoch. + */ + timestampToUnixMillis(): FunctionExpression { + return new FunctionExpression('timestamp_to_unix_millis', [this]); + } + + /** + * @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 Expression} representing the timestamp. + */ + unixSecondsToTimestamp(): FunctionExpression { + return new FunctionExpression('unix_seconds_to_timestamp', [this]); + } + + /** + * @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 Expression} representing the number of seconds since epoch. + */ + timestampToUnixSeconds(): FunctionExpression { + return new FunctionExpression('timestamp_to_unix_seconds', [this]); + } + + /** + * @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 Expression} 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 Expression} 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), + ]); + } + + /** + * @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 Expression} 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 Expression} 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), + ]); + } + + /** + * @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 Expression} representing the documentId operation. + */ + documentId(): FunctionExpression { + return new FunctionExpression('document_id', [this]); + } + + /** + * @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]); + } else { + return new FunctionExpression('substring', [ + this, + positionExpr, + valueToDefaultExpr(length), + ]); + } + } + + /** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and returns the element. If the index exceeds the array length, an error is + * returned. A negative index, starts from the end. + * + * ```typescript + * // Return the value in the 'tags' field array at index `1`. + * field('tags').arrayGet(1); + * ``` + * + * @param index The index of the element to return. + * @return A new Expression representing the 'arrayGet' operation. + */ + arrayGet(index: number): FunctionExpression; + + /** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and returns the element. If the index exceeds the array length, an error is + * returned. A negative index, 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 indexExpr An Expression evaluating to the index of the element to return. + * @return A new Expression representing the 'arrayGet' operation. + */ + arrayGet(indexExpr: Expression): FunctionExpression; + arrayGet(index: Expression | number): FunctionExpression { + return new FunctionExpression('array_get', [ + this, + valueToDefaultExpr(index), + ]); + } + + /** + * @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 BooleanExpression} representing the 'isError' check. + */ + isError(): BooleanExpression { + return new FunctionExpression('is_error', [this]).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 Expression} 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 Expression} representing the 'ifError' operation. + */ + ifError(catchValue: unknown): FunctionExpression; + ifError(catchValue: unknown): FunctionExpression { + return new FunctionExpression('if_error', [ + this, + valueToDefaultExpr(catchValue), + ]); + } + + /** + * @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 BooleanExpression} representing the 'isAbsent' check. + */ + isAbsent(): BooleanExpression { + return new FunctionExpression('is_absent', [this]).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), + ]); + } + + /** + * @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, + ]); + } + + /** + * @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 `Expression` 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 `Expression` 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 `Expression` 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), + ]); + } + } + + /** + * @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 Expression} 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 `Expression` 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 Expression} 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 Expression} 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 Expression} 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), + ]); + } + + /** + * @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), + ]); + } + + /** + * @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: firestore.Pipelines.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: firestore.Pipelines.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); + } +} + +/** + * @beta + * A class that represents an aggregate function. + */ +export class AggregateFunction implements AggregateFunction, HasUserData { + expressionType: firestore.Pipelines.ExpressionType = 'AggregateFunction'; + + /** + * @beta + * @internal + * @private + * Indicates if this expression was created from a literal value passed + * by the caller. + */ + _createdFromLiteral = false; + + /** + * @beta + * @private + * @internal + */ + _validateUserData(ignoreUndefinedProperties: boolean): void { + this.params.forEach(expr => { + return expr._validateUserData(ignoreUndefinedProperties); + }); + } + + constructor( + private name: string, + private params: Expression[], + ) {} + + /** + * @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); + } + + /** + * @beta + * @private + * @internal + */ + _toProto(serializer: Serializer): api.IValue { + return { + functionValue: { + name: this.name, + args: this.params.map(p => p._toProto(serializer)), + }, + }; + } + + _protoValueType = 'ProtoValue' as const; +} + +/** + * @beta + * An AggregateFunction with alias. + */ +export class AliasedAggregate implements AliasedAggregate, HasUserData { + constructor( + readonly _aggregate: AggregateFunction, + readonly _alias: string, + ) {} + + /** + * @beta + * @internal + * @private + * Indicates if this expression was created from a literal value passed + * by the caller. + */ + _createdFromLiteral = false; + + /** + * @beta + * @private + * @internal + */ + _validateUserData(ignoreUndefinedProperties: boolean): void { + this._aggregate._validateUserData(ignoreUndefinedProperties); + } +} + +/** + * @beta + * Represents an expression that has been assigned an alias using the `.as()` method. + * + * This class wraps an existing {@link Expression} and associates it with a user-defined alias, + * allowing the expression's result to be referred to by name in the output + * of a Firestore pipeline query. + */ +export class AliasedExpression + implements firestore.Pipelines.Selectable, HasUserData +{ + expressionType: firestore.Pipelines.ExpressionType = 'AliasedExpression'; + selectable = true as const; + + /** + * @beta + * @internal + * @private + * Indicates if this expression was created from a literal value passed + * by the caller. + */ + _createdFromLiteral = false; + + constructor( + readonly _expr: Expression, + readonly _alias: string, + ) {} + + /** + * @beta + * @private + * @internal + */ + _validateUserData(ignoreUndefinedProperties: boolean): void { + this._expr._validateUserData(ignoreUndefinedProperties); + } +} + +/** + * @beta + * @internal + */ +class ListOfExprs extends Expression { + expressionType: firestore.Pipelines.ExpressionType = 'ListOfExprs'; + + constructor(private exprs: Expression[]) { + super(); + } + + /** + * @beta + * @private + * @internal + */ + _toProto(serializer: Serializer): api.IValue { + return { + arrayValue: { + values: this.exprs.map(p => p._toProto(serializer)!), + }, + }; + } + + /** + * @beta + * @private + * @internal + */ + _validateUserData(ignoreUndefinedProperties: boolean): void { + this.exprs.forEach(expr => { + return expr._validateUserData(ignoreUndefinedProperties); + }); + } +} + +/** + * @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 firestore.Pipelines.Selectable +{ + readonly expressionType: firestore.Pipelines.ExpressionType = 'Field'; + selectable = true as const; + + /** + * @beta + * @internal + * @private + * @hideconstructor + * @param fieldPath + */ + constructor(private fieldPath: FieldPath) { + super(); + } + + get fieldName(): string { + return this.fieldPath.formattedName; + } + + get _alias(): string { + return this.fieldName; + } + + get _expr(): Expression { + return this; + } + + /** + * @beta + * @private + * @internal + */ + _toProto(_: Serializer): api.IValue { + return { + fieldReferenceValue: this.fieldPath.formattedName, + }; + } + + /** + * @beta + * @private + * @internal + */ + _validateUserData(_: boolean): 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 field The path to the field. + * @return A new {@code Field} instance representing the specified field. + */ +export function field(field: string | firestore.FieldPath): Field { + if (typeof field === 'string') { + if ('__name__' === field) { + return new Field(FieldPath.documentId()); + } + return new Field(FieldPath.fromArgument(field)); + } else { + return new Field(field as unknown as FieldPath); + } +} + +/** + * @beta + * @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: firestore.Pipelines.ExpressionType = 'Constant'; + + private protoValue?: api.IValue; + + /** + * @beta + * @private + * @internal + * @hideconstructor + * @param value The value of the constant. + */ + constructor(private value: unknown) { + super(); + } + + /** + * @beta + * @private + * @internal + */ + static _fromProto(value: api.IValue): Constant { + const result = new Constant(value); + result.protoValue = value; + return result; + } + + /** + * @beta + * @private + * @internal + */ + _toProto(serializer: Serializer): api.IValue { + if (isFirestoreValue(this.value)) { + return this.value; + } + + return serializer.encodeValue(this.value)!; + } + + /** + * @beta + * @private + * @internal + */ + _validateUserData(ignoreUndefinedProperties: boolean): void { + validateUserInput('value', this.value, 'constant value', { + allowUndefined: ignoreUndefinedProperties, + allowDeletes: 'none', + allowTransforms: false, + }); + } +} + +/** + * @beta + * Creates an 'Expression' instance for a number value. + * + * @param value The number value. + * @return A new `Expression` instance. + */ +export function constant(value: number): Expression; + +/** + * @beta + * Creates an 'Expression' instance for a string value. + * + * @param value The string value. + * @return A new `Expression` instance. + */ +export function constant(value: string): Expression; + +/** + * @beta + * Creates an 'Expression' instance for a boolean value. + * + * @param value The boolean value. + * @return A new `Expression` instance. + */ +export function constant(value: boolean): BooleanExpression; + +/** + * @beta + * Creates an 'Expression' instance for a null value. + * + * @param value The null value. + * @return A new `Expression` instance. + */ +export function constant(value: null): Expression; + +/** + * @beta + * Creates an 'Expression' instance for a GeoPoint value. + * + * @param value The GeoPoint value. + * @return A new `Expression` instance. + */ +export function constant(value: firestore.GeoPoint): Expression; + +/** + * @beta + * Creates an 'Expression' instance for a Timestamp value. + * + * @param value The Timestamp value. + * @return A new `Expression` instance. + */ +export function constant(value: firestore.Timestamp): Expression; + +/** + * @beta + * Creates an 'Expression' instance for a Date value. + * + * @param value The Date value. + * @return A new `Expression` instance. + */ +export function constant(value: Date): Expression; + +/** + * @beta + * Creates an 'Expression' instance for a Buffer | Uint8Array value. + * + * @param value The Buffer | Uint8Array value. + * @return A new `Expression` instance. + */ +export function constant(value: Buffer | Uint8Array): Expression; + +/** + * @beta + * Creates an 'Expression' instance for a DocumentReference value. + * + * @param value The DocumentReference value. + * @return A new `Expression` instance. + */ +export function constant(value: firestore.DocumentReference): Expression; + +/** + * @beta + * Creates an 'Expression' instance for a Firestore proto value. + * For internal use only. + * @private + * @internal + * @param value The Firestore proto value. + * @return A new `Expression` instance. + */ +export function constant(value: api.IValue): Expression; + +/** + * @beta + * Creates an 'Expression' instance for a VectorValue value. + * + * @param value The VectorValue value. + * @return A new `Expression` instance. + */ +export function constant(value: firestore.VectorValue): Expression; + +/** + * @beta + * @internal + * @private + * @param value + */ +export function constant(value: unknown): Expression; + +export function constant(value: unknown): Expression { + return _constant(value); +} + +export function _constant(value: unknown): Constant | BooleanExpression { + const c = new Constant(value); + if (typeof value === 'boolean') { + return new BooleanConstant(c); + } else { + return c; + } +} + +/** + * @beta + * Internal only + * @internal + * @private + */ +export class MapValue extends Expression { + constructor(private plainObject: Map) { + super(); + } + + expressionType: firestore.Pipelines.ExpressionType = 'Constant'; + + _toProto(serializer: Serializer): api.IValue { + return serializer.encodeValue(this.plainObject); + } + + /** + * @beta + * @private + * @internal + */ + _validateUserData(ignoreUndefinedProperties: boolean): void { + this.plainObject.forEach(expr => { + return expr._validateUserData(ignoreUndefinedProperties); + }); + } +} + +/** + * @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: firestore.Pipelines.ExpressionType = 'Function'; + + constructor( + public _methodName: string, + private params: Expression[], + ) { + super(); + } + + /** + * @beta + * @private + * @internal + */ + _toProto(serializer: Serializer): api.IValue { + return { + functionValue: { + name: this._methodName, + args: this.params.map(p => cast(p)._toProto(serializer)), + }, + }; + } + + /** + * @beta + * @private + * @internal + */ + _validateUserData(ignoreUndefinedProperties: boolean): void { + this.params.forEach(expr => { + return expr._validateUserData(ignoreUndefinedProperties); + }); + } +} + +/** + * @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. + */ +class MapFunctionExpr extends FunctionExpression { + readonly expressionType: firestore.Pipelines.ExpressionType = 'Function'; + + constructor(private map: Record) { + super('map', []); + } + + /** + * @beta + * @private + * @internal + */ + _toProto(serializer: Serializer): api.IValue { + const args: api.IValue[] = []; + for (const key in this.map) { + if (Object.prototype.hasOwnProperty.call(this.map, key)) { + if (this.map[key]) { + args.push(constant(key)._toProto(serializer)); + args.push(this.map[key]!._toProto(serializer)); + } + } + } + return { + functionValue: { + name: this._methodName, + args: args, + }, + }; + } + + /** + * @beta + * @private + * @internal + */ + _validateUserData(ignoreUndefinedProperties: boolean): void { + validateUserInput('value', this.map, 'map value', { + allowUndefined: ignoreUndefinedProperties, + allowTransforms: false, + allowDeletes: 'none', + }); + } +} + +/** + * @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. + */ +class ArrayFunctionExpr extends FunctionExpression { + readonly expressionType: firestore.Pipelines.ExpressionType = 'Function'; + + constructor(private values: Array) { + super('array', []); + } + + /** + * @beta + * @private + * @internal + */ + _toProto(serializer: Serializer): api.IValue { + return { + functionValue: { + name: this._methodName, + args: this.values + .filter(v => !!v) + .map(value => value!._toProto(serializer)), + }, + }; + } + + /** + * @beta + * @private + * @internal + */ + _validateUserData(ignoreUndefinedProperties: boolean): void { + validateUserInput('value', this.values, 'array value', { + allowUndefined: ignoreUndefinedProperties, + allowTransforms: false, + allowDeletes: 'none', + }); + } +} + +/** + * @beta + * An expression that evaluates to a boolean value. + * + * This expression type is useful for filter conditions. + * + */ +export abstract class BooleanExpression + extends Expression + implements firestore.Pipelines.BooleanExpression +{ + abstract get _expr(): Expression; + + /** + * @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 new AggregateFunction('count_if', [this]); + } + + /** + * @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 Expression} representing the negated filter condition. + */ + not(): BooleanExpression { + return new FunctionExpression('not', [this]).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]); + } + + /** + * @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, + ]); + + return normalizedCatchValue instanceof BooleanExpression + ? expr.asBoolean() + : expr; + } + + _toProto(serializer: Serializer): api.IValue { + return this._expr._toProto(serializer); + } + + _validateUserData(ignoreUndefinedProperties: boolean): void { + this._expr._validateUserData(ignoreUndefinedProperties); + } +} + +export class BooleanFunctionExpression extends BooleanExpression { + readonly expressionType: firestore.Pipelines.ExpressionType = 'Function'; + constructor(readonly _expr: FunctionExpression) { + super(); + } +} + +export class BooleanConstant extends BooleanExpression { + readonly expressionType: firestore.Pipelines.ExpressionType = 'Constant'; + constructor(readonly _expr: Constant) { + super(); + } +} + +export class BooleanField extends BooleanExpression { + readonly expressionType: firestore.Pipelines.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 new AggregateFunction('count_if', [cast(booleanExpr)]); +} + +/** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the index exceeds the array length, an error is + * returned. A negative index, 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 index The index of the element to return. + * @return A new Expression representing the 'arrayGet' operation. + */ +export function arrayGet(arrayField: string, index: number): FunctionExpression; + +/** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the index exceeds the array length, an error is + * returned. A negative index, 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 indexExpr An Expression evaluating to the index of the element to return. + * @return A new Expression representing the 'arrayGet' operation. + */ +export function arrayGet( + arrayField: string, + indexExpr: Expression, +): FunctionExpression; + +/** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the index exceeds the array length, an error is + * returned. A negative index, starts from the end. + * + * ```typescript + * // Return the value in the tags field array at index 1. + * arrayGet(field('tags'), 1); + * ``` + * + * @param arrayExpression An Expression evaluating to an array. + * @param index The index of the element to return. + * @return A new Expression representing the 'arrayGet' operation. + */ +export function arrayGet( + arrayExpression: Expression, + index: number, +): FunctionExpression; + +/** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the index exceeds the array length, an error is + * returned. A negative index, 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 Expression evaluating to an array. + * @param indexExpr An Expression evaluating to the index of the element to return. + * @return A new Expression representing the 'arrayGet' operation. + */ +export function arrayGet( + arrayExpression: Expression, + indexExpr: Expression, +): FunctionExpression; +export function arrayGet( + array: Expression | string, + index: Expression | number, +): FunctionExpression { + return fieldOrExpression(array).arrayGet(valueToDefaultExpr(index)); +} + +/** + * @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 Expression} representing the 'isError' check. + */ +export function isError(value: Expression): BooleanExpression { + const expr: Expression = cast(value); + return expr.isError(); +} + +/** + * @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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 | firestore.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 Expression} representing the documentId operation. + */ +export function documentId(documentPathExpr: Expression): FunctionExpression; +export function documentId( + documentPath: Expression | string | firestore.DocumentReference, +): FunctionExpression { + 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 the result of 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. + * @return A new {@code Expression} representing the addition operation. + */ +export function add( + first: Expression, + second: Expression | unknown, +): FunctionExpression; + +/** + * @beta + * Creates an expression that adds a field's value to the result of 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. + * @return A new {@code Expression} 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 minuend The expression to subtract from. + * @param subtrahend The expression to subtract. + * @return A new {@code Expression} representing the subtraction operation. + */ +export function subtract( + minuend: Expression, + subtrahend: 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 minuend The expression to subtract from. + * @param subtrahend The constant value to subtract. + * @return A new {@code Expression} representing the subtraction operation. + */ +export function subtract( + minuend: Expression, + subtrahend: 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 minuendFieldName The field name to subtract from. + * @param subtrahend The expression to subtract. + * @return A new {@code Expression} representing the subtraction operation. + */ +export function subtract( + minuendFieldName: string, + subtrahend: 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 minuendFieldName The field name to subtract from. + * @param subtrahend The constant value to subtract. + * @return A new {@code Expression} representing the subtraction operation. + */ +export function subtract( + minuendFieldName: string, + subtrahend: unknown, +): FunctionExpression; +export function subtract( + left: Expression | string, + right: Expression | unknown, +): FunctionExpression { + const normalizedLeft = fieldOrExpression(left); + const normalizedRight = valueToDefaultExpr(right); + return normalizedLeft.subtract(normalizedRight); +} + +/** + * @beta + * Creates an expression that multiplies the result of 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. + * @return A new {@code Expression} representing the multiplication operation. + */ +export function multiply( + first: Expression, + second: Expression | unknown, +): FunctionExpression; + +/** + * @beta + * Creates an expression that multiplies a field's value by the result of 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 multiply. + * @param second The second expression or literal to multiply. + * @return A new {@code Expression} 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 dividend The expression to be divided. + * @param divisort The expression to divide by. + * @return A new {@code Expression} representing the division operation. + */ +export function divide( + dividend: Expression, + divisort: 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 dividend The expression to be divided. + * @param divisor The constant value to divide by. + * @return A new {@code Expression} representing the division operation. + */ +export function divide( + dividend: Expression, + divisor: 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 dividend The field name to be divided. + * @param divisor The expression to divide by. + * @return A new {@code Expression} representing the division operation. + */ +export function divide( + dividend: string, + divisor: 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 dividend The field name to be divided. + * @param divisor The constant value to divide by. + * @return A new {@code Expression} representing the division operation. + */ +export function divide(dividend: string, divisor: unknown): FunctionExpression; +export function divide( + dividend: Expression | string, + divisor: Expression | unknown, +): FunctionExpression { + const normalizedLeft = fieldOrExpression(dividend); + const normalizedRight = valueToDefaultExpr(divisor); + 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 Expression} 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 Expression} 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 Expression} 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 Expression} representing the modulo operation. + */ +export function mod(fieldName: string, value: unknown): FunctionExpression; +export function mod( + left: Expression | string, + right: Expression | unknown, +): FunctionExpression { + const normalizedLeft = fieldOrExpression(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('baz')}).as('data'); + * ``` + * + * @param elements The input map to evaluate in the expression. + * @return A new {@code Expression} representing the map function. + */ +export function map(elements: Record): FunctionExpression { + const result: Record = {}; + + for (const key in elements) { + if (Object.prototype.hasOwnProperty.call(elements, key)) { + result[key] = + elements[key] !== undefined + ? valueToDefaultExpr(elements[key]) + : undefined; + } + } + return new MapFunctionExpr(result); +} + +/** + * @beta + * 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); +} + +/** + * @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('baz')]).as('foo'); + * ``` + * + * @param elements The input array to evaluate in the expression. + * @return A new {@code Expression} representing the array function. + */ +export function array(elements: unknown[]): FunctionExpression { + return new ArrayFunctionExpr( + elements.map(element => { + return element !== undefined ? valueToDefaultExpr(element) : undefined; + }), + ); +} + +/** + * @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 `Expression` 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 `Expression` 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 `Expression` 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 `Expression` representing the equality comparison. + */ +export function equal(fieldName: string, value: unknown): BooleanExpression; +export function equal( + left: Expression | string, + right: unknown, +): BooleanExpression { + const leftExpr = fieldOrExpression(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 `Expression` 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 `Expression` 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 `Expression` 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 `Expression` representing the inequality comparison. + */ +export function notEqual(fieldName: string, value: unknown): BooleanExpression; +export function notEqual( + left: Expression | string, + right: unknown, +): BooleanExpression { + const leftExpr = fieldOrExpression(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 `Expression` 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 `Expression` 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 `Expression` 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 `Expression` representing the less than comparison. + */ +export function lessThan(fieldName: string, value: unknown): BooleanExpression; +export function lessThan( + left: Expression | string, + right: unknown, +): BooleanExpression { + const leftExpr = fieldOrExpression(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 + * lessThanOrEqual(field("quantity"), field("limit")); + * ``` + * + * @param left The first expression to compare. + * @param right The second expression to compare. + * @return A new `Expression` 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 + * lessThanOrEqual(field("quantity"), 20); + * ``` + * + * @param expression The expression to compare. + * @param value The constant value to compare to. + * @return A new `Expression` 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 + * lessThanOrEqual("quantity", field("limit")); + * ``` + * + * @param fieldName The field name to compare. + * @param expression The expression to compare to. + * @return A new `Expression` 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 + * lessThanOrEqual("score", 70); + * ``` + * + * @param fieldName The field name to compare. + * @param value The constant value to compare to. + * @return A new `Expression` 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 = fieldOrExpression(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 `Expression` 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 `Expression` 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 `Expression` 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 `Expression` representing the greater than comparison. + */ +export function greaterThan( + fieldName: string, + value: unknown, +): BooleanExpression; +export function greaterThan( + left: Expression | string, + right: unknown, +): BooleanExpression { + const leftExpr = fieldOrExpression(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 `Expression` 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 `Expression` 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 `Expression` 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 `Expression` 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 = fieldOrExpression(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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} representing the 'array_contains_any' comparison. + */ +export function arrayContainsAny( + fieldName: string, + values: Expression, +): BooleanExpression; +export function arrayContainsAny( + array: Expression | string, + values: unknown[] | Expression, +): BooleanExpression { + if (Array.isArray(values)) { + return fieldOrExpression(array).arrayContainsAny(values); + } else { + 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 Expression} 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 Expression} 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 Expression} 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 Expression} representing the 'array_contains_all' comparison. + */ +export function arrayContainsAll( + fieldName: string, + arrayExpression: Expression, +): BooleanExpression; +export function arrayContainsAll( + array: Expression | string, + values: unknown[] | Expression, +): BooleanExpression { + if (Array.isArray(values)) { + return fieldOrExpression(array).arrayContainsAll(values); + } else { + 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} representing the 'IN' comparison. + */ +export function equalAny( + fieldName: string, + arrayExpression: Expression, +): BooleanExpression; +export function equalAny( + element: Expression | string, + values: unknown[] | Expression, +): BooleanExpression { + if (Array.isArray(values)) { + return fieldOrExpression(element).equalAny(values); + } else { + 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 Expression} 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 Expression} 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 Expression} 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 Expression} representing the 'NOT IN' comparison. + */ +export function notEqualAny( + fieldName: string, + arrayExpression: Expression, +): BooleanExpression; + +export function notEqualAny( + element: Expression | string, + values: unknown[] | Expression, +): BooleanExpression { + if (Array.isArray(values)) { + return fieldOrExpression(element).notEqualAny(values); + } else { + return fieldOrExpression(element).notEqualAny(values); + } +} + +/** + * @beta + * Creates an expression that performs a logical 'XOR' (exclusive OR) operation on multiple BooleanExprs. + * + * ```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 Expression} representing the logical 'XOR' operation. + */ +export function xor( + first: BooleanExpression, + second: BooleanExpression, + ...additionalConditions: BooleanExpression[] +): BooleanExpression { + return new FunctionExpression('xor', [ + first, + second, + ...additionalConditions, + ]).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 Expression} representing the conditional expression. + */ +export function conditional( + condition: BooleanExpression, + thenExpr: Expression, + elseExpr: Expression, +): FunctionExpression { + return new FunctionExpression('conditional', [ + condition, + cast(thenExpr), + cast(elseExpr), + ]); +} + +/** + * @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 Expression} 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 Expression} representing the logical max 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 Expression} representing the logical max 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 Expression} representing the logical min 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 Expression} representing the logical min 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 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 Expression} 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 Expression} 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 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 Expression} 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 Expression} 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 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 new AggregateFunction('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 'min' 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 'min' 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 'max' 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 'max' 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 Expression} 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 Expression) to compare against. + * @return A new {@code Expression} 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 Expression) to compare against. + * @param vector The other vector (as an array of doubles or VectorValue) to compare against. + * @return A new {@code Expression} 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 Expression) to compare against. + * @param otherVectorExpression The other vector (represented as an Expression) to compare against. + * @return A new {@code Expression} 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[] | firestore.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 Expression} 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 Expression) to calculate with. + * @return A new {@code Expression} 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 Expression) to calculate with. + * @param vector The other vector (as an array of doubles or VectorValue) to calculate with. + * @return A new {@code Expression} 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 Expression) to calculate with. + * @param otherVectorExpression The other vector (represented as an Expression) to calculate with. + * @return A new {@code Expression} 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 Expression} 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 Expression) to compare against. + * @return A new {@code Expression} 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 Expression) to compare against. + * @param vector The other vector (as an array of doubles or VectorValue) to compare against. + * @return A new {@code Expression} 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 Expression) to compare against. + * @param otherVectorExpression The other vector (represented as an Expression) to compare against. + * @return A new {@code Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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 Expression} 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', []); +} + +/** + * @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 Expression} representing the logical 'AND' operation. + */ +export function and( + first: BooleanExpression, + second: BooleanExpression, + ...more: BooleanExpression[] +): BooleanExpression { + return new FunctionExpression('and', [first, second, ...more]).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 Expression} representing the logical 'OR' operation. + */ +export function or( + first: BooleanExpression, + second: BooleanExpression, + ...more: BooleanExpression[] +): BooleanExpression { + return new FunctionExpression('or', [first, second, ...more]).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 `Expression` 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 `Expression` 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 `Expression` 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 `Expression` 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 `Expression` 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 `Expression` 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 Expression} 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 Expression} 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 `Expression` 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 `Expression` 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 `Expression` 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 `Expression` 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 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 Expression} 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 Expression} 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. + * stringReverse(field("myString")); + * ``` + * + * @param stringExpression An expression evaluating to a string value, which will be reversed. + * @return A new {@code Expression} 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. + * stringReverse("myString"); + * ``` + * + * @param field The name of the field representing the string to reverse. + * @return A new {@code Expression} 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 fieldOrExpression(fieldNameOrExpression).concat( + 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), + ); +} + +/** + * 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: firestore.Pipelines.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 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; + +/** + * 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: firestore.Pipelines.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 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: firestore.Pipelines.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 a {@link Field} instance representing the field at the given path. + * + * ```typescript + * // Sort documents by the 'name' field in lowercase in ascending order + * db.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 + * db.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'); +} + +/** + * @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 + * db.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 + * db.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'); +} + +/** + * @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 HasUserData { + constructor( + readonly expr: Expression, + readonly direction: 'ascending' | 'descending', + ) {} + + /** + * @beta + * @internal + * @private + * Indicates if this expression was created from a literal value passed + * by the caller. + */ + _createdFromLiteral = false; + + /** + * @beta + * @private + * @internal + */ + _toProto(serializer: Serializer): api.IValue { + const expr = this.expr as Expression; + return { + mapValue: { + fields: { + direction: serializer.encodeValue(this.direction), + expression: expr._toProto(serializer), + }, + }, + }; + } + + _protoValueType: 'ProtoValue' = 'ProtoValue' as const; + + /** + * @beta + * @private + * @internal + */ + _validateUserData(ignoreUndefinedProperties: boolean): void { + (this.expr as Expression)._validateUserData(ignoreUndefinedProperties); + } +} diff --git a/dev/src/pipelines/index.ts b/dev/src/pipelines/index.ts new file mode 100644 index 000000000..d3cd46b8f --- /dev/null +++ b/dev/src/pipelines/index.ts @@ -0,0 +1,128 @@ +// 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. + +export { + Pipeline, + PipelineResult, + PipelineSnapshot, + PipelineSource, +} from './pipelines'; + +export { + and, + arrayContains, + arrayContainsAny, + arrayReverse, + average, + equal, + ceil, + exp, + floor, + greaterThan, + like, + lessThan, + notEqual, + ascending, + not, + or, + regexContains, + regexMatch, + startsWith, + stringConcat, + subtract, + cosineDistance, + countDistinct, + dotProduct, + euclideanDistance, + mapGet, + lessThanOrEqual, + equalAny, + map, + array, + field, + xor, + AggregateFunction, + arrayGet, + add, + BooleanExpression, + Expression, + FunctionExpression, + minimum, + count, + countIf, + arrayLength, + stringContains, + charLength, + divide, + mod, + reverse, + trim, + toUpper, + toLower, + vectorLength, + exists, + isAbsent, + ifError, + isError, + substring, + documentId, + arrayContainsAll, + constant, + Field, + Constant, + sum, + maximum, + descending, + greaterThanOrEqual, + multiply, + conditional, + Ordering, + AliasedAggregate, + endsWith, + AliasedExpression, + mapMerge, + mapRemove, + byteLength, + logicalMaximum, + logicalMinimum, + notEqualAny, + countAll, + timestampAdd, + timestampSubtract, + timestampToUnixMicros, + timestampToUnixSeconds, + unixMicrosToTimestamp, + timestampToUnixMillis, + unixSecondsToTimestamp, + unixMillisToTimestamp, + pow, + collectionId, + length, + ln, + round, + sqrt, + stringReverse, + abs, + arraySum, + ifAbsent, + log10, + concat, + join, + currentTimestamp, + arrayConcat, + type, + timestampTruncate, + split, + // TODO(new-expression): Add new expression exports above this line +} from './expression'; diff --git a/dev/src/pipelines/options-util.ts b/dev/src/pipelines/options-util.ts new file mode 100644 index 000000000..bf9cc7e78 --- /dev/null +++ b/dev/src/pipelines/options-util.ts @@ -0,0 +1,89 @@ +// 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 {ObjectValue, Serializer} from '../serializer'; +import {ObjectValueFieldPath} from '../path'; +import {ApiMapValue} from '../types'; +import {isPlainObject, mapToArray} from '../util'; +import {google} from '../../protos/firestore_v1_proto_api'; +import IValue = google.firestore.v1.IValue; +export type OptionsDefinitions = Record; +export type OptionDefinition = { + serverName: string; + nestedOptions?: OptionsDefinitions; +}; + +export class OptionsUtil { + constructor(private optionDefinitions: OptionsDefinitions) {} + + private _getKnownOptions( + options: Record, + serializer: Serializer, + ): ObjectValue { + const knownOptions: ObjectValue = ObjectValue.empty(); + + // SERIALIZE KNOWN OPTIONS + for (const knownOptionKey in this.optionDefinitions) { + const optionDefinition: OptionDefinition = + this.optionDefinitions[knownOptionKey]; + + if (knownOptionKey in options) { + const optionValue: unknown = options[knownOptionKey]; + let protoValue: IValue | undefined = undefined; + + if (optionDefinition.nestedOptions && isPlainObject(optionValue)) { + const nestedUtil = new OptionsUtil(optionDefinition.nestedOptions); + protoValue = { + mapValue: { + fields: nestedUtil.getOptionsProto(serializer, optionValue), + }, + }; + } else if (optionValue) { + protoValue = serializer.encodeValue(optionValue) ?? undefined; + } + + if (protoValue) { + knownOptions.set( + new ObjectValueFieldPath(optionDefinition.serverName), + protoValue, + ); + } + } + } + + return knownOptions; + } + + getOptionsProto( + serializer: Serializer, + knownOptions: Record, + optionsOverride?: Record, + ): ApiMapValue | undefined { + const result: ObjectValue = this._getKnownOptions(knownOptions, serializer); + + // APPLY OPTIONS OVERRIDES + if (optionsOverride) { + const optionsMap = new Map( + mapToArray(optionsOverride, (value, key) => [ + ObjectValueFieldPath.fromDotNotation(key), + value !== undefined ? serializer.encodeValue(value) : null, + ]), + ); + result.setAll(optionsMap); + } + + // Return IMapValue from `result` + return result.value.mapValue.fields ?? {}; + } +} diff --git a/dev/src/pipelines/pipeline-util.ts b/dev/src/pipelines/pipeline-util.ts new file mode 100644 index 000000000..f49cbcb32 --- /dev/null +++ b/dev/src/pipelines/pipeline-util.ts @@ -0,0 +1,765 @@ +// 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 * as firestore from '@google-cloud/firestore'; +import {GoogleError} from 'google-gax'; +import {Duplex, Transform} from 'stream'; +import {google} from '../../protos/firestore_v1_proto_api'; + +import * as protos from '../../protos/firestore_v1_proto_api'; +import './expression'; +import Firestore, { + CollectionReference, + DocumentReference, + FieldValue, + Timestamp, + VectorValue, +} from '../index'; +import {logger} from '../logger'; +import {FieldPath, QualifiedResourcePath} from '../path'; +import {CompositeFilterInternal} from '../reference/composite-filter-internal'; +import {NOOP_MESSAGE} from '../reference/constants'; +import {FieldFilterInternal} from '../reference/field-filter-internal'; +import {FilterInternal} from '../reference/filter-internal'; +import { + PipelineResponse, + PipelineStreamElement, + QueryCursor, +} from '../reference/types'; +import {Serializer} from '../serializer'; +import { + Deferred, + getTotalTimeout, + isObject, + isPermanentRpcError, + isPlainObject, + requestTag, + wrapError, +} from '../util'; +import api = protos.google.firestore.v1; + +import { + Expression, + BooleanExpression, + and, + or, + field as createField, + constant, + map, + array, + Constant, + field, + Ordering, + greaterThan, + lessThan, + Field, + AggregateFunction, +} from './expression'; +import {Pipeline, PipelineResult, ExplainStats} from './pipelines'; +import {StructuredPipeline} from './structured-pipeline'; +import Selectable = FirebaseFirestore.Pipelines.Selectable; + +/** + * Returns a builder for DocumentSnapshot and QueryDocumentSnapshot instances. + * Invoke `.build()' to assemble the final snapshot. + * + * @private + * @internal + */ +export class ExecutionUtil { + constructor( + /** @private */ + readonly _firestore: Firestore, + /** @private */ + readonly _serializer: Serializer, + ) {} + + _getResponse( + structuredPipeline: StructuredPipeline, + transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, + ): Promise { + // Capture the error stack to preserve stack tracing across async calls. + const stack = Error().stack!; + + return new Promise((resolve, reject): void => { + const result: Array = []; + const output: PipelineResponse = {}; + + const stream: NodeJS.EventEmitter = this._stream( + structuredPipeline, + transactionOrReadTime, + ); + stream.on('error', err => { + reject(wrapError(err, stack)); + }); + stream.on('data', (data: PipelineStreamElement[]) => { + for (const element of data) { + if (element.transaction) { + output.transaction = element.transaction; + } + if (element.executionTime) { + output.executionTime = element.executionTime; + } + if (element.explainStats) { + output.explainStats = element.explainStats; + } + if (element.result) { + result.push(element.result); + } + } + }); + stream.on('end', () => { + output.result = result; + resolve(output); + }); + }); + } + + // This method exists solely to enable unit tests to mock it. + _isPermanentRpcError(err: GoogleError, methodName: string): boolean { + return isPermanentRpcError(err, methodName); + } + + _hasRetryTimedOut(methodName: string, startTime: number): boolean { + const totalTimeout = getTotalTimeout(methodName); + if (totalTimeout === 0) { + return false; + } + + return Date.now() - startTime >= totalTimeout; + } + + stream( + structuredPipeline: StructuredPipeline, + transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, + ): NodeJS.ReadableStream { + const responseStream = this._stream( + structuredPipeline, + transactionOrReadTime, + ); + const transform = new Transform({ + objectMode: true, + transform(chunk: Array, encoding, callback) { + chunk.forEach(item => { + if (item.result) { + this.push(item.result); + } + }); + callback(); + }, + }); + + responseStream.pipe(transform); + responseStream.on('error', e => transform.destroy(e)); + return transform; + } + + _stream( + structuredPipeline: StructuredPipeline, + transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, + ): NodeJS.ReadableStream { + const tag = requestTag(); + + let backendStream: Duplex; + const stream = new Transform({ + objectMode: true, + transform: ( + proto: api.ExecutePipelineResponse | typeof NOOP_MESSAGE, + enc, + callback, + ) => { + if (proto === NOOP_MESSAGE) { + callback(undefined); + return; + } + + if (proto.results && proto.results.length === 0) { + const output: PipelineStreamElement = {}; + if (proto.transaction?.length) { + output.transaction = proto.transaction; + } + if (proto.executionTime) { + output.executionTime = Timestamp.fromProto(proto.executionTime); + } + callback(undefined, [output]); + } else { + let output: PipelineStreamElement[] = proto.results.map(result => { + const output: PipelineStreamElement = {}; + if (proto.transaction?.length) { + output.transaction = proto.transaction; + } + if (proto.executionTime) { + output.executionTime = Timestamp.fromProto(proto.executionTime); + } + + const ref = result.name + ? new DocumentReference( + this._firestore, + QualifiedResourcePath.fromSlashSeparatedString(result.name), + ) + : undefined; + + if (!result.fields) { + logger( + '_stream', + null, + 'Unexpected state: `result.fields` was falsey. Using an empty map.', + ); + } + + output.result = new PipelineResult( + this._serializer, + result.fields || {}, + ref, + Timestamp.fromProto(proto.executionTime!), + result.createTime + ? Timestamp.fromProto(result.createTime!) + : undefined, + result.updateTime + ? Timestamp.fromProto(result.updateTime!) + : undefined, + ); + return output; + }); + if (proto.explainStats?.data?.value) { + const explainStats = new ExplainStats(proto.explainStats.data); + + output = [ + ...output, + { + explainStats, + } as PipelineStreamElement, + ]; + } + callback(undefined, output); + } + }, + }); + + Promise.all([ + this._firestore.initializeIfNeeded(tag), + ExplainStats._ensureMessageTypesLoaded(), + ]) + .then(async () => { + // `toProto()` might throw an exception. We rely on the behavior of an + // async function to convert this exception into the rejected Promise we + // catch below. + const request: api.IExecutePipelineRequest = { + database: this._firestore.formattedName, + structuredPipeline: structuredPipeline._toProto(this._serializer), + }; + + if (transactionOrReadTime instanceof Uint8Array) { + request.transaction = transactionOrReadTime; + } else if (transactionOrReadTime instanceof Timestamp) { + request.readTime = transactionOrReadTime.toProto().timestampValue; + } else if (transactionOrReadTime) { + request.newTransaction = transactionOrReadTime; + } + + let streamActive: Deferred; + do { + streamActive = new Deferred(); + const methodName = 'executePipeline'; + backendStream = await this._firestore.requestStream( + methodName, + /* bidirectional= */ false, + request, + tag, + ); + backendStream.on('error', err => { + backendStream.unpipe(stream); + + logger( + 'PipelineUtil._stream', + tag, + 'Pipeline failed with stream error:', + err, + ); + stream.destroy(err); + streamActive.resolve(/* active= */ false); + }); + backendStream.on('end', () => { + streamActive.resolve(/* active= */ false); + }); + backendStream.resume(); + backendStream.pipe(stream); + } while (await streamActive.promise); + }) + .catch(e => { + logger( + 'PipelineUtil._stream', + tag, + 'Pipeline failed with stream error:', + e, + ); + stream.destroy(e); + }); + + return stream; + } +} + +function isITimestamp(obj: unknown): obj is google.protobuf.ITimestamp { + 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: unknown): obj is google.type.ILatLng { + 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: unknown): obj is api.IArrayValue { + 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: unknown): obj is api.IMapValue { + if (typeof obj !== 'object' || obj === null) { + return false; // Must be a non-null object + } + if ('fields' in obj && (obj.fields === null || isObject(obj.fields))) { + return true; + } + + return false; +} +function isIFunction(obj: unknown): obj is api.IFunction { + 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: unknown): obj is api.IPipeline { + 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: unknown): obj is api.IValue { + 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; +} + +export function whereConditionsFromCursor( + cursor: QueryCursor, + orderings: Ordering[], + position: 'before' | 'after', +): BooleanExpression { + // The filterFunc is either greater than or less than + const filterFunc = position === 'before' ? lessThan : greaterThan; + const cursors = cursor.values.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 ( + (position === 'after' && cursor.before) || + (position === 'before' && !cursor.before) + ) { + // 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) as unknown as BooleanExpression, + ); + } + + // 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) lessThan|greaterThan the cursor value, + // b) or equal the cursor value and lessThan|greaterThan the cursor values for other fields + condition = or( + filterFunc(field, value), + and(field.equal(value) as unknown as BooleanExpression, condition), + ); + } + + return condition; +} + +export function reverseOrderings(orderings: Ordering[]): Ordering[] { + return orderings.map( + o => + new Ordering( + o.expr, + o.direction === 'ascending' ? 'descending' : 'ascending', + ), + ); +} + +export function toPipelineBooleanExpr( + f: FilterInternal, + serializer: Serializer, +): BooleanExpression { + if (f instanceof FieldFilterInternal) { + const field = createField(f.field); + + // Comparison filters + const value = isFirestoreValue(f.value) + ? f.value + : serializer.encodeValue(f.value); + switch (f.op) { + case 'LESS_THAN': + return and(field.exists(), field.lessThan(value)); + case 'LESS_THAN_OR_EQUAL': + return and(field.exists(), field.lessThanOrEqual(value)); + case 'GREATER_THAN': + return and(field.exists(), field.greaterThan(value)); + case 'GREATER_THAN_OR_EQUAL': + return and(field.exists(), field.greaterThanOrEqual(value)); + case 'EQUAL': + return and(field.exists(), field.equal(value)); + case 'NOT_EQUAL': + return and(field.exists(), field.notEqual(value)); + case 'ARRAY_CONTAINS': + return and(field.exists(), field.arrayContains(value)); + case 'IN': { + const values = value?.arrayValue?.values?.map(val => constant(val)); + return and(field.exists(), field.equalAny(values!)); + } + case 'ARRAY_CONTAINS_ANY': { + const values = value?.arrayValue?.values?.map(val => constant(val)); + return and(field.exists(), field.arrayContainsAny(values!)); + } + case 'NOT_IN': { + const values = value?.arrayValue?.values?.map(val => constant(val)); + // In Enterprise DB's NOT_IN will match a field that does not exist, + // therefore we do not want an existence filter for the NOT_IN conversion + // so the Query and Pipeline behavior are consistent in Enterprise. + return field.notEqualAny(values!); + } + } + } else if (f instanceof CompositeFilterInternal) { + switch (f._getOperator()) { + case 'AND': { + const conditions = f + .getFilters() + .map(f => toPipelineBooleanExpr(f, serializer)); + return and(conditions[0], conditions[1], ...conditions.slice(2)); + } + case 'OR': { + const conditions = f + .getFilters() + .map(f => toPipelineBooleanExpr(f, serializer)); + return or(conditions[0], conditions[1], ...conditions.slice(2)); + } + } + } + + throw new Error( + `Failed to convert filter to pipeline conditions: ${f.toProto()}`, + ); +} + +export function isString(val: unknown): val is string { + return typeof val === 'string'; +} + +export function isNumber(val: unknown): val is number { + return typeof val === 'number'; +} + +export function isSelectable( + val: unknown, +): val is firestore.Pipelines.Selectable { + const candidate = val as firestore.Pipelines.Selectable; + return ( + candidate.selectable && + isString(candidate._alias) && + isExpr(candidate._expr) + ); +} + +export function isOrdering(val: unknown): val is firestore.Pipelines.Ordering { + const candidate = val as firestore.Pipelines.Ordering; + return ( + isExpr(candidate.expr) && + (candidate.direction === 'ascending' || + candidate.direction === 'descending') + ); +} + +export function isAliasedAggregate( + val: unknown, +): val is firestore.Pipelines.AliasedAggregate { + const candidate = val as firestore.Pipelines.AliasedAggregate; + return ( + isString(candidate._alias) && + candidate._aggregate instanceof AggregateFunction + ); +} + +export function isExpr(val: unknown): val is firestore.Pipelines.Expression { + return val instanceof Expression; +} + +export function isBooleanExpr( + val: unknown, +): val is firestore.Pipelines.BooleanExpression { + return val instanceof BooleanExpression; +} + +export function isField(val: unknown): val is firestore.Pipelines.Field { + return val instanceof Field; +} + +export function isPipeline(val: unknown): val is firestore.Pipelines.Pipeline { + return val instanceof Pipeline; +} + +export function isCollectionReference( + val: unknown, +): val is firestore.CollectionReference { + return val instanceof CollectionReference; +} + +/** + * Converts a value to an Expression, 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); + } + + // TODO(pipeline) is this still used? + result._createdFromLiteral = true; + return result; +} + +/** + * Converts a value to an Expression, Returning either a Constant, MapFunction, + * ArrayFunction, or the input itself (if it's already an expression). + * + * @private + * @internal + * @param value + */ +export function vectorToExpr( + value: firestore.VectorValue | number[] | Expression, +): Expression { + if (value instanceof Expression) { + return value; + } else if (value instanceof VectorValue) { + const result = constant(value); + result._createdFromLiteral = true; + return result; + } else if (Array.isArray(value)) { + const result = constant(FieldValue.vector(value)); + result._createdFromLiteral = true; + return result; + } else { + throw new Error('Unsupported value: ' + typeof value); + } +} + +/** + * Converts a value to an Expression, 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); + result._createdFromLiteral = true; + return result; + } else { + return valueToDefaultExpr(value); + } +} + +export function toField(value: string | firestore.Pipelines.Field): Field { + if (isString(value)) { + const result = field(value); + result._createdFromLiteral = true; + return result; + } else { + return value as Field; + } +} + +/** + * Converts a value to a Selectable, returning either a + * Field, or the input itself (if it's already a Selectable). + * 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 fieldOrSelectable(value: string | Selectable): Selectable { + if (isString(value)) { + const result = field(value); + result._createdFromLiteral = true; + return result; + } else { + return value; + } +} + +export function selectablesToMap( + selectables: (firestore.Pipelines.Selectable | string)[], +): 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 = new Field(FieldPath.fromArgument(selectable)); + } else { + alias = selectable._alias; + expression = selectable._expr as unknown as Expression; + } + + if (result.get(alias) !== undefined) { + throw new Error(`Duplicate alias or field '${alias}'`); + } + + result.set(alias, expression); + } + return result; +} + +export function aliasedAggregateToMap( + aliasedAggregatees: firestore.Pipelines.AliasedAggregate[], +): Map { + return aliasedAggregatees.reduce( + ( + map: Map, + selectable: firestore.Pipelines.AliasedAggregate, + ) => { + if (map.get(selectable._alias) !== undefined) { + throw new Error(`Duplicate alias or field '${selectable._alias}'`); + } + + map.set(selectable._alias, selectable._aggregate as AggregateFunction); + return map; + }, + new Map() as Map, + ); +} diff --git a/dev/src/pipelines/pipelines.ts b/dev/src/pipelines/pipelines.ts new file mode 100644 index 000000000..c8fcb27cb --- /dev/null +++ b/dev/src/pipelines/pipelines.ts @@ -0,0 +1,2070 @@ +// 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 * as firestore from '@google-cloud/firestore'; +import * as deepEqual from 'fast-deep-equal'; +import {google} from '../../protos/firestore_v1_proto_api'; +import Firestore, { + CollectionReference, + FieldPath, + Query, + Timestamp, +} from '../index'; +import {validateFieldPath} from '../path'; +import { + ExecutionUtil, + aliasedAggregateToMap, + fieldOrExpression, + isAliasedAggregate, + isBooleanExpr, + isCollectionReference, + isExpr, + isField, + isNumber, + isOrdering, + isPipeline, + isSelectable, + isString, + selectablesToMap, + toField, + vectorToExpr, +} from './pipeline-util'; +import {DocumentReference} from '../reference/document-reference'; +import {PipelineResponse} from '../reference/types'; +import {HasUserData, hasUserData, Serializer} from '../serializer'; +import {ApiMapValue} from '../types'; +import * as protos from '../../protos/firestore_v1_proto_api'; +import api = protos.google.firestore.v1; +import IStage = google.firestore.v1.Pipeline.IStage; +import {isOptionalEqual, isPlainObject} from '../util'; + +import { + AggregateFunction, + AliasedAggregate, + Expression, + Field, + BooleanExpression, + Ordering, + constant, + _mapValue, + field, +} from './expression'; +import { + AddFields, + Aggregate, + CollectionSource, + CollectionGroupSource, + DatabaseSource, + DocumentsSource, + Where, + FindNearest, + RawStage, + Limit, + Offset, + Select, + Sort, + Stage, + Distinct, + RemoveFields, + ReplaceWith, + Sample, + Union, + Unnest, + InternalWhereStageOptions, + InternalOffsetStageOptions, + InternalLimitStageOptions, + InternalDistinctStageOptions, + InternalAggregateStageOptions, + InternalFindNearestStageOptions, + InternalReplaceWithStageOptions, + InternalSampleStageOptions, + InternalUnionStageOptions, + InternalUnnestStageOptions, + InternalSortStageOptions, + InternalDocumentsStageOptions, + InternalCollectionGroupStageOptions, + InternalCollectionStageOptions, +} from './stage'; +import {StructuredPipeline} from './structured-pipeline'; +import Selectable = FirebaseFirestore.Pipelines.Selectable; + +import { + load as loadProtos, + Root as ProtoRoot, + Type as MessageType, + ReflectionObject, +} from 'protobufjs'; + +/** + * @beta + * Represents the source of a Firestore {@link Pipeline}. + */ +export class PipelineSource implements firestore.Pipelines.PipelineSource { + constructor(private db: Firestore) {} + + /** + * @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 | firestore.CollectionReference): Pipeline; + /** + * @beta + * Returns all documents from the entire collection. The collection can be nested. + * @param options - Options defining how this CollectionStage is evaluated. + */ + collection(options: firestore.Pipelines.CollectionStageOptions): Pipeline; + collection( + collectionOrOptions: + | string + | firestore.CollectionReference + | firestore.Pipelines.CollectionStageOptions, + ): Pipeline { + const options = + isString(collectionOrOptions) || + isCollectionReference(collectionOrOptions) + ? {} + : collectionOrOptions; + + const collection = + isString(collectionOrOptions) || + isCollectionReference(collectionOrOptions) + ? collectionOrOptions + : collectionOrOptions.collection; + + // Validate that a user provided reference is for the same Firestore DB + if (isCollectionReference(collection)) { + this._validateReference(collection); + } + + const normalizedCollection = isString(collection) + ? this.db.collection(collection) + : (collection as CollectionReference); + + const internalOptions: InternalCollectionStageOptions = { + ...options, + collection: normalizedCollection, + }; + + return new Pipeline(this.db, [new CollectionSource(internalOptions)]); + } + + /** + * @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): Pipeline; + + /** + * @beta + * Returns all documents from a collection ID regardless of the parent. + * @param options - Options defining how this CollectionGroupStage is evaluated. + */ + collectionGroup( + options: firestore.Pipelines.CollectionGroupStageOptions, + ): Pipeline; + collectionGroup( + collectionIdOrOptions: + | string + | firestore.Pipelines.CollectionGroupStageOptions, + ): Pipeline { + const options: InternalCollectionGroupStageOptions = isString( + collectionIdOrOptions, + ) + ? {collectionId: collectionIdOrOptions} + : {...collectionIdOrOptions}; + return new Pipeline(this.db, [new CollectionGroupSource(options)]); + } + + /** + * @beta + * Returns all documents from the entire database. + */ + database(): Pipeline; + /** + * @beta + * Returns all documents from the entire database. + * @param options - Options defining how a DatabaseStage is evaluated. + */ + database(options: firestore.Pipelines.DatabaseStageOptions): Pipeline; + database(options?: firestore.Pipelines.DatabaseStageOptions): Pipeline { + return new Pipeline(this.db, [new DatabaseSource(options ?? {})]); + } + + /** + * @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): Pipeline; + + /** + * @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: firestore.Pipelines.DocumentsStageOptions): Pipeline; + documents( + docsOrOptions: + | Array + | firestore.Pipelines.DocumentsStageOptions, + ): Pipeline { + const options = Array.isArray(docsOrOptions) ? {} : docsOrOptions; + const docs = Array.isArray(docsOrOptions) + ? docsOrOptions + : docsOrOptions.docs; + + // Validate that all user provided references are for the same Firestore DB + docs + .filter(v => v instanceof DocumentReference) + .forEach(dr => + this._validateReference(dr as firestore.DocumentReference), + ); + + const normalizedDocs: Array = docs.map(doc => + isString(doc) ? this.db.doc(doc) : (doc as DocumentReference), + ); + + const internalOptions: InternalDocumentsStageOptions = { + ...options, + docs: normalizedDocs, + }; + return new Pipeline(this.db, [new DocumentsSource(internalOptions)]); + } + + /** + * @beta + * Convert the given VectorQuery into an equivalent Pipeline. + * + * @param query A VectorQuery to be converted into a Pipeline. + * + * @throws {@FirestoreError} Thrown if the provided VectorQuer targets a different project or database than the Pipeline. + */ + createFrom(query: firestore.VectorQuery): Pipeline; + + /** + * @beta + * Convert the given Query into an equivalent Pipeline. + * + * @param query A Query to be converted into a Pipeline. + * + * @throws {@FirestoreError} Thrown if the provided VectorQuer targets a different project or database than the Pipeline. + */ + createFrom(query: firestore.Query): Pipeline; + createFrom(query: firestore.Query | firestore.VectorQuery): Pipeline { + return (query as unknown as {_pipeline(): Pipeline})._pipeline(); + } + + _validateReference( + reference: firestore.CollectionReference | firestore.DocumentReference, + ): reference is CollectionReference | DocumentReference { + if ( + !( + reference instanceof CollectionReference || + reference instanceof DocumentReference + ) + ) { + throw new Error( + 'Invalid reference. The value may not be a CollectionReference or DocumentReference. Or, it may be an object from a different SDK build.', + ); + } + + const refDbId = reference.firestore.formattedName; + if (refDbId !== this.db.formattedName) { + throw new Error( + `Invalid ${ + reference instanceof CollectionReference + ? 'CollectionReference' + : 'DocumentReference' + }. ` + + `The database name ("${refDbId}") of this reference does not match ` + + `the database name ("${this.db.formattedName}") of the target database of this Pipeline.`, + ); + } + + return true; + } +} + +/** + * @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 db.pipeline() + * .collection('books') + * .select('title', 'author', field('rating').as('bookRating')) + * .execute(); + * + * // Example 2: Filter documents where 'genre' is 'Science Fiction' and 'published' is after 1950 + * const results2 = await db.pipeline() + * .collection('books') + * .where(and(field('genre').equal('Science Fiction'), field('published').greaterThan(1950))) + * .execute(); + * + * // Example 3: Calculate the average rating of books published after 1980 + * const results3 = await db.pipeline() + * .collection('books') + * .where(field('published').greaterThan(1980)) + * .aggregate(average(field('rating')).as('averageRating')) + * .execute(); + * ``` + */ +export class Pipeline implements firestore.Pipelines.Pipeline { + constructor( + private db: Firestore, + private stages: Stage[], + ) {} + + private _addStage(stage: Stage): Pipeline { + const copy = this.stages.map(s => s); + copy.push(stage); + return new Pipeline(this.db, copy); + } + + /** + * @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 {@FunctionExpression}) 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: firestore.Pipelines.Selectable, + ...additionalFields: firestore.Pipelines.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 {@FunctionExpression}) 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: firestore.Pipelines.AddFieldsStageOptions): Pipeline; + addFields( + fieldOrOptions: + | firestore.Pipelines.Selectable + | firestore.Pipelines.AddFieldsStageOptions, + ...additionalFields: firestore.Pipelines.Selectable[] + ): Pipeline { + const options = isSelectable(fieldOrOptions) ? {} : fieldOrOptions; + const fields: firestore.Pipelines.Selectable[] = isSelectable( + fieldOrOptions, + ) + ? [fieldOrOptions, ...additionalFields] + : fieldOrOptions.fields; + const normalizedFields: Map = selectablesToMap(fields); + + this._validateUserData('select', normalizedFields); + + const internalOptions = { + ...options, + fields: normalizedFields, + }; + return this._addStage(new AddFields(internalOptions)); + } + + /** + * @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: firestore.Pipelines.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: firestore.Pipelines.RemoveFieldsStageOptions): Pipeline; + removeFields( + fieldValueOrOptions: + | firestore.Pipelines.Field + | string + | firestore.Pipelines.RemoveFieldsStageOptions, + ...additionalFields: Array + ): Pipeline { + const options = + isField(fieldValueOrOptions) || isString(fieldValueOrOptions) + ? {} + : fieldValueOrOptions; + + const fields: Array = + isField(fieldValueOrOptions) || isString(fieldValueOrOptions) + ? [fieldValueOrOptions, ...additionalFields] + : fieldValueOrOptions.fields; + const convertedFields: Array = fields.map(f => + isString(f) ? field(f) : (f as Field), + ); + this._validateUserData('removeFields', convertedFields); + + const innerOptions = { + ...options, + fields: convertedFields, + }; + + return this._addStage(new RemoveFields(innerOptions)); + } + + /** + * @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: firestore.Pipelines.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: firestore.Pipelines.SelectStageOptions): Pipeline; + select( + selectionOrOptions: + | firestore.Pipelines.Selectable + | string + | firestore.Pipelines.SelectStageOptions, + ...additionalSelections: Array + ): Pipeline { + const options = + isSelectable(selectionOrOptions) || isString(selectionOrOptions) + ? {} + : selectionOrOptions; + + const selections: Array = + isSelectable(selectionOrOptions) || isString(selectionOrOptions) + ? [selectionOrOptions, ...additionalSelections] + : selectionOrOptions.selections; + const normalizedSelections: Map = + selectablesToMap(selections); + + this._validateUserData('select', normalizedSelections); + + const internalOptions = { + ...options, + selections: normalizedSelections, + }; + return this._addStage(new Select(internalOptions)); + } + + /** + * @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#equal}, {@link Function#lessThan} (less than), {@link + * Function#greaterThan} (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( + * greaterThan(field("rating"), 4.0), // Filter for ratings greater than 4.0 + * field("genre").equal("Science Fiction") // Equivalent to greaterThan("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: firestore.Pipelines.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#equal}, {@link Function#lessThan} (less than), {@link + * Function#greaterThan} (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( + * greaterThan(field("rating"), 4.0), // Filter for ratings greater than 4.0 + * field("genre").equal("Science Fiction") // Equivalent to greaterThan("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: firestore.Pipelines.WhereStageOptions): Pipeline; + where( + conditionOrOptions: + | firestore.Pipelines.BooleanExpression + | firestore.Pipelines.WhereStageOptions, + ): Pipeline { + const options = isBooleanExpr(conditionOrOptions) ? {} : conditionOrOptions; + + const condition: firestore.Pipelines.BooleanExpression = isBooleanExpr( + conditionOrOptions, + ) + ? conditionOrOptions + : conditionOrOptions.condition; + const convertedCondition: BooleanExpression = + condition as BooleanExpression; + this._validateUserData('where', convertedCondition); + + const internalOptions: InternalWhereStageOptions = { + ...options, + condition: convertedCondition, + }; + + return this._addStage(new Where(internalOptions)); + } + + /** + * @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: firestore.Pipelines.OffsetStageOptions): Pipeline; + offset( + offsetOrOptions: number | firestore.Pipelines.OffsetStageOptions, + ): Pipeline { + const options = isNumber(offsetOrOptions) ? {} : offsetOrOptions; + + const offset: number = isNumber(offsetOrOptions) + ? offsetOrOptions + : offsetOrOptions.offset; + + const internalOptions: InternalOffsetStageOptions = { + ...options, + offset, + }; + return this._addStage(new Offset(internalOptions)); + } + + /** + * @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: firestore.Pipelines.LimitStageOptions): Pipeline; + limit( + limitOrOptions: number | firestore.Pipelines.LimitStageOptions, + ): Pipeline { + const options = isNumber(limitOrOptions) ? {} : limitOrOptions; + + const limit: number = isNumber(limitOrOptions) + ? limitOrOptions + : limitOrOptions.limit; + + const internalOptions: InternalLimitStageOptions = { + ...options, + limit, + }; + return this._addStage(new Limit(internalOptions)); + } + + /** + * @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 AliasedExpression}: 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 | firestore.Pipelines.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 AliasedExpression}: 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: firestore.Pipelines.DistinctStageOptions): Pipeline; + distinct( + groupOrOptions: + | string + | firestore.Pipelines.Selectable + | firestore.Pipelines.DistinctStageOptions, + ...additionalGroups: Array + ): Pipeline { + const options = + isString(groupOrOptions) || isSelectable(groupOrOptions) + ? {} + : groupOrOptions; + + const groups: Array = + isString(groupOrOptions) || isSelectable(groupOrOptions) + ? [groupOrOptions, ...additionalGroups] + : groupOrOptions.groups; + const convertedGroups: Map = selectablesToMap(groups); + this._validateUserData('distinct', convertedGroups); + + const internalOptions: InternalDistinctStageOptions = { + ...options, + groups: convertedGroups, + }; + + return this._addStage(new Distinct(internalOptions)); + } + + /** + * @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").average().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: firestore.Pipelines.AliasedAggregate, + ...additionalAccumulators: firestore.Pipelines.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: [average(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: firestore.Pipelines.AggregateStageOptions): Pipeline; + aggregate( + targetOrOptions: + | firestore.Pipelines.AliasedAggregate + | firestore.Pipelines.AggregateStageOptions, + ...rest: firestore.Pipelines.AliasedAggregate[] + ): Pipeline { + const options = isAliasedAggregate(targetOrOptions) ? {} : targetOrOptions; + + const accumulators: Array = + isAliasedAggregate(targetOrOptions) + ? [targetOrOptions, ...rest] + : targetOrOptions.accumulators; + const convertedAccumulators: Map = + aliasedAggregateToMap(accumulators); + const groups: Array = + isAliasedAggregate(targetOrOptions) ? [] : (targetOrOptions.groups ?? []); + const convertedGroups: Map = selectablesToMap(groups); + this._validateUserData('aggregate', convertedGroups); + + const internalOptions: InternalAggregateStageOptions = { + ...options, + accumulators: convertedAccumulators, + groups: convertedGroups, + }; + + return this._addStage(new Aggregate(internalOptions)); + } + + /** + * @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: firestore.Pipelines.FindNearestStageOptions): Pipeline { + const field = toField(options.field); + const vectorValue = vectorToExpr(options.vectorValue); + const distanceField = options.distanceField + ? toField(options.distanceField) + : undefined; + + this._validateUserData('findNearest', field); + + this._validateUserData('findNearest', vectorValue); + + const internalOptions: InternalFindNearestStageOptions = { + ...options, + field, + vectorValue, + distanceField, + }; + + return this._addStage(new FindNearest(internalOptions)); + } + + /** + * @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: firestore.Pipelines.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: firestore.Pipelines.ReplaceWithStageOptions): Pipeline; + replaceWith( + valueOrOptions: + | firestore.Pipelines.Expression + | string + | firestore.Pipelines.ReplaceWithStageOptions, + ): Pipeline { + const options = + isString(valueOrOptions) || isExpr(valueOrOptions) ? {} : valueOrOptions; + + const fieldNameOrExpr: string | firestore.Pipelines.Expression = + isString(valueOrOptions) || isExpr(valueOrOptions) + ? valueOrOptions + : valueOrOptions.map; + const mapExpr = fieldOrExpression(fieldNameOrExpr); + this._validateUserData('replaceWith', mapExpr); + + const internalOptions: InternalReplaceWithStageOptions = { + ...options, + map: mapExpr, + }; + return this._addStage(new ReplaceWith(internalOptions)); + } + + /** + * @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: firestore.Pipelines.SampleStageOptions): Pipeline; + sample( + documentsOrOptions: number | firestore.Pipelines.SampleStageOptions, + ): Pipeline { + 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'; + } + + const internalOptions: InternalSampleStageOptions = { + ...options, + rate, + mode, + }; + + return this._addStage(new Sample(internalOptions)); + } + + /** + * @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: firestore.Pipelines.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: firestore.Pipelines.UnionStageOptions): Pipeline; + union( + otherOrOptions: + | firestore.Pipelines.Pipeline + | firestore.Pipelines.UnionStageOptions, + ): Pipeline { + const options = isPipeline(otherOrOptions) ? {} : otherOrOptions; + + const otherPipeline: firestore.Pipelines.Pipeline = isPipeline( + otherOrOptions, + ) + ? otherOrOptions + : otherOrOptions.other; + const normalizedOtherPipeline = otherPipeline as Pipeline; + + const internalOptions: InternalUnionStageOptions = { + ...options, + other: normalizedOtherPipeline, + }; + return this._addStage(new Union(internalOptions)); + } + + /** + * @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: firestore.Pipelines.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: firestore.Pipelines.UnnestStageOptions): Pipeline; + unnest( + selectableOrOptions: + | firestore.Pipelines.Selectable + | firestore.Pipelines.UnnestStageOptions, + indexField?: string, + ): Pipeline { + const options = isSelectable(selectableOrOptions) + ? {} + : selectableOrOptions; + + const selectable: firestore.Pipelines.Selectable = isSelectable( + selectableOrOptions, + ) + ? selectableOrOptions + : selectableOrOptions.selectable; + const alias = selectable._alias; + const expr = selectable._expr as Expression; + + const indexFieldName = isSelectable(selectableOrOptions) + ? indexField + : selectableOrOptions.indexField; + const normalizedIndexField = indexFieldName + ? field(indexFieldName) + : undefined; + + const internalOptions: InternalUnnestStageOptions = { + ...options, + alias, + expr, + indexField: normalizedIndexField, + }; + return this._addStage(new Unnest(internalOptions)); + } + + /** + * @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: firestore.Pipelines.Ordering, + ...additionalOrderings: firestore.Pipelines.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: firestore.Pipelines.SortStageOptions): Pipeline; + sort( + orderingOrOptions: + | firestore.Pipelines.Ordering + | firestore.Pipelines.SortStageOptions, + ...additionalOrderings: firestore.Pipelines.Ordering[] + ): Pipeline { + const options = isOrdering(orderingOrOptions) ? {} : orderingOrOptions; + + const orderings: Array = isOrdering( + orderingOrOptions, + ) + ? [orderingOrOptions, ...additionalOrderings] + : orderingOrOptions.orderings; + const normalizedOrderings = orderings as Array; + this._validateUserData('sort', normalizedOrderings); + + const internalOptions: InternalSortStageOptions = { + ...options, + orderings: normalizedOrderings, + }; + + return this._addStage(new Sort(internalOptions)); + } + + /** + * @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').lessThan(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 input values to Expressions. + // We treat objects as mapValues and arrays as arrayValues, + // this is unlike the default conversion for objects and arrays + // passed to an expression. + 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); + } + }); + + expressionParams.forEach(param => { + if (hasUserData(param)) { + param._validateUserData(!!this.db._settings.ignoreUndefinedProperties); + } + }); + return this._addStage(new RawStage(name, expressionParams, options ?? {})); + } + + /** + * @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 in a {@link PipelineSnapshot} object, which 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 futureResults = await firestore.pipeline().collection('books') + * .where(greaterThan(field('rating'), 4.5)) + * .select('title', 'author', 'rating') + * .execute(); + * ``` + * + * @param pipelineExecuteOptions - Optionally specify pipeline execution behavior. + * @return A Promise representing the asynchronous pipeline execution. + */ + execute( + pipelineExecuteOptions?: firestore.Pipelines.PipelineExecuteOptions, + ): Promise { + return this._execute(undefined, pipelineExecuteOptions).then(response => { + const results = response.result || []; + const executionTime = response.executionTime; + const stats = response.explainStats; + + return new PipelineSnapshot(this, results, executionTime, stats); + }); + } + + _execute( + transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, + pipelineExecuteOptions?: firestore.Pipelines.PipelineExecuteOptions, + ): Promise { + const util = new ExecutionUtil(this.db, this.db._serializer!); + const structuredPipeline = this._toStructuredPipeline( + pipelineExecuteOptions, + ); + return util + ._getResponse(structuredPipeline, transactionOrReadTime) + .then(result => result!); + } + + _toStructuredPipeline( + pipelineExecuteOptions?: firestore.Pipelines.PipelineExecuteOptions, + ): StructuredPipeline { + const structuredPipelineOptions = pipelineExecuteOptions ?? {}; + const optionsOverride = pipelineExecuteOptions?.rawOptions ?? {}; + return new StructuredPipeline( + this, + structuredPipelineOptions, + optionsOverride, + ); + } + + /** + * @beta + * Executes this pipeline and streams the results as {@link PipelineResult}s. + * + * @returns {Stream.} A stream of + * PipelineResult. + * + * @example + * ```typescript + * firestore.pipeline().collection('books') + * .where(greaterThan(field('rating'), 4.5)) + * .select('title', 'author', 'rating') + * .stream() + * .on('data', (pipelineResult) => {}) + * .on('end', () => {}); + * ``` + */ + stream(): NodeJS.ReadableStream { + const util = new ExecutionUtil(this.db, this.db._serializer!); + // TODO(pipelines) support options + const structuredPipeline = this._toStructuredPipeline(); + return util.stream(structuredPipeline, undefined); + } + + _toProto(): api.IPipeline { + const stages: IStage[] = this.stages.map(stage => + stage._toProto(this.db._serializer!), + ); + return {stages}; + } + + /** + * @beta + * Validates user data for each expression in the expressionMap. + * @param name Name of the calling function. Used for error messages when invalid user data is encountered. + * @param val + * @return the expressionMap argument. + * @private + */ + _validateUserData< + T extends Map | HasUserData[] | HasUserData, + >(_: string, val: T): T { + const ignoreUndefinedProperties = + !!this.db._settings.ignoreUndefinedProperties; + if (hasUserData(val)) { + val._validateUserData(ignoreUndefinedProperties); + } else if (Array.isArray(val)) { + val.forEach(readableData => { + readableData._validateUserData(ignoreUndefinedProperties); + }); + } else { + val.forEach(expr => expr._validateUserData(ignoreUndefinedProperties)); + } + return val; + } +} + +/** + * @beta + * A wrapper object to access explain stats if explain or analyze + * was enabled for the Pipeline query execution. + */ +export class ExplainStats implements firestore.Pipelines.ExplainStats { + private static protoRoot: ProtoRoot | undefined = undefined; + + /** + * @beta + * @private + * @internal + */ + static async _ensureMessageTypesLoaded(): Promise { + if (ExplainStats.protoRoot) { + return; + } + this.protoRoot = await loadProtos( + '../protos/google/protobuf/wrappers.proto', + ); + } + + /** + * @beta + * @private + * @internal + * @hideconstructor + * @param _value + */ + constructor(private readonly explainStatsData: google.protobuf.IAny) {} + + /** + * @beta + * Decode an ExplainStats proto message into a value. + * @private + * @internal + * @param explainStatsMessage + */ + _decode(): unknown { + if (!ExplainStats.protoRoot) { + throw new Error( + 'Ensure message types are loaded before attempting to decode ExplainStats', + ); + } + + if (!this.explainStatsData.value) { + throw new Error('Unexpected explainStatsMessage without data'); + } + + if (!this.explainStatsData.type_url) { + throw new Error('Unexpected explainStatsMessage without type_url'); + } + + const typeUrl = this.explainStatsData.type_url; + let reflectionObject: ReflectionObject | null = null; + const NAMESPACE = 'type.googleapis.com/'; + + if ( + !typeUrl.startsWith(NAMESPACE) || + !(reflectionObject = ExplainStats.protoRoot.lookup( + typeUrl.slice(NAMESPACE.length), + )) || + !(reflectionObject instanceof MessageType) + ) { + throw new Error( + `Unsupported explainStatsMessage type_url "${this.explainStatsData.type_url}". Use \`.rawMessage\` to get access to the encoded explainStats message and perform the protobuf parsing in your application.`, + ); + } + + const messageType = reflectionObject as MessageType; + + return messageType.toObject(messageType.decode(this.explainStatsData.value)) + .value; + } + + /** + * @beta + * When explain stats were requested with `outputFormat = 'text'`, this returns + * the explain stats string verbatium as returned from the Firestore backend. + * + * If explain stats were requested with `outputFormat = 'json'`, this returns + * the explain stats as stringified JSON, which was returned from the Firestore backend. + */ + get text(): string { + const value = this._decode(); + if (isString(value)) { + return value; + } + + throw new Error( + "Explain stats is not in a string format, ensure you requested an `explainOptions.outputFormat` that returns a string value, such as 'text'.", + ); + } + + /** + * @beta + * Returns the explain stats in an encoded proto format, as returned from the Firestore backend. + * The caller is responsible for unpacking this proto message. + */ + get rawData(): { + type_url?: string | null; + value?: Uint8Array | null; + } { + return this.explainStatsData; + } +} + +/** + * @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 implements firestore.Pipelines.PipelineSnapshot { + private readonly _pipeline: Pipeline; + private readonly _executionTime: Timestamp | undefined; + private readonly _results: PipelineResult[]; + private readonly _explainStats: ExplainStats | undefined; + + constructor( + pipeline: Pipeline, + results: PipelineResult[], + executionTime?: Timestamp, + explainStats?: ExplainStats, + ) { + this._pipeline = pipeline; + this._executionTime = executionTime; + this._results = results; + this._explainStats = explainStats; + } + + /** + * @beta + * The Pipeline on which you called `execute()` in order to get this + * `PipelineSnapshot`. + */ + get pipeline(): Pipeline { + return this._pipeline; + } + + /** + * @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 + * Return stats from query explain. + * + * If `explainOptions.mode` was set to `execute` or left unset, then this returns `undefined`. + */ + get explainStats(): ExplainStats | undefined { + return this._explainStats; + } +} + +/** + * @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 implements firestore.Pipelines.PipelineResult { + private readonly _ref: DocumentReference | undefined; + private _serializer: Serializer; + public readonly _executionTime: Timestamp | undefined; + public readonly _createTime: Timestamp | undefined; + public readonly _updateTime: Timestamp | undefined; + + /** + * @beta + * @private + * @internal + * + * @param serializer The serializer used to encode/decode protobuf. + * @param ref The reference to the document. + * @param _fieldsProto The fields of the Firestore `Document` Protobuf backing + * this document (or undefined if the document does not exist). + * @param readTime The time when this result was read (or undefined if + * the document exists only locally). + * @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( + serializer: Serializer, + /** + * @beta + * @internal + * @private + **/ + readonly _fieldsProto: ApiMapValue, + ref?: DocumentReference, + readTime?: Timestamp, + createTime?: Timestamp, + updateTime?: Timestamp, + ) { + this._ref = ref; + this._serializer = serializer; + this._executionTime = readTime; + this._createTime = createTime; + this._updateTime = updateTime; + } + + /** + * @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. + * + * @example + * ``` + * let p = firestore.pipeline().collection('col'); + * + * p.execute().then(results => { + * let data = results[0].data(); + * console.log(`Retrieved data: ${JSON.stringify(data)}`); + * }); + * ``` + */ + data(): firestore.DocumentData { + const fields = this._fieldsProto; + + const obj: firestore.DocumentData = {}; + for (const prop of Object.keys(fields)) { + obj[prop] = this._serializer.decodeValue(fields[prop]); + } + return obj; + } + + /** + * @beta + * Retrieves the field specified by `field`. + * + * @param {string|FieldPath} fieldPath 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): any { + validateFieldPath('field', fieldPath); + + const protoField = this.protoField(fieldPath); + + if (protoField === undefined) { + return undefined; + } + + return this._serializer.decodeValue(protoField); + } + + /** + * @beta + * Retrieves the field specified by 'fieldPath' in its Protobuf JS + * representation. + * + * @private + * @internal + * @param field The path (e.g. 'foo' or 'foo.bar') to a specific field. + * @returns The Protobuf-encoded data at the specified field location or + * undefined if no such field exists. + */ + protoField(field: string | FieldPath): api.IValue | undefined { + let fields: ApiMapValue | api.IValue | undefined = this._fieldsProto; + + if (fields === undefined) { + return undefined; + } + + const components = FieldPath.fromArgument(field).toArray(); + while (components.length > 1) { + fields = (fields as ApiMapValue)[components.shift()!]; + + if (!fields || !fields.mapValue) { + return undefined; + } + + fields = fields.mapValue.fields!; + } + + return (fields as ApiMapValue)[components[0]]; + } + + /** + * @beta + * Returns true if the document's data and path in this `PipelineResult` is + * equal to the provided value. + * + * @param {*} other The value to compare against. + * @return {boolean} true if this `PipelineResult` is equal to the provided + * value. + */ + isEqual(other: PipelineResult): boolean { + return ( + this === other || + (isOptionalEqual(this.ref, other.ref) && + deepEqual(this._fieldsProto, other._fieldsProto)) + ); + } +} diff --git a/dev/src/pipelines/stage.ts b/dev/src/pipelines/stage.ts new file mode 100644 index 000000000..59bfe482d --- /dev/null +++ b/dev/src/pipelines/stage.ts @@ -0,0 +1,608 @@ +// 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 * as firestore from '@google-cloud/firestore'; +import * as protos from '../../protos/firestore_v1_proto_api'; +import api = protos.google.firestore.v1; + +import {DocumentReference} from '../reference/document-reference'; +import {ProtoSerializable, Serializer} from '../serializer'; + +import { + AggregateFunction, + BooleanExpression, + Expression, + Field, + field, + Ordering, +} from './expression'; +import {OptionsUtil} from './options-util'; +import {CollectionReference} from '../reference/collection-reference'; + +/** + * + */ +export interface Stage extends ProtoSerializable { + name: string; + + _toProto(serializer: Serializer): api.Pipeline.IStage; +} + +export type InternalRemoveFieldsStageOptions = Omit< + firestore.Pipelines.RemoveFieldsStageOptions, + 'fields' +> & { + fields: Array; +}; + +/** + * + */ +export class RemoveFields implements Stage { + name = 'remove_fields'; + readonly optionsUtil = new OptionsUtil({}); + + constructor(private options: InternalRemoveFieldsStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: this.options.fields.map(f => serializer.encodeValue(f)!), + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalAggregateStageOptions = Omit< + firestore.Pipelines.AggregateStageOptions, + 'groups' | 'accumulators' +> & { + groups: Map; + accumulators: Map; +}; + +/** + */ +export class Aggregate implements Stage { + name = 'aggregate'; + readonly optionsUtil = new OptionsUtil({}); + + constructor(private options: InternalAggregateStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [ + serializer.encodeValue(this.options.accumulators)!, + serializer.encodeValue(this.options.groups)!, + ], + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalDistinctStageOptions = Omit< + firestore.Pipelines.DistinctStageOptions, + 'groups' +> & { + groups: Map; +}; + +/** + */ +export class Distinct implements Stage { + name = 'distinct'; + readonly optionsUtil = new OptionsUtil({}); + + constructor(private options: InternalDistinctStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [serializer.encodeValue(this.options.groups)!], + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalCollectionStageOptions = Omit< + firestore.Pipelines.CollectionStageOptions, + 'collection' +> & { + collection: CollectionReference; +}; + +/** + */ +export class CollectionSource implements Stage { + name = 'collection'; + readonly optionsUtil = new OptionsUtil({ + forceIndex: { + serverName: 'force_index', + }, + }); + readonly collectionPath: string; + + constructor(private options: InternalCollectionStageOptions) { + this.collectionPath = this.options.collection.path; + // prepend slash to collection string + if (!this.collectionPath.startsWith('/')) { + this.collectionPath = '/' + this.collectionPath; + } + } + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [serializer.encodeReference(this.collectionPath)], + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalCollectionGroupStageOptions = + firestore.Pipelines.CollectionGroupStageOptions; + +/** + */ +export class CollectionGroupSource implements Stage { + name = 'collection_group'; + readonly optionsUtil = new OptionsUtil({ + forceIndex: { + serverName: 'force_index', + }, + }); + + constructor(private options: InternalCollectionGroupStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [ + serializer.encodeReference(''), + serializer.encodeValue(this.options.collectionId)!, + ], + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalDatabaseStageOptions = + firestore.Pipelines.DatabaseStageOptions; + +/** + */ +export class DatabaseSource implements Stage { + name = 'database'; + readonly optionsUtil = new OptionsUtil({}); + + constructor(private options: InternalDatabaseStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalDocumentsStageOptions = Omit< + firestore.Pipelines.DocumentsStageOptions, + 'docs' +> & { + docs: Array; +}; + +/** + */ +export class DocumentsSource implements Stage { + name = 'documents'; + readonly optionsUtil = new OptionsUtil({}); + readonly formattedPaths: string[]; + + constructor(private options: InternalDocumentsStageOptions) { + this.formattedPaths = options.docs.map(ref => '/' + ref.path); + } + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: this.formattedPaths.map(p => serializer.encodeReference(p)!), + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalWhereStageOptions = Omit< + firestore.Pipelines.WhereStageOptions, + 'condition' +> & { + condition: BooleanExpression; +}; + +/** + */ +export class Where implements Stage { + name = 'where'; + readonly optionsUtil = new OptionsUtil({}); + + constructor(private options: InternalWhereStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [this.options.condition._toProto(serializer)], + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalFindNearestStageOptions = Omit< + firestore.Pipelines.FindNearestStageOptions, + 'vectorValue' | 'field' | 'distanceField' +> & { + vectorValue: Expression; + field: Field; + distanceField?: Field; +}; + +/** + */ +export class FindNearest implements Stage { + name = 'find_nearest'; + readonly optionsUtil = new OptionsUtil({ + limit: { + serverName: 'limit', + }, + distanceField: { + serverName: 'distance_field', + }, + }); + + constructor(private _options: InternalFindNearestStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [ + serializer.encodeValue(this._options.field)!, + serializer.encodeValue(this._options.vectorValue)!, + serializer.encodeValue(this._options.distanceMeasure)!, + ], + options: this.optionsUtil.getOptionsProto( + serializer, + this._options, + this._options.rawOptions, + ), + }; + } +} + +export type InternalSampleStageOptions = Omit< + firestore.Pipelines.SampleStageOptions, + 'percentage' | 'documents' +> & { + rate: number; + mode: 'percent' | 'documents'; +}; + +/** + */ +export class Sample implements Stage { + name = 'sample'; + readonly optionsUtil = new OptionsUtil({}); + + constructor(private options: InternalSampleStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [ + serializer.encodeValue(this.options.rate)!, + serializer.encodeValue(this.options.mode)!, + ], + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalUnionStageOptions = firestore.Pipelines.UnionStageOptions; + +/** + */ +export class Union implements Stage { + name = 'union'; + readonly optionsUtil = new OptionsUtil({}); + + constructor(private options: InternalUnionStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [serializer.encodeValue(this.options.other)!], + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalUnnestStageOptions = Omit< + firestore.Pipelines.UnnestStageOptions, + 'selectable' | 'indexField' +> & { + alias: string; + expr: Expression; + indexField?: Field; +}; + +/** + */ +export class Unnest implements Stage { + name = 'unnest'; + readonly optionsUtil = new OptionsUtil({ + indexField: { + serverName: 'index_field', + }, + }); + + constructor(private options: InternalUnnestStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [ + serializer.encodeValue(this.options.expr)!, + serializer.encodeValue(field(this.options.alias))!, + ], + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalLimitStageOptions = firestore.Pipelines.LimitStageOptions; + +/** + */ +export class Limit implements Stage { + name = 'limit'; + readonly optionsUtil = new OptionsUtil({}); + + constructor(private options: InternalLimitStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [serializer.encodeValue(this.options.limit)!], + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalOffsetStageOptions = firestore.Pipelines.OffsetStageOptions; + +/** + */ +export class Offset implements Stage { + name = 'offset'; + readonly optionsUtil = new OptionsUtil({}); + + constructor(private options: InternalOffsetStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [serializer.encodeValue(this.options.offset)!], + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalReplaceWithStageOptions = Omit< + firestore.Pipelines.ReplaceWithStageOptions, + 'map' +> & { + map: Expression; +}; + +/** + */ +export class ReplaceWith implements Stage { + name = 'replace_with'; + readonly optionsUtil = new OptionsUtil({}); + + constructor(private options: InternalReplaceWithStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [ + serializer.encodeValue(this.options.map)!, + serializer.encodeValue('full_replace'), + ], + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalSelectStageOptions = Omit< + firestore.Pipelines.SelectStageOptions, + 'selections' +> & { + selections: Map; +}; + +/** + */ +export class Select implements Stage { + name = 'select'; + readonly optionsUtil = new OptionsUtil({}); + + constructor(private options: InternalSelectStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [serializer.encodeValue(this.options.selections)!], + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalAddFieldsStageOptions = Omit< + firestore.Pipelines.AddFieldsStageOptions, + 'fields' +> & { + fields: Map; +}; + +/** + * + */ +export class AddFields implements Stage { + name = 'add_fields'; + readonly optionsUtil = new OptionsUtil({}); + + constructor(private options: InternalAddFieldsStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [serializer.encodeValue(this.options.fields)!], + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +export type InternalSortStageOptions = Omit< + firestore.Pipelines.SortStageOptions, + 'orderings' +> & { + orderings: Array; +}; + +/** + */ +export class Sort implements Stage { + name = 'sort'; + readonly optionsUtil = new OptionsUtil({}); + + constructor(private options: InternalSortStageOptions) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: this.options.orderings.map(o => serializer.encodeValue(o)!), + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.options.rawOptions, + ), + }; + } +} + +/** + */ +export class RawStage implements Stage { + readonly optionsUtil = new OptionsUtil({}); + + /** + * @private + * @internal + */ + constructor( + public name: string, + private params: Array, + private rawOptions: Record, + ) {} + + /** + * @internal + * @private + */ + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: this.params.map(o => o._toProto(serializer)), + options: this.optionsUtil.getOptionsProto( + serializer, + {}, + this.rawOptions, + ), + }; + } +} diff --git a/dev/src/pipelines/structured-pipeline.ts b/dev/src/pipelines/structured-pipeline.ts new file mode 100644 index 000000000..9559c12e9 --- /dev/null +++ b/dev/src/pipelines/structured-pipeline.ts @@ -0,0 +1,68 @@ +/** + * @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 {ProtoSerializable, Serializer} from '../serializer'; +import {google} from '../../protos/firestore_v1_proto_api'; +import IStructuredPipeline = google.firestore.v1.IStructuredPipeline; +import IPipeline = google.firestore.v1.IPipeline; +import {OptionsUtil} from './options-util'; + +export type StructuredPipelineOptions = { + indexMode?: 'recommended'; + explainOptions?: { + mode?: 'execute' | 'explain' | 'analyze'; + outputFormat?: 'text'; + }; +}; + +export class StructuredPipeline + implements ProtoSerializable +{ + readonly optionsUtil = new OptionsUtil({ + indexMode: { + serverName: 'index_mode', + }, + explainOptions: { + serverName: 'explain_options', + nestedOptions: { + mode: { + serverName: 'mode', + }, + outputFormat: { + serverName: 'output_format', + }, + }, + }, + }); + + constructor( + private pipeline: ProtoSerializable, + private options: StructuredPipelineOptions, + private optionsOverride: Record, + ) {} + + _toProto(serializer: Serializer): IStructuredPipeline { + return { + pipeline: this.pipeline._toProto(serializer), + options: this.optionsUtil.getOptionsProto( + serializer, + this.options, + this.optionsOverride, + ), + }; + } +} diff --git a/dev/src/reference/aggregate-query.ts b/dev/src/reference/aggregate-query.ts index 050df2da5..6c2e19b32 100644 --- a/dev/src/reference/aggregate-query.ts +++ b/dev/src/reference/aggregate-query.ts @@ -22,6 +22,8 @@ import * as deepEqual from 'fast-deep-equal'; import * as firestore from '@google-cloud/firestore'; import {Aggregate, AggregateSpec} from '../aggregate'; +import {average, count, countAll, field, sum} from '../pipelines'; +import {Pipeline} from '../pipelines'; import {Timestamp} from '../timestamp'; import {mapToArray, requestTag, wrapError} from '../util'; import {ExplainMetrics, ExplainResults} from '../query-profile'; @@ -347,6 +349,40 @@ export class AggregateQuery< return runQueryRequest; } + /** + * @private + * @internal + */ + _pipeline(): Pipeline { + const aggregates = mapToArray( + this._aggregates, + (aggregate, clientAlias) => { + if (aggregate.aggregateType === 'count') { + if (aggregate._field === undefined) { + return countAll().as(clientAlias); + } + return count(field(aggregate._field)).as(clientAlias); + } else if (aggregate.aggregateType === 'avg') { + return average(field(aggregate._field!)).as(clientAlias); + } else if (aggregate.aggregateType === 'sum') { + return sum(field(aggregate._field!)).as(clientAlias); + } else { + throw new Error(`Unknown aggregate type ${aggregate.aggregateType}`); + } + }, + ); + + if (aggregates.length === 0) { + throw new Error( + 'Cannot convert an AggregateQuery with 0 aggregates to a Pipeline', + ); + } + + return this._query + ._pipeline() + .aggregate(aggregates[0], ...aggregates.slice(1)); + } + /** * Compares this object with the given object for equality. * diff --git a/dev/src/reference/collection-reference.ts b/dev/src/reference/collection-reference.ts index 81967b499..3d8958fe5 100644 --- a/dev/src/reference/collection-reference.ts +++ b/dev/src/reference/collection-reference.ts @@ -303,7 +303,6 @@ export class CollectionReference< ); } - withConverter(converter: null): CollectionReference; withConverter< NewAppModelType, NewDbModelType extends firestore.DocumentData = firestore.DocumentData, @@ -313,6 +312,9 @@ export class CollectionReference< NewDbModelType >, ): CollectionReference; + withConverter( + converter: null, + ): CollectionReference; /** * Applies a custom data converter to this CollectionReference, allowing you * to use your own custom model objects with Firestore. When you call add() on diff --git a/dev/src/reference/composite-filter-internal.ts b/dev/src/reference/composite-filter-internal.ts index 7dfda544c..a6a042973 100644 --- a/dev/src/reference/composite-filter-internal.ts +++ b/dev/src/reference/composite-filter-internal.ts @@ -40,6 +40,14 @@ export class CompositeFilterInternal extends FilterInternal { return this.operator === 'AND'; } + /** + * @private + * @internal + */ + public _getOperator(): api.StructuredQuery.CompositeFilter.Operator { + return this.operator; + } + public getFlattenedFilters(): FieldFilterInternal[] { if (this.memoizedFlattenedFilters !== null) { return this.memoizedFlattenedFilters; diff --git a/dev/src/reference/document-reference.ts b/dev/src/reference/document-reference.ts index dd9ed9d74..593f60a01 100644 --- a/dev/src/reference/document-reference.ts +++ b/dev/src/reference/document-reference.ts @@ -565,7 +565,6 @@ export class DocumentReference< return {referenceValue: this.formattedName}; } - withConverter(converter: null): DocumentReference; withConverter< NewAppModelType, NewDbModelType extends firestore.DocumentData = firestore.DocumentData, @@ -573,7 +572,7 @@ export class DocumentReference< converter: firestore.FirestoreDataConverter< NewAppModelType, NewDbModelType - >, + > | null, ): DocumentReference; /** * Applies a custom data converter to this DocumentReference, allowing you to diff --git a/dev/src/reference/field-filter-internal.ts b/dev/src/reference/field-filter-internal.ts index 58560efbf..3b08c4919 100644 --- a/dev/src/reference/field-filter-internal.ts +++ b/dev/src/reference/field-filter-internal.ts @@ -48,8 +48,8 @@ export class FieldFilterInternal extends FilterInternal { constructor( private readonly serializer: Serializer, readonly field: FieldPath, - private readonly op: api.StructuredQuery.FieldFilter.Operator, - private readonly value: unknown, + readonly op: api.StructuredQuery.FieldFilter.Operator, + readonly value: unknown, ) { super(); } @@ -74,6 +74,38 @@ export class FieldFilterInternal extends FilterInternal { } } + /** + * @private + * @internal + */ + isNanChecking(): boolean { + return typeof this.value === 'number' && isNaN(this.value); + } + + /** + * @private + * @internal + */ + nanOp(): 'IS_NAN' | 'IS_NOT_NAN' { + return this.op === 'EQUAL' ? 'IS_NAN' : 'IS_NOT_NAN'; + } + + /** + * @private + * @internal + */ + isNullChecking(): boolean { + return this.value === null; + } + + /** + * @private + * @internal + */ + nullOp(): 'IS_NULL' | 'IS_NOT_NULL' { + return this.op === 'EQUAL' ? 'IS_NULL' : 'IS_NOT_NULL'; + } + /** * Generates the proto representation for this field filter. * @@ -81,24 +113,24 @@ export class FieldFilterInternal extends FilterInternal { * @internal */ toProto(): api.StructuredQuery.IFilter { - if (typeof this.value === 'number' && isNaN(this.value)) { + if (this.isNanChecking()) { return { unaryFilter: { field: { fieldPath: this.field.formattedName, }, - op: this.op === 'EQUAL' ? 'IS_NAN' : 'IS_NOT_NAN', + op: this.nanOp(), }, }; } - if (this.value === null) { + if (this.isNullChecking()) { return { unaryFilter: { field: { fieldPath: this.field.formattedName, }, - op: this.op === 'EQUAL' ? 'IS_NULL' : 'IS_NOT_NULL', + op: this.nullOp(), }, }; } diff --git a/dev/src/reference/query.ts b/dev/src/reference/query.ts index d3c383417..3f2f84238 100644 --- a/dev/src/reference/query.ts +++ b/dev/src/reference/query.ts @@ -19,43 +19,53 @@ import api = protos.google.firestore.v1; import * as firestore from '@google-cloud/firestore'; import {GoogleError} from 'google-gax'; import {Transform} from 'stream'; +import {and, field, Ordering} from '../pipelines'; -import {QueryUtil} from './query-util'; +import {CompositeFilter, UnaryFilter} from '../filter'; import { - Firestore, AggregateField, DocumentChange, DocumentSnapshot, FieldPath, Filter, + Firestore, QueryDocumentSnapshot, Timestamp, } from '../index'; -import {QueryOptions} from './query-options'; -import {FieldOrder} from './field-order'; -import {FilterInternal} from './filter-internal'; -import {FieldFilterInternal} from './field-filter-internal'; -import {CompositeFilterInternal} from './composite-filter-internal'; -import {comparisonOperators, directionOperators} from './constants'; -import {VectorQueryOptions} from './vector-query-options'; -import {DocumentReference} from './document-reference'; -import {QuerySnapshot} from './query-snapshot'; -import {Serializer} from '../serializer'; -import {ExplainResults} from '../query-profile'; - -import {CompositeFilter, UnaryFilter} from '../filter'; +import {compare} from '../order'; import {validateFieldPath} from '../path'; +import {Pipeline} from '../pipelines'; import { - validateQueryOperator, - validateQueryOrder, - validateQueryValue, -} from './helpers'; + reverseOrderings, + toPipelineBooleanExpr, + whereConditionsFromCursor, +} from '../pipelines/pipeline-util'; +import {ExplainResults} from '../query-profile'; +import {Serializer} from '../serializer'; +import {defaultConverter} from '../types'; import { invalidArgumentMessage, validateFunction, validateInteger, validateMinNumberOfArguments, } from '../validate'; +import {QueryWatch} from '../watch'; +import {AggregateQuery} from './aggregate-query'; +import {CompositeFilterInternal} from './composite-filter-internal'; +import {comparisonOperators, directionOperators} from './constants'; +import {DocumentReference} from './document-reference'; +import {FieldFilterInternal} from './field-filter-internal'; +import {FieldOrder} from './field-order'; +import {FilterInternal} from './filter-internal'; +import { + validateQueryOperator, + validateQueryOrder, + validateQueryValue, +} from './helpers'; +import {QueryOptions} from './query-options'; +import {QuerySnapshot} from './query-snapshot'; + +import {QueryUtil} from './query-util'; import { LimitType, QueryCursor, @@ -63,11 +73,8 @@ import { QuerySnapshotResponse, QueryStreamElement, } from './types'; -import {AggregateQuery} from './aggregate-query'; import {VectorQuery} from './vector-query'; -import {QueryWatch} from '../watch'; -import {compare} from '../order'; -import {defaultConverter} from '../types'; +import {VectorQueryOptions} from './vector-query-options'; import {SPAN_NAME_QUERY_GET} from '../telemetry/trace-util'; /** @@ -721,6 +728,149 @@ export class Query< return new VectorQuery(this, options); } + /** + * Returns a value indicating if this query is a collection group query + */ + _isCollectionGroupQuery(): boolean { + return this._queryOptions.allDescendants; + } + + /** + * @private + * @internal + */ + _pipeline(): Pipeline { + let pipeline: Pipeline; + const db = this.firestore; + if (this._isCollectionGroupQuery()) { + pipeline = db + .pipeline() + .collectionGroup(this._queryOptions.collectionId!); + } else { + pipeline = db + .pipeline() + .collection( + this._queryOptions.parentPath.append(this._queryOptions.collectionId) + .relativeName, + ); + } + + // filters + for (const filter of this._queryOptions.filters) { + pipeline = pipeline.where( + toPipelineBooleanExpr(filter, this._serializer), + ); + } + + // projections + const projections = this._queryOptions.projection?.fields || []; + if (projections.length > 0) { + const projectionFields = projections.map(p => field(p.fieldPath!)); + pipeline = pipeline.select( + projectionFields[0], + ...projectionFields.slice(1), + ); + } + + // orders + // Ignore inequality fields when creating implicit order-bys + // for generating existsConditions, because existence filters + // will have already been added in the call to `toPipelineBooleanExpr` + const existsConditions = this.createImplicitOrderBy(true).map( + fieldOrder => { + return field(fieldOrder.field).exists(); + }, + ); + if (existsConditions.length > 1) { + const [first, second, ...rest] = existsConditions; + pipeline = pipeline.where(and(first, second, ...rest)); + } else { + pipeline = pipeline.where(existsConditions[0]); + } + + const orderings = this.createImplicitOrderBy().map(fieldOrder => { + let dir: 'ascending' | 'descending' | undefined = undefined; + switch (fieldOrder.direction) { + case 'ASCENDING': { + dir = 'ascending'; + break; + } + case 'DESCENDING': { + dir = 'descending'; + break; + } + } + return new Ordering(field(fieldOrder.field), dir || 'ascending'); + }); + + if (orderings.length > 0) { + if (this._queryOptions.limitType === LimitType.Last) { + const actualOrderings = reverseOrderings(orderings); + pipeline = pipeline.sort( + actualOrderings[0], + ...actualOrderings.slice(1), + ); + // cursors + if (this._queryOptions.startAt !== undefined) { + pipeline = pipeline.where( + whereConditionsFromCursor( + this._queryOptions.startAt, + orderings, + 'after', + ), + ); + } + + if (this._queryOptions.endAt !== undefined) { + pipeline = pipeline.where( + whereConditionsFromCursor( + this._queryOptions.endAt, + orderings, + 'before', + ), + ); + } + + if (this._queryOptions.limit !== undefined) { + pipeline = pipeline.limit(this._queryOptions.limit!); + } + + pipeline = pipeline.sort(orderings[0], ...orderings.slice(1)); + } else { + pipeline = pipeline.sort(orderings[0], ...orderings.slice(1)); + if (this._queryOptions.startAt !== undefined) { + pipeline = pipeline.where( + whereConditionsFromCursor( + this._queryOptions.startAt, + orderings, + 'after', + ), + ); + } + if (this._queryOptions.endAt !== undefined) { + pipeline = pipeline.where( + whereConditionsFromCursor( + this._queryOptions.endAt, + orderings, + 'before', + ), + ); + } + + if (this._queryOptions.limit !== undefined) { + pipeline = pipeline.limit(this._queryOptions.limit); + } + } + } + + // offset + if (this._queryOptions.offset) { + pipeline = pipeline.offset(this._queryOptions.offset); + } + + return pipeline; + } + /** * Returns true if this `Query` is equal to the provided value. * @@ -765,7 +915,7 @@ export class Query< * set of field values to use as the boundary. * @returns The implicit ordering semantics. */ - private createImplicitOrderBy( + private createImplicitOrderByForCursor( cursorValuesOrDocumentSnapshot: Array< DocumentSnapshot | unknown >, @@ -778,6 +928,10 @@ export class Query< return this._queryOptions.fieldOrders; } + return this.createImplicitOrderBy(); + } + + private createImplicitOrderBy(ignoreInequalityFields = false): FieldOrder[] { const fieldOrders = this._queryOptions.fieldOrders.slice(); const fieldsNormalized = new Set([ ...fieldOrders.map(item => item.field.toString()), @@ -789,21 +943,23 @@ export class Query< ? directionOperators.ASC : fieldOrders[fieldOrders.length - 1].direction; - /** - * Any inequality fields not explicitly ordered should be implicitly ordered in a - * lexicographical order. When there are multiple inequality filters on the same field, the - * field should be added only once. - * Note: getInequalityFilterFields function sorts the key field before - * other fields. However, we want the key field to be sorted last. - */ - const inequalityFields = this.getInequalityFilterFields(); - for (const field of inequalityFields) { - if ( - !fieldsNormalized.has(field.toString()) && - !field.isEqual(FieldPath.documentId()) - ) { - fieldOrders.push(new FieldOrder(field, lastDirection)); - fieldsNormalized.add(field.toString()); + if (!ignoreInequalityFields) { + /** + * Any inequality fields not explicitly ordered should be implicitly ordered in a + * lexicographical order. When there are multiple inequality filters on the same field, the + * field should be added only once. + * Note: getInequalityFilterFields function sorts the key field before + * other fields. However, we want the key field to be sorted last. + */ + const inequalityFields = this.getInequalityFilterFields(); + for (const field of inequalityFields) { + if ( + !fieldsNormalized.has(field.toString()) && + !field.isEqual(FieldPath.documentId()) + ) { + fieldOrders.push(new FieldOrder(field, lastDirection)); + fieldsNormalized.add(field.toString()); + } } } @@ -973,7 +1129,7 @@ export class Query< 1, ); - const fieldOrders = this.createImplicitOrderBy( + const fieldOrders = this.createImplicitOrderByForCursor( fieldValuesOrDocumentSnapshot, ); const startAt = this.createCursor( @@ -1017,7 +1173,7 @@ export class Query< 1, ); - const fieldOrders = this.createImplicitOrderBy( + const fieldOrders = this.createImplicitOrderByForCursor( fieldValuesOrDocumentSnapshot, ); const startAt = this.createCursor( @@ -1060,7 +1216,7 @@ export class Query< 1, ); - const fieldOrders = this.createImplicitOrderBy( + const fieldOrders = this.createImplicitOrderByForCursor( fieldValuesOrDocumentSnapshot, ); const endAt = this.createCursor( @@ -1103,7 +1259,7 @@ export class Query< 1, ); - const fieldOrders = this.createImplicitOrderBy( + const fieldOrders = this.createImplicitOrderByForCursor( fieldValuesOrDocumentSnapshot, ); const endAt = this.createCursor( @@ -1585,7 +1741,6 @@ export class Query< }; } - withConverter(converter: null): Query; withConverter< NewAppModelType, NewDbModelType extends firestore.DocumentData = firestore.DocumentData, @@ -1593,7 +1748,7 @@ export class Query< converter: firestore.FirestoreDataConverter< NewAppModelType, NewDbModelType - >, + > | null, ): Query; /** * Applies a custom data converter to this Query, allowing you to use your diff --git a/dev/src/reference/types.ts b/dev/src/reference/types.ts index 844233e24..884eb8bc2 100644 --- a/dev/src/reference/types.ts +++ b/dev/src/reference/types.ts @@ -15,6 +15,7 @@ */ import * as protos from '../../protos/firestore_v1_proto_api'; +import {PipelineResult} from '../pipelines'; import api = protos.google.firestore.v1; import {Timestamp} from '../timestamp'; @@ -22,6 +23,7 @@ import {ExplainMetrics} from '../query-profile'; import {QueryDocumentSnapshot} from '../document'; import * as firestore from '@google-cloud/firestore'; +import {ExplainStats} from '../pipelines/pipelines'; export interface QueryStreamElement< AppModelType = firestore.DocumentData, @@ -59,6 +61,20 @@ export enum LimitType { Last, } +export interface PipelineStreamElement { + transaction?: Uint8Array; + executionTime?: Timestamp; + explainStats?: ExplainStats; + result?: PipelineResult; +} + +export interface PipelineResponse { + transaction?: Uint8Array; + executionTime?: Timestamp; + result?: Array; + explainStats?: ExplainStats; +} + /** * onSnapshot() callback that receives a QuerySnapshot. * diff --git a/dev/src/reference/vector-query.ts b/dev/src/reference/vector-query.ts index 17512faf2..56de19296 100644 --- a/dev/src/reference/vector-query.ts +++ b/dev/src/reference/vector-query.ts @@ -17,6 +17,8 @@ import * as protos from '../../protos/firestore_v1_proto_api'; import api = protos.google.firestore.v1; import * as firestore from '@google-cloud/firestore'; +import {field} from '../pipelines'; +import {Pipeline} from '../pipelines'; import {Timestamp} from '../timestamp'; import {VectorValue} from '../field-value'; @@ -138,6 +140,26 @@ export class VectorQuery< return result; } + /** + * @private + * @internal + */ + _pipeline(): Pipeline { + const options: firestore.Pipelines.FindNearestStageOptions = { + field: field(this._options.vectorField), + vectorValue: this._options.queryVector, + limit: this._options.limit, + distanceMeasure: this._options.distanceMeasure.toLowerCase() as + | 'cosine' + | 'euclidean' + | 'dot_product', + }; + return this.query + ._pipeline() + .where(field(this._options.vectorField).exists()) + .findNearest(options); + } + _getResponse( explainOptions?: firestore.ExplainOptions, ): Promise>> { diff --git a/dev/src/serializer.ts b/dev/src/serializer.ts index 32177703a..d5164a8d5 100644 --- a/dev/src/serializer.ts +++ b/dev/src/serializer.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import {DocumentData} from '@google-cloud/firestore'; +import * as firestore from '@google-cloud/firestore'; import * as proto from '../protos/firestore_v1_proto_api'; @@ -22,11 +22,12 @@ import {DeleteTransform, FieldTransform, VectorValue} from './field-value'; import {detectGoogleProtobufValueType, detectValueType} from './convert'; import {GeoPoint} from './geo-point'; import {DocumentReference, Firestore} from './index'; -import {FieldPath, QualifiedResourcePath} from './path'; +import {FieldPath, ObjectValueFieldPath, QualifiedResourcePath} from './path'; import {Timestamp} from './timestamp'; import {ApiMapValue, ValidationOptions} from './types'; -import {isEmpty, isObject, isPlainObject} from './util'; +import {forEach, isEmpty, isObject, isPlainObject} from './util'; import {customObjectMessage, invalidArgumentMessage} from './validate'; +import {Pipeline} from './pipelines'; import api = proto.google.firestore.v1; import { @@ -34,6 +35,11 @@ import { RESERVED_MAP_KEY_VECTOR_VALUE, VECTOR_MAP_VECTORS_KEY, } from './map-type'; +import {google} from '../protos/firestore_v1_proto_api'; +import IMapValue = google.firestore.v1.IMapValue; +import IValue = google.firestore.v1.IValue; +import Value = google.firestore.v1.Value; +import {isString} from './pipelines/pipeline-util'; /** * The maximum depth of a Firestore object. @@ -62,14 +68,14 @@ export interface Serializable { */ export class Serializer { private allowUndefined: boolean; - private createReference: (path: string) => DocumentReference; + private createDocumentReference: (path: string) => DocumentReference; private createInteger: (n: number | string) => number | BigInt; - constructor(firestore: Firestore) { + constructor(private firestore: Firestore) { // Instead of storing the `firestore` object, we store just a reference to // its `.doc()` method. This avoid a circular reference, which breaks // JSON.stringify(). - this.createReference = path => firestore.doc(path); + this.createDocumentReference = path => firestore.doc(path); this.createInteger = n => firestore._settings.useBigInt ? BigInt(n) : Number(n); this.allowUndefined = !!firestore._settings.ignoreUndefinedProperties; @@ -83,7 +89,7 @@ export class Serializer { * @param obj The object to encode. * @returns The Firestore 'Fields' representation */ - encodeFields(obj: DocumentData): ApiMapValue { + encodeFields(obj: firestore.DocumentData): ApiMapValue { const fields: ApiMapValue = {}; for (const prop of Object.keys(obj)) { @@ -105,6 +111,22 @@ export class Serializer { * @param val The object to encode * @returns The Firestore Proto or null if we are deleting a field. */ + encodeValue(val: FieldTransform | undefined): null; + encodeValue( + val: + | string + | boolean + | number + | bigint + | Date + | null + | Buffer + | Uint8Array + | VectorValue + | Map + | Pipeline, + ): api.IValue; + encodeValue(val: unknown): api.IValue | null; encodeValue(val: unknown): api.IValue | null { if (val instanceof FieldTransform) { return null; @@ -177,6 +199,12 @@ export class Serializer { return val._toProto(this); } + if (val instanceof Pipeline) { + return { + pipelineValue: val._toProto(), + }; + } + if (isObject(val)) { const toProto = val['toProto']; if (typeof toProto === 'function') { @@ -184,6 +212,13 @@ export class Serializer { } } + if (isObject(val)) { + const _toProto = val['_toProto']; + if (typeof _toProto === 'function') { + return _toProto.bind(val)(this); + } + } + if (Array.isArray(val)) { const array: api.IValue = { arrayValue: {}, @@ -202,6 +237,21 @@ export class Serializer { return array; } + if (val instanceof Map) { + const map: api.IMapValue = {fields: {}}; + for (const [key, value] of val.entries()) { + if (typeof key !== 'string') { + throw new Error(`Cannot encode map with non-string key: ${key}`); + } + + map.fields![key] = this.encodeValue(value)!; + } + + return { + mapValue: map, + }; + } + if (typeof val === 'object' && isPlainObject(val)) { const map: api.IValue = { mapValue: {}, @@ -226,6 +276,14 @@ export class Serializer { throw new Error(`Cannot encode value: ${val}`); } + encodeReference( + path: string | firestore.DocumentReference | firestore.CollectionReference, + ): api.IValue { + return { + referenceValue: isString(path) ? path : path.path, + }; + } + /** * @private */ @@ -282,7 +340,13 @@ export class Serializer { const resourcePath = QualifiedResourcePath.fromSlashSeparatedString( proto.referenceValue!, ); - return this.createReference(resourcePath.relativeName); + if (resourcePath.isDocument) { + return this.createDocumentReference(resourcePath.relativeName); + } else { + throw new Error( + `The SDK does not currently support decoding referenceValues for collections or partitions. Actual path value: '${proto.referenceValue!}'`, + ); + } } case 'arrayValue': { const array: unknown[] = []; @@ -299,7 +363,7 @@ export class Serializer { case 'mapValue': { const fields = proto.mapValue!.fields; if (fields) { - const obj: DocumentData = {}; + const obj: firestore.DocumentData = {}; for (const prop of Object.keys(fields)) { obj[prop] = this.decodeValue(fields[prop]); } @@ -546,6 +610,8 @@ export function validateUserInput( // Ok. } else if (value === null) { // Ok. + } else if (isProtoValueSerializable(value)) { + // Ok. } else if (typeof value === 'object') { throw new Error(customObjectMessage(arg, value, path)); } @@ -565,3 +631,130 @@ function isMomentJsType(value: unknown): value is {toDate(): Date} { typeof (value as {toDate: unknown}).toDate === 'function' ); } + +export function isProtoValueSerializable( + value: unknown, +): value is ProtoValueSerializable { + return ( + !!value && + typeof (value as ProtoValueSerializable)._toProto === 'function' && + (value as ProtoValueSerializable)._protoValueType === 'ProtoValue' + ); +} + +export interface ProtoSerializable { + _toProto(serializer: Serializer): ProtoType; +} + +export interface ProtoValueSerializable extends ProtoSerializable { + // Supports runtime identification of the ProtoSerializable type. + _protoValueType: 'ProtoValue'; +} + +export interface HasUserData { + _validateUserData(ignoreUndefinedProperties: boolean): void; +} + +export function hasUserData(value: unknown): value is HasUserData { + return typeof (value as HasUserData)._validateUserData === 'function'; +} + +/** + * An ObjectValue represents a MapValue in the Firestore Proto and offers the + * ability to add and remove fields. + */ +export class ObjectValue { + constructor(readonly value: {mapValue: IMapValue}) {} + + static empty(): ObjectValue { + return new ObjectValue({mapValue: {}}); + } + + /** + * Sets the field to the provided value. + * + * @param path - The field path to set. + * @param value - The value to set. + */ + set(path: ObjectValueFieldPath, value: IValue): void { + const fieldsMap = this.getFieldsMap(path.popLast()); + fieldsMap[path.lastSegment()] = Value.fromObject(value).toJSON(); + } + + /** + * Sets the provided fields to the provided values. + * + * @param data - A map of fields to values (or null for deletes). + */ + setAll(data: Map): void { + let parent = new ObjectValueFieldPath(); + + let upserts: {[key: string]: IValue} = {}; + let deletes: string[] = []; + + data.forEach((value, path) => { + if (!parent.isImmediateParentOf(path)) { + // Insert the accumulated changes at this parent location + const fieldsMap = this.getFieldsMap(parent); + this.applyChanges(fieldsMap, upserts, deletes); + upserts = {}; + deletes = []; + parent = path.popLast(); + } + + if (value) { + upserts[path.lastSegment()] = Value.fromObject(value).toJSON(); + } else { + deletes.push(path.lastSegment()); + } + }); + + const fieldsMap = this.getFieldsMap(parent); + this.applyChanges(fieldsMap, upserts, deletes); + } + + /** + * Returns the map that contains the leaf element of `path`. If the parent + * entry does not yet exist, or if it is not a map, a new map will be created. + */ + private getFieldsMap(path: ObjectValueFieldPath): Record { + let current = this.value; + + if (!current.mapValue!.fields) { + current.mapValue = {fields: {}}; + } + + for (let i = 0; i < path.size; ++i) { + let next = current.mapValue!.fields![path.get(i)]; + if (!isMapValue(next) || !next.mapValue.fields) { + next = {mapValue: {fields: {}}}; + current.mapValue!.fields![path.get(i)] = next; + } + current = next as {mapValue: IMapValue}; + } + + return current.mapValue!.fields!; + } + + /** + * Modifies `fieldsMap` by adding, replacing or deleting the specified + * entries. + */ + private applyChanges( + fieldsMap: Record, + inserts: {[key: string]: IValue}, + deletes: string[], + ): void { + forEach(inserts, (key, val) => (fieldsMap[key] = val)); + for (const field of deletes) { + delete fieldsMap[field]; + } + } +} + +/** Returns true if `value` is a MapValue. */ +export function isMapValue( + value?: IValue | null, +): value is {mapValue: IMapValue} { + return !!value && 'mapValue' in value; +} diff --git a/dev/src/transaction.ts b/dev/src/transaction.ts index 88b4154db..b3cb2e670 100644 --- a/dev/src/transaction.ts +++ b/dev/src/transaction.ts @@ -22,6 +22,7 @@ import * as proto from '../protos/firestore_v1_proto_api'; import {ExponentialBackoff} from './backoff'; import {DocumentSnapshot} from './document'; import {DEFAULT_MAX_TRANSACTION_ATTEMPTS, Firestore, WriteBatch} from './index'; +import {Pipeline, PipelineResult, PipelineSnapshot} from './pipelines'; import {Timestamp} from './timestamp'; import {logger} from './logger'; import {FieldPath, validateFieldPath} from './path'; @@ -286,6 +287,62 @@ export class Transaction implements firestore.Transaction { ); } + /** + * 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 in a {@link PipelineSnapshot} object, which 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 futureResults = await transaction + * .execute( + * firestore.pipeline().collection("books") + * .where(greaterThan(field("rating"), 4.5)) + * .select("title", "author", "rating")); + * ``` + * + * @return A Promise representing the asynchronous pipeline execution. + */ + execute(pipeline: Pipeline): Promise { + if (this._writeBatch && !this._writeBatch.isEmpty) { + throw new Error(READ_AFTER_WRITE_ERROR_MSG); + } + + if (pipeline instanceof Pipeline) { + return this.withLazyStartedTransaction( + pipeline, + this.executePipelineFn, + ).then(results => { + const executionTime = results.reduce((maxTime, result) => { + return result._executionTime && + result._executionTime?.valueOf() > maxTime.valueOf() + ? result._executionTime + : maxTime; + }, Timestamp.fromMillis(0)); + + return new PipelineSnapshot(pipeline, results, executionTime); + }); + } + + throw new Error('Value for argument "pipeline" must be a Pipeline'); + } + /** * Create the document referred to by the provided * [DocumentReference]{@link DocumentReference}. The operation will @@ -797,6 +854,17 @@ export class Transaction implements firestore.Transaction { }> { return query._get(opts); } + + private async executePipelineFn( + pipeline: Pipeline, + opts: Uint8Array | api.ITransactionOptions | Timestamp, + ): Promise<{ + transaction?: Uint8Array; + result: Array; + }> { + const {transaction, result} = await pipeline._execute(opts); + return {transaction, result: result || []}; + } } /** diff --git a/dev/src/types.ts b/dev/src/types.ts index 0523bd46c..94a7e8f52 100644 --- a/dev/src/types.ts +++ b/dev/src/types.ts @@ -64,6 +64,10 @@ export interface GapicClient { request?: api.IBatchGetDocumentsRequest, options?: CallOptions, ): Duplex; + executePipeline( + request?: api.IExecutePipelineRequest, + options?: CallOptions, + ): Duplex; runQuery(request?: api.IRunQueryRequest, options?: CallOptions): Duplex; runAggregationQuery( request?: api.IRunAggregationQueryRequest, @@ -96,6 +100,7 @@ export type FirestoreUnaryMethod = /** Streaming methods used in the Firestore SDK. */ export type FirestoreStreamingMethod = + | 'executePipeline' | 'listen' | 'partitionQueryStream' | 'runQuery' diff --git a/dev/src/util.ts b/dev/src/util.ts index 10effb5e8..8290471e2 100644 --- a/dev/src/util.ts +++ b/dev/src/util.ts @@ -316,6 +316,30 @@ export function isArrayEqual boolean}>( return true; } +/** + * Verifies equality for an optional type using the `isEqual` interface. + * + * @private + * @internal + * @param left Optional object supporting `isEqual`. + * @param right Optional object supporting `isEqual`. + * @return True if equal. + */ +export function isOptionalEqual boolean}>( + left: T | undefined, + right: T | undefined, +): boolean { + if (left === undefined && right === undefined) { + return true; + } + + if (left === undefined || right === undefined) { + return false; + } + + return left.isEqual(right); +} + /** * Verifies equality for an array of primitives. * @@ -341,3 +365,27 @@ export function isPrimitiveArrayEqual( return true; } + +export function cast( + val: unknown, + constructor?: {new (...args: unknown[]): T}, +): T { + if (!constructor) { + return val as T; + } else if (val instanceof constructor) { + return val; + } + + throw new Error(`${val} not instance of ${constructor}`); +} + +export function forEach( + obj: Record | undefined, + fn: (key: string, val: V) => void, +): void { + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + fn(key, obj[key]); + } + } +} diff --git a/dev/src/v1beta1/firestore_client.ts b/dev/src/v1beta1/firestore_client.ts index 132509a83..7d3b2fde4 100644 --- a/dev/src/v1beta1/firestore_client.ts +++ b/dev/src/v1beta1/firestore_client.ts @@ -39,6 +39,12 @@ import {loggingUtils as logging, decodeAnyProtosInArray} from 'google-gax'; import * as gapicConfig from './firestore_client_config.json'; // tslint:disable deprecation +// tslint:disable deprecation + +// tslint:disable deprecation + +// tslint:disable deprecation + const version = require('../../../package.json').version; /** @@ -52,6 +58,9 @@ const version = require('../../../package.json').version; * building truly serverless apps. * @class * @deprecated Use v1/firestore_client instead. + * @deprecated Use v1/firestore_client instead. + * @deprecated Use v1/firestore_client instead. + * @deprecated Use v1/firestore_client instead. * @memberof v1beta1 */ export class FirestoreClient { diff --git a/dev/system-test/firestore.ts b/dev/system-test/firestore.ts index 382c1f3a8..2ceca9626 100644 --- a/dev/system-test/firestore.ts +++ b/dev/system-test/firestore.ts @@ -24,6 +24,7 @@ import { } from '@google-cloud/firestore'; import {afterEach, before, beforeEach, describe, it} from 'mocha'; +import '../test/util/mocha_extensions'; import {expect, use} from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as extend from 'extend'; @@ -48,6 +49,7 @@ import {autoId, Deferred} from '../src/util'; import {TEST_BUNDLE_ID, verifyMetadata} from '../test/bundle'; import { bundleToElementArray, + isEnterprise, Post, postConverter, postConverterMerge, @@ -65,7 +67,7 @@ use(chaiAsPromised); const version = require('../../package.json').version; -class DeferredPromise { +export class DeferredPromise { resolve: Function; reject: Function; promise: Promise | null; @@ -101,17 +103,39 @@ if (process.env.NODE_ENV === 'DEBUG') { setLogFunction(console.log); } -function getTestRoot(settings: Settings = {}): CollectionReference { +export function getTestDb(settings: Settings = {}): Firestore { const internalSettings: Settings = {}; if (process.env.FIRESTORE_NAMED_DATABASE) { internalSettings.databaseId = process.env.FIRESTORE_NAMED_DATABASE; } - const firestore = new Firestore({ + if (process.env.FIRESTORE_TARGET_BACKEND) { + switch (process.env.FIRESTORE_TARGET_BACKEND.toUpperCase()) { + case 'PROD': { + break; + } + case 'QA': { + internalSettings.host = 'staging-firestore.sandbox.googleapis.com'; + break; + } + case 'NIGHTLY': { + internalSettings.host = 'test-firestore.sandbox.googleapis.com'; + break; + } + default: { + break; + } + } + } + + return new Firestore({ ...internalSettings, ...settings, // caller settings take precedent over internal settings }); - return firestore.collection(`node_${version}_${autoId()}`); +} + +export function getTestRoot(settings: Settings = {}): CollectionReference { + return getTestDb(settings).collection(`node_${version}_${autoId()}`); } describe('Firestore class', () => { @@ -147,7 +171,7 @@ describe('Firestore class', () => { }); }); - it('can plan a query using default options', async () => { + it.skipEnterprise('can plan a query using default options', async () => { await randomCol.doc('doc1').set({foo: 1}); await randomCol.doc('doc2').set({foo: 2}); await randomCol.doc('doc3').set({foo: 1}); @@ -167,7 +191,7 @@ describe('Firestore class', () => { expect(explainResults.snapshot).to.be.null; }); - it('can plan a query', async () => { + it.skipEnterprise('can plan a query', async () => { await randomCol.doc('doc1').set({foo: 1}); await randomCol.doc('doc2').set({foo: 2}); await randomCol.doc('doc3').set({foo: 1}); @@ -189,7 +213,7 @@ describe('Firestore class', () => { expect(explainResults.snapshot).to.be.null; }); - it('can profile a query', async () => { + it.skipEnterprise('can profile a query', async () => { await randomCol.doc('doc1').set({foo: 1, bar: 0}); await randomCol.doc('doc2').set({foo: 2, bar: 1}); await randomCol.doc('doc3').set({foo: 1, bar: 2}); @@ -219,77 +243,83 @@ describe('Firestore class', () => { expect(explainResults.snapshot!.size).to.equal(2); }); - it('can profile a query that does not match any docs', async () => { - await randomCol.doc('doc1').set({foo: 1, bar: 0}); - await randomCol.doc('doc2').set({foo: 2, bar: 1}); - await randomCol.doc('doc3').set({foo: 1, bar: 2}); - const results = await randomCol.where('foo', '==', 12345).get(); - expect(results.empty).to.be.true; - expect(results.docs.length).to.equal(0); - expect(results.readTime.toMillis()).to.be.greaterThan(0); + it.skipEnterprise( + 'can profile a query that does not match any docs', + async () => { + await randomCol.doc('doc1').set({foo: 1, bar: 0}); + await randomCol.doc('doc2').set({foo: 2, bar: 1}); + await randomCol.doc('doc3').set({foo: 1, bar: 2}); + const results = await randomCol.where('foo', '==', 12345).get(); + expect(results.empty).to.be.true; + expect(results.docs.length).to.equal(0); + expect(results.readTime.toMillis()).to.be.greaterThan(0); - const explainResults = await randomCol - .where('foo', '==', 12345) - .explain({analyze: true}); + const explainResults = await randomCol + .where('foo', '==', 12345) + .explain({analyze: true}); - const metrics = explainResults.metrics; + const metrics = explainResults.metrics; - expect(metrics.planSummary).to.not.be.null; - expect(metrics.executionStats).to.not.be.null; - expect(explainResults.snapshot).to.not.be.null; + expect(metrics.planSummary).to.not.be.null; + expect(metrics.executionStats).to.not.be.null; + expect(explainResults.snapshot).to.not.be.null; - expect( - Object.keys(metrics.planSummary.indexesUsed).length, - ).to.be.greaterThan(0); + expect( + Object.keys(metrics.planSummary.indexesUsed).length, + ).to.be.greaterThan(0); - const stats = metrics.executionStats!; - expect(stats.readOperations).to.be.greaterThan(0); - expect(stats.resultsReturned).to.be.equal(0); - expect( - stats.executionDuration.nanoseconds > 0 || - stats.executionDuration.seconds > 0, - ).to.be.true; - expect(Object.keys(stats.debugStats).length).to.be.greaterThan(0); + const stats = metrics.executionStats!; + expect(stats.readOperations).to.be.greaterThan(0); + expect(stats.resultsReturned).to.be.equal(0); + expect( + stats.executionDuration.nanoseconds > 0 || + stats.executionDuration.seconds > 0, + ).to.be.true; + expect(Object.keys(stats.debugStats).length).to.be.greaterThan(0); - expect(explainResults.snapshot!.size).to.equal(0); - }); + expect(explainResults.snapshot!.size).to.equal(0); + }, + ); - it('can stream explain results with default options', async () => { - await randomCol.doc('doc1').set({foo: 1, bar: 0}); - await randomCol.doc('doc2').set({foo: 2, bar: 1}); - await randomCol.doc('doc3').set({foo: 1, bar: 2}); - let totalResponses = 0; - let totalDocuments = 0; - let metrics: ExplainMetrics | null = null; - const stream = randomCol.explainStream(); - const promise = new Promise((resolve, reject) => { - stream.on('data', data => { - ++totalResponses; - if (data.document) { - ++totalDocuments; - } - if (data.metrics) { - metrics = data.metrics; - } - }); - stream.on('end', () => { - expect(totalResponses).to.equal(1); - expect(totalDocuments).to.equal(0); - expect(metrics).to.not.be.null; - expect(metrics!.planSummary.indexesUsed.length).to.be.greaterThan(0); - expect(metrics!.executionStats).to.be.null; - resolve(true); - }); - stream.on('error', (error: Error) => { - reject(error); + it.skipEnterprise( + 'can stream explain results with default options', + async () => { + await randomCol.doc('doc1').set({foo: 1, bar: 0}); + await randomCol.doc('doc2').set({foo: 2, bar: 1}); + await randomCol.doc('doc3').set({foo: 1, bar: 2}); + let totalResponses = 0; + let totalDocuments = 0; + let metrics: ExplainMetrics | null = null; + const stream = randomCol.explainStream(); + const promise = new Promise((resolve, reject) => { + stream.on('data', data => { + ++totalResponses; + if (data.document) { + ++totalDocuments; + } + if (data.metrics) { + metrics = data.metrics; + } + }); + stream.on('end', () => { + expect(totalResponses).to.equal(1); + expect(totalDocuments).to.equal(0); + expect(metrics).to.not.be.null; + expect(metrics!.planSummary.indexesUsed.length).to.be.greaterThan(0); + expect(metrics!.executionStats).to.be.null; + resolve(true); + }); + stream.on('error', (error: Error) => { + reject(error); + }); }); - }); - const success: boolean = await promise; - expect(success).to.be.true; - }); + const success: boolean = await promise; + expect(success).to.be.true; + }, + ); - it('can stream explain results without analyze', async () => { + it.skipEnterprise('can stream explain results without analyze', async () => { await randomCol.doc('doc1').set({foo: 1, bar: 0}); await randomCol.doc('doc2').set({foo: 2, bar: 1}); await randomCol.doc('doc3').set({foo: 1, bar: 2}); @@ -324,7 +354,7 @@ describe('Firestore class', () => { expect(success).to.be.true; }); - it('can stream explain results with analyze', async () => { + it.skipEnterprise('can stream explain results with analyze', async () => { await randomCol.doc('doc1').set({foo: 1, bar: 0}); await randomCol.doc('doc2').set({foo: 2, bar: 1}); await randomCol.doc('doc3').set({foo: 1, bar: 2}); @@ -362,26 +392,29 @@ describe('Firestore class', () => { expect(success).to.be.true; }); - it('can plan an aggregate query using default options', async () => { - await randomCol.doc('doc1').set({foo: 1}); - await randomCol.doc('doc2').set({foo: 2}); - await randomCol.doc('doc3').set({foo: 1}); - const explainResults = await randomCol - .where('foo', '>', 0) - .count() - .explain(); + it.skipEnterprise( + 'can plan an aggregate query using default options', + async () => { + await randomCol.doc('doc1').set({foo: 1}); + await randomCol.doc('doc2').set({foo: 2}); + await randomCol.doc('doc3').set({foo: 1}); + const explainResults = await randomCol + .where('foo', '>', 0) + .count() + .explain(); - const metrics = explainResults.metrics; + const metrics = explainResults.metrics; - const plan = metrics.planSummary; - expect(plan).to.not.be.null; - expect(Object.keys(plan.indexesUsed).length).to.be.greaterThan(0); + const plan = metrics.planSummary; + expect(plan).to.not.be.null; + expect(Object.keys(plan.indexesUsed).length).to.be.greaterThan(0); - expect(metrics.executionStats).to.be.null; - expect(explainResults.snapshot).to.be.null; - }); + expect(metrics.executionStats).to.be.null; + expect(explainResults.snapshot).to.be.null; + }, + ); - it('can plan an aggregate query', async () => { + it.skipEnterprise('can plan an aggregate query', async () => { await randomCol.doc('doc1').set({foo: 1}); await randomCol.doc('doc2').set({foo: 2}); await randomCol.doc('doc3').set({foo: 1}); @@ -400,7 +433,7 @@ describe('Firestore class', () => { expect(explainResults.snapshot).to.be.null; }); - it('can profile an aggregate query', async () => { + it.skipEnterprise('can profile an aggregate query', async () => { await randomCol.doc('doc1').set({foo: 1}); await randomCol.doc('doc2').set({foo: 2}); await randomCol.doc('doc3').set({foo: 1}); @@ -429,7 +462,7 @@ describe('Firestore class', () => { expect(explainResults.snapshot!.data().count).to.equal(3); }); - it('can plan a vector query', async () => { + it.skipEnterprise('can plan a vector query', async () => { const indexTestHelper = new IndexTestHelper(firestore); const collectionReference = await indexTestHelper.createTestDocs([ @@ -461,7 +494,7 @@ describe('Firestore class', () => { expect(explainResults.snapshot).to.be.null; }); - it('can profile a vector query', async () => { + it.skipEnterprise('can profile a vector query', async () => { const indexTestHelper = new IndexTestHelper(firestore); const collectionReference = await indexTestHelper.createTestDocs([ @@ -588,128 +621,125 @@ describe('Firestore class', () => { // Skip partition query tests when running against the emulator because // partition queries are not supported by the emulator. -(process.env.FIRESTORE_EMULATOR_HOST === undefined ? describe : describe.skip)( - 'CollectionGroup class', - () => { - const desiredPartitionCount = 3; - const documentCount = 2 * 128 + 127; // Minimum partition size is 128. - - let firestore: Firestore; - let randomColl: CollectionReference; - let collectionGroup: CollectionGroup; +describe.skipEmulator.skipEnterprise('CollectionGroup class', () => { + const desiredPartitionCount = 3; + const documentCount = 2 * 128 + 127; // Minimum partition size is 128. - before(async () => { - randomColl = getTestRoot(); - firestore = randomColl.firestore; - collectionGroup = firestore.collectionGroup(randomColl.id); + let firestore: Firestore; + let randomColl: CollectionReference; + let collectionGroup: CollectionGroup; - const batch = firestore.batch(); - for (let i = 0; i < documentCount; ++i) { - batch.create(randomColl.doc(), {title: 'post', author: 'author'}); - } - await batch.commit(); - }); + before(async () => { + randomColl = getTestRoot(); + firestore = randomColl.firestore; + collectionGroup = firestore.collectionGroup(randomColl.id); - async function getPartitions( - collectionGroup: CollectionGroup, - desiredPartitionsCount: number, - ): Promise[]> { - const partitions: QueryPartition[] = []; - for await (const partition of collectionGroup.getPartitions( - desiredPartitionsCount, - )) { - partitions.push(partition); - } - return partitions; + const batch = firestore.batch(); + for (let i = 0; i < documentCount; ++i) { + batch.create(randomColl.doc(), {title: 'post', author: 'author'}); } + await batch.commit(); + }); - async function verifyPartitions( - partitions: QueryPartition[], - ): Promise[]> { - expect(partitions.length).to.not.be.greaterThan(desiredPartitionCount); + async function getPartitions( + collectionGroup: CollectionGroup, + desiredPartitionsCount: number, + ): Promise[]> { + const partitions: QueryPartition[] = []; + for await (const partition of collectionGroup.getPartitions( + desiredPartitionsCount, + )) { + partitions.push(partition); + } + return partitions; + } - expect(partitions[0].startAt).to.be.undefined; - for (let i = 0; i < partitions.length - 1; ++i) { - // The cursor value is a single DocumentReference - expect( - (partitions[i].endBefore![0] as DocumentReference).isEqual( - partitions[i + 1].startAt![0] as DocumentReference, - ), - ).to.be.true; - } - expect(partitions[partitions.length - 1].endBefore).to.be.undefined; + async function verifyPartitions( + partitions: QueryPartition[], + ): Promise[]> { + expect(partitions.length).to.not.be.greaterThan(desiredPartitionCount); - // Validate that we can use the partitions to read the original documents. - const documents: QueryDocumentSnapshot[] = []; - for (const partition of partitions) { - documents.push(...(await partition.toQuery().get()).docs); - } - expect(documents.length).to.equal(documentCount); + expect(partitions[0].startAt).to.be.undefined; + for (let i = 0; i < partitions.length - 1; ++i) { + // The cursor value is a single DocumentReference + expect( + (partitions[i].endBefore![0] as DocumentReference).isEqual( + partitions[i + 1].startAt![0] as DocumentReference, + ), + ).to.be.true; + } + expect(partitions[partitions.length - 1].endBefore).to.be.undefined; - return documents; + // Validate that we can use the partitions to read the original documents. + const documents: QueryDocumentSnapshot[] = []; + for (const partition of partitions) { + documents.push(...(await partition.toQuery().get()).docs); } + expect(documents.length).to.equal(documentCount); - it('partition query', async () => { - const partitions = await getPartitions( - collectionGroup, - desiredPartitionCount, - ); - await verifyPartitions(partitions); - }); + return documents; + } - it('partition query with manual cursors', async () => { - const partitions = await getPartitions( - collectionGroup, - desiredPartitionCount, - ); + it('partition query', async () => { + const partitions = await getPartitions( + collectionGroup, + desiredPartitionCount, + ); + await verifyPartitions(partitions); + }); - const documents: QueryDocumentSnapshot[] = []; - for (const partition of partitions) { - let partitionedQuery: Query = collectionGroup.orderBy( - FieldPath.documentId(), - ); - if (partition.startAt) { - partitionedQuery = partitionedQuery.startAt(...partition.startAt); - } - if (partition.endBefore) { - partitionedQuery = partitionedQuery.endBefore(...partition.endBefore); - } - documents.push(...(await partitionedQuery.get()).docs); + it('partition query with manual cursors', async () => { + const partitions = await getPartitions( + collectionGroup, + desiredPartitionCount, + ); + + const documents: QueryDocumentSnapshot[] = []; + for (const partition of partitions) { + let partitionedQuery: Query = collectionGroup.orderBy( + FieldPath.documentId(), + ); + if (partition.startAt) { + partitionedQuery = partitionedQuery.startAt(...partition.startAt); } + if (partition.endBefore) { + partitionedQuery = partitionedQuery.endBefore(...partition.endBefore); + } + documents.push(...(await partitionedQuery.get()).docs); + } - expect(documents.length).to.equal(documentCount); - }); + expect(documents.length).to.equal(documentCount); + }); - it('partition query with converter', async () => { - const collectionGroupWithConverter = - collectionGroup.withConverter(postConverter); - const partitions = await getPartitions( - collectionGroupWithConverter, - desiredPartitionCount, - ); - const documents = await verifyPartitions(partitions); + it('partition query with converter', async () => { + const collectionGroupWithConverter = + collectionGroup.withConverter(postConverter); + const partitions = await getPartitions( + collectionGroupWithConverter, + desiredPartitionCount, + ); + const documents = await verifyPartitions(partitions); - for (const document of documents) { - expect(document.data()).to.be.an.instanceOf(Post); - } - }); + for (const document of documents) { + expect(document.data()).to.be.an.instanceOf(Post); + } + }); - it('empty partition query', async () => { - const desiredPartitionCount = 3; + it('empty partition query', async () => { + const desiredPartitionCount = 3; - const collectionGroupId = randomColl.doc().id; - const collectionGroup = firestore.collectionGroup(collectionGroupId); - const partitions = await getPartitions( - collectionGroup, - desiredPartitionCount, - ); + const collectionGroupId = randomColl.doc().id; + const collectionGroup = firestore.collectionGroup(collectionGroupId); + const partitions = await getPartitions( + collectionGroup, + desiredPartitionCount, + ); - expect(partitions.length).to.equal(1); - expect(partitions[0].startAt).to.be.undefined; - expect(partitions[0].endBefore).to.be.undefined; - }); - }, -); + expect(partitions.length).to.equal(1); + expect(partitions[0].startAt).to.be.undefined; + expect(partitions[0].endBefore).to.be.undefined; + }); +}); describe('CollectionReference class', () => { let firestore: Firestore; @@ -760,7 +790,8 @@ describe('CollectionReference class', () => { }); }); - it('lists missing documents', async () => { + // showMissing is not supported in Enterprise + it.skipEnterprise('lists missing documents', async () => { const batch = firestore.batch(); batch.set(randomCol.doc('a'), {}); @@ -778,24 +809,28 @@ describe('CollectionReference class', () => { expect(missingDocs.map(doc => doc.id)).to.have.members(['b']); }); - it('lists documents (more than the max page size)', async () => { - const batch = firestore.batch(); - const expectedResults = []; - for (let i = 0; i < 400; i++) { - const docRef = randomCol.doc(`${i}`.padStart(3, '0')); - batch.set(docRef, {id: i}); - expectedResults.push(docRef.id); - } - await batch.commit(); + // showMissing is not supported in Enterprise + it.skipEnterprise( + 'lists documents (more than the max page size)', + async () => { + const batch = firestore.batch(); + const expectedResults = []; + for (let i = 0; i < 400; i++) { + const docRef = randomCol.doc(`${i}`.padStart(3, '0')); + batch.set(docRef, {id: i}); + expectedResults.push(docRef.id); + } + await batch.commit(); - const documentRefs = await randomCol.listDocuments(); + const documentRefs = await randomCol.listDocuments(); - const actualDocIds = documentRefs - .map(dr => dr.id) - .sort((a, b) => a.localeCompare(b)); + const actualDocIds = documentRefs + .map(dr => dr.id) + .sort((a, b) => a.localeCompare(b)); - expect(actualDocIds).to.deep.equal(expectedResults); - }); + expect(actualDocIds).to.deep.equal(expectedResults); + }, + ); it('supports withConverter()', async () => { const ref = await firestore @@ -1191,7 +1226,10 @@ describe('DocumentReference class', () => { }); }); - it('has listCollections() method', () => { + // TODO this test times out in the RPC because there is no index in the backend + // to support the query. The latency scales with the total number of collection + // groups in the database, regardless of which collection / parent is being listed. + it.skipEnterprise('has listCollections() method', () => { const collections: string[] = []; const promises: Array> = []; @@ -1835,7 +1873,7 @@ describe('runs query on a large collection', () => { }); }); -describe('Query class', () => { +describe.skipEnterprise('Query class - Standard DB', () => { interface PaginatedResults { pages: number; docs: QueryDocumentSnapshot[]; @@ -2061,7 +2099,7 @@ describe('Query class', () => { expect( res.docs[2].get('embedding').isEqual(FieldValue.vector([20, 0])) || - res.docs[2].get('embedding').isEqual(FieldValue.vector([20, 0])), + res.docs[2].get('embedding').isEqual(FieldValue.vector([10, 0])), ).to.be.true; }); @@ -2783,7 +2821,11 @@ describe('Query class', () => { {zip: null}, ); - let res = await randomCol.where('zip', '!=', 98101).get(); + let res = await randomCol + .where('zip', '!=', 98101) + .orderBy('zip') + .orderBy(FieldPath.documentId()) + .get(); expectDocs( res, {zip: NaN}, @@ -2822,6 +2864,7 @@ describe('Query class', () => { const refs = await addDocs({count: 1}, {count: 2}, {count: 3}); const res = await randomCol .where(FieldPath.documentId(), '!=', refs[0].id) + .orderBy(FieldPath.documentId()) .get(); expectDocs(res, {count: 2}, {count: 3}); }); @@ -2845,6 +2888,7 @@ describe('Query class', () => { ); res = await randomCol.where('zip', 'not-in', [NaN]).get(); + expectDocs( res, {zip: 91102}, @@ -2856,6 +2900,7 @@ describe('Query class', () => { ); res = await randomCol.where('zip', 'not-in', [null]).get(); + expect(res.size).to.equal(0); }); @@ -2876,7 +2921,10 @@ describe('Query class', () => { {zip: ['98101', {zip: 98101}]}, {zip: {zip: 98101}}, ); - const res = await randomCol.where('zip', 'in', [98101, 98103]).get(); + const res = await randomCol + .where('zip', 'in', [98101, 98103]) + .orderBy('zip') + .get(); expectDocs(res, {zip: 98101}, {zip: 98103}); }); @@ -3212,6 +3260,7 @@ describe('Query class', () => { const querySnapshot = await firestore .collectionGroup(collectionGroup) + .orderBy(FieldPath.documentId()) .get(); expect(querySnapshot.docs.map(d => d.id)).to.deep.equal([ 'cg-doc1', @@ -3287,6 +3336,7 @@ describe('Query class', () => { .collectionGroup(collectionGroup) .where(FieldPath.documentId(), '>=', 'a/b') .where(FieldPath.documentId(), '<=', 'a/b0') + .orderBy(FieldPath.documentId()) .get(); expect(querySnapshot.docs.map(d => d.id)).to.deep.equal([ 'cg-doc2', @@ -3330,6 +3380,7 @@ describe('Query class', () => { .where( Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)), ) + .orderBy(FieldPath.documentId()) .get(), 'doc1', 'doc2', @@ -3346,6 +3397,7 @@ describe('Query class', () => { Filter.and(Filter.where('a', '==', 3), Filter.where('b', '==', 2)), ), ) + .orderBy(FieldPath.documentId()) .get(), 'doc1', 'doc3', @@ -3360,6 +3412,7 @@ describe('Query class', () => { Filter.or(Filter.where('b', '==', 0), Filter.where('b', '==', 3)), ), ) + .orderBy(FieldPath.documentId()) .get(), 'doc1', 'doc4', @@ -3374,6 +3427,7 @@ describe('Query class', () => { Filter.or(Filter.where('a', '==', 3), Filter.where('b', '==', 3)), ), ) + .orderBy(FieldPath.documentId()) .get(), 'doc3', ); @@ -3385,87 +3439,84 @@ describe('Query class', () => { Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 1)), ) .limit(1) + .orderBy(FieldPath.documentId()) .get(), 'doc2', ); }); - // Skip this test if running against production because it results in a 'missing index' error. - // The Firestore Emulator, however, does serve these queries. - (process.env.FIRESTORE_EMULATOR_HOST === undefined ? it.skip : it)( - 'supports OR queries with composite indexes', - async () => { - const collection = await testCollectionWithDocs({ - doc1: {a: 1, b: 0}, - doc2: {a: 2, b: 1}, - doc3: {a: 3, b: 2}, - doc4: {a: 1, b: 3}, - doc5: {a: 1, b: 1}, - }); + // TODO Enterprise - wait for implicit sort order decision + // Skip this test if running against standard production because it results in a 'missing index' error. + it.skipClassic('supports OR queries with composite indexes', async () => { + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: 0}, + doc2: {a: 2, b: 1}, + doc3: {a: 3, b: 2}, + doc4: {a: 1, b: 3}, + doc5: {a: 1, b: 1}, + }); - // with one inequality: a>2 || b==1. - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '>', 2), Filter.where('b', '==', 1)), - ) - .get(), - 'doc5', - 'doc2', - 'doc3', - ); + // with one inequality: a>2 || b==1. + expectDocs( + await collection + .where(Filter.or(Filter.where('a', '>', 2), Filter.where('b', '==', 1))) + .orderBy(FieldPath.documentId()) + .get(), + 'doc2', + 'doc3', + 'doc5', + ); - // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2 - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '==', 1), Filter.where('b', '>', 0)), - ) - .limit(2) - .get(), - 'doc1', - 'doc2', - ); + // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2 + expectDocs( + await collection + .where(Filter.or(Filter.where('a', '==', 1), Filter.where('b', '>', 0))) + .limit(2) + .orderBy(FieldPath.documentId()) + .get(), + 'doc1', + 'doc2', + ); - // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2 - // Note: The public query API does not allow implicit ordering when limitToLast is used. - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '==', 1), Filter.where('b', '>', 0)), - ) - .limitToLast(2) - .orderBy('b') - .get(), - 'doc3', - 'doc4', - ); + // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2 + // Note: The public query API does not allow implicit ordering when limitToLast is used. + expectDocs( + await collection + .where(Filter.or(Filter.where('a', '==', 1), Filter.where('b', '>', 0))) + .limitToLast(2) + .orderBy('b') + .orderBy(FieldPath.documentId()) + .get(), + 'doc3', + 'doc4', + ); - // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1 - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 1)), - ) - .limit(1) - .orderBy('a') - .get(), - 'doc5', - ); + // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1 + expectDocs( + await collection + .where( + Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 1)), + ) + .limit(1) + .orderBy('a') + .orderBy(FieldPath.documentId()) + .get(), + 'doc5', + ); - // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT 1 - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 1)), - ) - .limit(1) - .orderBy('a', 'desc') - .get(), - 'doc2', - ); - }, - ); + // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT 1 + expectDocs( + await collection + .where( + Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 1)), + ) + .limit(1) + .orderBy('a', 'desc') + .orderBy(FieldPath.documentId()) + .get(), + 'doc2', + ); + }); it('supports OR queries on documents with missing fields', async () => { const collection = await testCollectionWithDocs({ @@ -3485,6 +3536,7 @@ describe('Query class', () => { .where( Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)), ) + .orderBy(FieldPath.documentId()) .get(), 'doc1', 'doc2', @@ -3493,9 +3545,9 @@ describe('Query class', () => { ); }); - // Skip this test if running against production because it results in a 'missing index' error. - // The Firestore Emulator, however, does serve these queries. - (process.env.FIRESTORE_EMULATOR_HOST === undefined ? it.skip : it)( + // Skip this test if running against production standard DB because it results in a 'missing index' error. + // The Firestore Emulator and Enterprise-editions, however, do serve these queries. + it.skipClassic( 'supports OR queries on documents with missing fields', async () => { const collection = await testCollectionWithDocs({ @@ -3515,6 +3567,7 @@ describe('Query class', () => { Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)), ) .orderBy('a') + .orderBy(FieldPath.documentId()) .get(), 'doc1', 'doc4', @@ -3529,6 +3582,7 @@ describe('Query class', () => { Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)), ) .orderBy('b') + .orderBy(FieldPath.documentId()) .get(), 'doc1', 'doc2', @@ -3537,7 +3591,7 @@ describe('Query class', () => { // Query: a>2 || b==1. // This query has an implicit 'order by a'. - // doc2 should not be included because it's missing the field 'a'. + // Standard Ed: doc2 should not be included because it's missing the field 'a'. expectDocs( await collection .where( @@ -3582,6 +3636,7 @@ describe('Query class', () => { Filter.where('b', 'in', [2, 3]), ), ) + .orderBy(FieldPath.documentId()) .get(), 'doc3', 'doc4', @@ -3590,35 +3645,30 @@ describe('Query class', () => { }); // Skip this test if running against production because it results in a 'missing index' error. - // The Firestore Emulator, however, does serve these queries. - (process.env.FIRESTORE_EMULATOR_HOST === undefined ? it.skip : it)( - 'supports OR queries with not-in', - async () => { - const collection = await testCollectionWithDocs({ - doc1: {a: 1, b: 0}, - doc2: {b: 1}, - doc3: {a: 3, b: 2}, - doc4: {a: 1, b: 3}, - doc5: {a: 1}, - doc6: {a: 2}, - }); - - // a==2 || (b != 2 && b != 3) - // Has implicit "orderBy b" - expectDocs( - await collection - .where( - Filter.or( - Filter.where('a', '==', 2), - Filter.where('b', 'not-in', [2, 3]), - ), - ) - .get(), - 'doc1', - 'doc2', - ); - }, - ); + it.skipClassic('supports OR queries with not-in', async () => { + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: 0}, + doc2: {b: 1}, + doc3: {a: 3, b: 2}, + doc4: {a: 1, b: 3}, + doc5: {a: 1}, + doc6: {a: 2}, + }); + // a==2 || (b != 2 && b != 3) + // Has implicit "orderBy b" + expectDocs( + await collection + .where( + Filter.or( + Filter.where('a', '==', 2), + Filter.where('b', 'not-in', [2, 3]), + ), + ) + .get(), + 'doc1', + 'doc2', + ); + }); it('supports OR queries with array membership', async () => { const collection = await testCollectionWithDocs({ @@ -3639,6 +3689,7 @@ describe('Query class', () => { Filter.where('b', 'array-contains', 7), ), ) + .orderBy(FieldPath.documentId()) .get(), 'doc3', 'doc4', @@ -3655,6 +3706,7 @@ describe('Query class', () => { Filter.where('b', 'array-contains-any', [0, 3]), ), ) + .orderBy(FieldPath.documentId()) .get(), 'doc1', 'doc4', @@ -3936,111 +3988,135 @@ describe('Query class', () => { unsubscribe(); }); - it('snapshot listener sorts query by DocumentId same way as server', async () => { - const batch = firestore.batch(); - batch.set(randomCol.doc('A'), {a: 1}); - batch.set(randomCol.doc('a'), {a: 1}); - batch.set(randomCol.doc('Aa'), {a: 1}); - batch.set(randomCol.doc('7'), {a: 1}); - batch.set(randomCol.doc('12'), {a: 1}); - batch.set(randomCol.doc('__id7__'), {a: 1}); - batch.set(randomCol.doc('__id12__'), {a: 1}); - batch.set(randomCol.doc('__id-2__'), {a: 1}); - batch.set(randomCol.doc('__id1_'), {a: 1}); - batch.set(randomCol.doc('_id1__'), {a: 1}); - batch.set(randomCol.doc('__id'), {a: 1}); - // largest long number - batch.set(randomCol.doc('__id9223372036854775807__'), {a: 1}); - batch.set(randomCol.doc('__id9223372036854775806__'), {a: 1}); - // smallest long number - batch.set(randomCol.doc('__id-9223372036854775808__'), {a: 1}); - batch.set(randomCol.doc('__id-9223372036854775807__'), {a: 1}); - await batch.commit(); + it.skipEnterprise( + 'snapshot listener sorts query by DocumentId same way as server', + async () => { + const batch = firestore.batch(); + batch.set(randomCol.doc('A'), {a: 1}); + batch.set(randomCol.doc('a'), {a: 1}); + batch.set(randomCol.doc('Aa'), {a: 1}); + batch.set(randomCol.doc('7'), {a: 1}); + batch.set(randomCol.doc('12'), {a: 1}); + batch.set(randomCol.doc('__id7__'), {a: 1}); + batch.set(randomCol.doc('__id12__'), {a: 1}); + batch.set(randomCol.doc('__id-2__'), {a: 1}); + batch.set(randomCol.doc('__id1_'), {a: 1}); + batch.set(randomCol.doc('_id1__'), {a: 1}); + batch.set(randomCol.doc('__id'), {a: 1}); + // largest long number + batch.set(randomCol.doc('__id9223372036854775807__'), {a: 1}); + batch.set(randomCol.doc('__id9223372036854775806__'), {a: 1}); + // smallest long number + batch.set(randomCol.doc('__id-9223372036854775808__'), {a: 1}); + batch.set(randomCol.doc('__id-9223372036854775807__'), {a: 1}); + await batch.commit(); - const query = randomCol.orderBy(FieldPath.documentId()); - const expectedDocs = [ - '__id-9223372036854775808__', - '__id-9223372036854775807__', - '__id-2__', - '__id7__', - '__id12__', - '__id9223372036854775806__', - '__id9223372036854775807__', - '12', - '7', - 'A', - 'Aa', - '__id', - '__id1_', - '_id1__', - 'a', - ]; + const query = randomCol.orderBy(FieldPath.documentId()); + const expectedDocs = isEnterprise() + ? [ + '12', + '7', + 'A', + 'Aa', + '__id', + '__id-2__', + '__id-9223372036854775807__', + '__id-9223372036854775808__', + '__id12__', + '__id1_', + '__id7__', + '__id9223372036854775806__', + '__id9223372036854775807__', + '_id1__', + 'a', + ] + : [ + '__id-9223372036854775808__', + '__id-9223372036854775807__', + '__id-2__', + '__id7__', + '__id12__', + '__id9223372036854775806__', + '__id9223372036854775807__', + '12', + '7', + 'A', + 'Aa', + '__id', + '__id1_', + '_id1__', + 'a', + ]; - const getSnapshot = await query.get(); - expect(getSnapshot.docs.map(d => d.id)).to.deep.equal(expectedDocs); + const getSnapshot = await query.get(); + expect(getSnapshot.docs.map(d => d.id)).to.deep.equal(expectedDocs); - const unsubscribe = query.onSnapshot(snapshot => - currentDeferred.resolve(snapshot), - ); + const unsubscribe = query.onSnapshot(snapshot => + currentDeferred.resolve(snapshot), + ); - const watchSnapshot = await waitForSnapshot(); - // Compare the snapshot (including sort order) of a snapshot - snapshotsEqual(watchSnapshot, { - docs: getSnapshot.docs, - docChanges: getSnapshot.docChanges(), - }); - unsubscribe(); - }); + const watchSnapshot = await waitForSnapshot(); + // Compare the snapshot (including sort order) of a snapshot + snapshotsEqual(watchSnapshot, { + docs: getSnapshot.docs, + docChanges: getSnapshot.docChanges(), + }); + unsubscribe(); + }, + ); - it('snapshot listener sorts filtered query by DocumentId same way as server', async () => { - const batch = firestore.batch(); - batch.set(randomCol.doc('A'), {a: 1}); - batch.set(randomCol.doc('a'), {a: 1}); - batch.set(randomCol.doc('Aa'), {a: 1}); - batch.set(randomCol.doc('7'), {a: 1}); - batch.set(randomCol.doc('12'), {a: 1}); - batch.set(randomCol.doc('__id7__'), {a: 1}); - batch.set(randomCol.doc('__id12__'), {a: 1}); - batch.set(randomCol.doc('__id-2__'), {a: 1}); - batch.set(randomCol.doc('__id1_'), {a: 1}); - batch.set(randomCol.doc('_id1__'), {a: 1}); - batch.set(randomCol.doc('__id'), {a: 1}); - // largest long number - batch.set(randomCol.doc('__id9223372036854775807__'), {a: 1}); - batch.set(randomCol.doc('__id9223372036854775806__'), {a: 1}); - // smallest long number - batch.set(randomCol.doc('__id-9223372036854775808__'), {a: 1}); - batch.set(randomCol.doc('__id-9223372036854775807__'), {a: 1}); - await batch.commit(); + it.skipEnterprise( + 'snapshot listener sorts filtered query by DocumentId same way as server', + async () => { + const batch = firestore.batch(); + batch.set(randomCol.doc('A'), {a: 1}); + batch.set(randomCol.doc('a'), {a: 1}); + batch.set(randomCol.doc('Aa'), {a: 1}); + batch.set(randomCol.doc('7'), {a: 1}); + batch.set(randomCol.doc('12'), {a: 1}); + batch.set(randomCol.doc('__id7__'), {a: 1}); + batch.set(randomCol.doc('__id12__'), {a: 1}); + batch.set(randomCol.doc('__id-2__'), {a: 1}); + batch.set(randomCol.doc('__id1_'), {a: 1}); + batch.set(randomCol.doc('_id1__'), {a: 1}); + batch.set(randomCol.doc('__id'), {a: 1}); + // largest long number + batch.set(randomCol.doc('__id9223372036854775807__'), {a: 1}); + batch.set(randomCol.doc('__id9223372036854775806__'), {a: 1}); + // smallest long number + batch.set(randomCol.doc('__id-9223372036854775808__'), {a: 1}); + batch.set(randomCol.doc('__id-9223372036854775807__'), {a: 1}); + await batch.commit(); - const query = randomCol - .where(FieldPath.documentId(), '>', '__id7__') - .where(FieldPath.documentId(), '<=', 'A') - .orderBy(FieldPath.documentId()); - const expectedDocs = [ - '__id12__', - '__id9223372036854775806__', - '__id9223372036854775807__', - '12', - '7', - 'A', - ]; + const query = randomCol + .where(FieldPath.documentId(), '>', '__id7__') + .where(FieldPath.documentId(), '<=', 'A') + .orderBy(FieldPath.documentId()); + const expectedDocs = [ + '__id12__', + '__id9223372036854775806__', + '__id9223372036854775807__', + '12', + '7', + 'A', + ]; - const getSnapshot = await query.get(); - expect(getSnapshot.docs.map(d => d.id)).to.deep.equal(expectedDocs); + const getSnapshot = await query.get(); + expect(getSnapshot.docs.map(d => d.id)).to.deep.equal(expectedDocs); - const unsubscribe = query.onSnapshot(snapshot => - currentDeferred.resolve(snapshot), - ); + const unsubscribe = query.onSnapshot(snapshot => + currentDeferred.resolve(snapshot), + ); - const watchSnapshot = await waitForSnapshot(); - // Compare the snapshot (including sort order) of a snapshot - snapshotsEqual(watchSnapshot, { - docs: getSnapshot.docs, - docChanges: getSnapshot.docChanges(), - }); - unsubscribe(); - }); + const watchSnapshot = await waitForSnapshot(); + // Compare the snapshot (including sort order) of a snapshot + snapshotsEqual(watchSnapshot, { + docs: getSnapshot.docs, + docChanges: getSnapshot.docChanges(), + }); + unsubscribe(); + }, + ); it('SDK orders vector field same way as backend', async () => { // We validate that the SDK orders the vector field the same way as the backend @@ -5787,7 +5863,11 @@ describe('Aggregation queries', () => { totalRating: AggregateField.sum('rating'), }) .get(); - expect(snapshot.data().totalRating).to.equal(0); + if (isEnterprise()) { + expect(snapshot.data().totalRating).to.equal(null); + } else { + expect(snapshot.data().totalRating).to.equal(0); + } }); it('performs sum only on numeric fields', async () => { @@ -7319,12 +7399,12 @@ describe('BulkWriter class', () => { await batch.commit(); }); - it('on top-level collection', async () => { + it.skipEnterprise('on top-level collection', async () => { await firestore.recursiveDelete(randomCol); expect(await countCollectionChildren(randomCol)).to.equal(0); }); - it('on nested collection', async () => { + it.skipEnterprise('on nested collection', async () => { const coll = randomCol.doc('bob').collection('parentsCol'); await firestore.recursiveDelete(coll); @@ -7332,7 +7412,8 @@ describe('BulkWriter class', () => { expect(await countCollectionChildren(randomCol)).to.equal(2); }); - it('on nested document', async () => { + // TODO enterprise waiting on b/469490062 + it.skip('on nested document', async () => { const doc = randomCol.doc('bob/parentsCol/daniel'); await firestore.recursiveDelete(doc); @@ -7342,7 +7423,8 @@ describe('BulkWriter class', () => { expect(await countCollectionChildren(randomCol)).to.equal(3); }); - it('on leaf document', async () => { + // TODO enterprise b/469490062 + it.skipEnterprise('on leaf document', async () => { const doc = randomCol.doc('bob/parentsCol/daniel/childCol/ernie'); await firestore.recursiveDelete(doc); @@ -7351,7 +7433,8 @@ describe('BulkWriter class', () => { expect(await countCollectionChildren(randomCol)).to.equal(5); }); - it('does not affect other collections', async () => { + // TODO enterprise b/469490062 + it.skipEnterprise('does not affect other collections', async () => { // Add other nested collection that shouldn't be deleted. const collB = firestore.collection('doggos'); await collB.doc('doggo').set({name: 'goodboi'}); @@ -7418,7 +7501,11 @@ describe('Client initialization', () => { ], [ 'CollectionReference.listDocuments()', - randomColl => randomColl.listDocuments(), + + randomColl => { + if (process.env.RUN_ENTERPRISE_TESTS) return Promise.resolve(); + return randomColl.listDocuments(); + }, ], [ 'CollectionReference.onSnapshot()', @@ -7455,7 +7542,11 @@ describe('Client initialization', () => { ['DocumentReference.delete()', randomColl => randomColl.doc().delete()], [ 'DocumentReference.listCollections()', - randomColl => randomColl.doc().listCollections(), + randomColl => { + // TODO enterprise waiting on b/469490062, skip for now + if (isEnterprise()) return Promise.resolve(); + return randomColl.doc().listCollections(); + }, ], [ 'DocumentReference.onSnapshot()', @@ -7471,6 +7562,9 @@ describe('Client initialization', () => { [ 'CollectionGroup.getPartitions()', async randomColl => { + // Requires PartitionQuery support + if (process.env.RUN_ENTERPRISE_TESTS) return; + const partitions = randomColl.firestore .collectionGroup('id') .getPartitions(2); diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts new file mode 100644 index 000000000..851ce1d6c --- /dev/null +++ b/dev/system-test/pipeline.ts @@ -0,0 +1,5169 @@ +// 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 { + DocumentData, + DocumentReference, + Pipelines, +} from '@google-cloud/firestore'; + +import { + map, + array, + field, + ceil, + floor, + exp, + xor, + AggregateFunction, + arrayGet, + timestampToUnixMicros, + timestampToUnixSeconds, + unixMicrosToTimestamp, + timestampToUnixMillis, + timestampSubtract, + timestampAdd, + byteLength, + multiply, + sum, + maximum, + descending, + FunctionExpression, + minimum, + count, + countIf, + arrayLength, + stringContains, + charLength, + divide, + mod, + reverse, + trim, + toUpper, + toLower, + vectorLength, + exists, + isAbsent, + ifError, + isError, + substring, + documentId, + arrayContainsAll, + mapRemove, + mapMerge, + unixSecondsToTimestamp, + unixMillisToTimestamp, + add, + and, + arrayContains, + arrayContainsAny, + arrayReverse, + average, + countAll, + endsWith, + equal, + greaterThan, + like, + lessThan, + notEqual, + ascending, + not, + or, + regexContains, + regexMatch, + startsWith, + stringConcat, + subtract, + cosineDistance, + dotProduct, + euclideanDistance, + mapGet, + lessThanOrEqual, + equalAny, + notEqualAny, + logicalMinimum, + logicalMaximum, + conditional, + constant, + PipelineResult, + PipelineSnapshot, + Pipeline, + countDistinct, + pow, + round, + collectionId, + length, + ln, + sqrt, + stringReverse, + abs, + log10, + concat, + ifAbsent, + join, + arraySum, + currentTimestamp, + arrayConcat, + type, + timestampTruncate, + split, + // TODO(new-expression): add new expression imports above this line +} from '../src/pipelines'; + +import { + Timestamp, + GeoPoint, + Filter, + FieldValue, + CollectionReference, + FieldPath, + Firestore, +} from '../src'; + +import {expect, use} from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import {afterEach, describe, it} from 'mocha'; +import '../test/util/mocha_extensions'; +import {verifyInstance} from '../test/util/helpers'; +import {getTestDb, getTestRoot} from './firestore'; + +import {Firestore as InternalFirestore} from '../src'; +import {ServiceError} from 'google-gax'; + +use(chaiAsPromised); + +const timestampDeltaMS = 3000; + +describe.skipClassic('Pipeline class', () => { + let firestore: Firestore; + let randomCol: CollectionReference; + let beginDocCreation = 0; + let endDocCreation = 0; + + async function testCollectionWithDocs(docs: { + [id: string]: DocumentData; + }): Promise> { + beginDocCreation = new Date().valueOf(); + for (const id in docs) { + const ref = randomCol.doc(id); + await ref.set(docs[id]); + } + endDocCreation = new Date().valueOf(); + return randomCol; + } + + function expectResults(result: PipelineSnapshot, ...docs: string[]): void; + function expectResults( + result: PipelineSnapshot, + ...data: DocumentData[] + ): void; + function expectResults( + result: PipelineSnapshot, + ...data: DocumentData[] | string[] + ): void { + if (data.length > 0) { + if (typeof data[0] === 'string') { + const actualIds = result.results.map(result => result.id); + expect(actualIds).to.deep.equal(data); + } else { + result.results.forEach(r => { + expect(r.data()).to.deep.equal(data.shift()); + }); + } + } else { + expect(result.results.length).to.equal(data.length); + } + } + + 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: FieldValue.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: FieldValue.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: FieldValue.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: FieldValue.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: FieldValue.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: FieldValue.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: FieldValue.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: FieldValue.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: FieldValue.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: FieldValue.vector([1, 1, 1, 1, 1, 1, 1, 1, 1, 10]), + }, + }; + return testCollectionWithDocs(bookDocs); + } + + before(async () => { + randomCol = getTestRoot(); + await setupBookDocs(); + firestore = randomCol.firestore; + }); + + afterEach(() => verifyInstance(firestore as unknown as InternalFirestore)); + + describe('pipeline results', () => { + it('empty snapshot as expected', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .limit(0) + .execute(); + 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 ppl.execute(); + expect(snapshot.results.length).to.equal(10); + expect(snapshot.pipeline).to.equal(ppl); + 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 ppl.execute(); + const snapshot2 = await ppl.execute(); + expect(snapshot1.results.length).to.equal(1); + expect(snapshot2.results.length).to.equal(1); + expect(snapshot1.results[0].isEqual(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 pipeline.execute(); + 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 pipeline.execute(); + 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 pipeline.execute(); + 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 = firestore.batch(); + snapshot.results.forEach(doc => { + wb.update(doc.ref!, {newField: 'value'}); + }); + await wb.commit(); + + snapshot = await pipeline.execute(); + 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 pipeline.execute(); + 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 pipeline.execute(); + + 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 explain', () => { + it('mode: analyze, format: text', async () => { + const ppl = firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('__name__')); + + const snapshot = await ppl.execute({ + explainOptions: { + mode: 'analyze', + outputFormat: 'text', + }, + }); + + expect(snapshot.explainStats).not.to.be.undefined; + expect(snapshot.explainStats!.text.length).to.be.greaterThan(0); + expect(snapshot.explainStats!.text.charAt(0)).not.to.equal('{'); + + expect(snapshot.explainStats!.rawData.type_url).to.equal( + 'type.googleapis.com/google.protobuf.StringValue', + ); + expect(snapshot.explainStats!.rawData.value).to.not.be.null; + expect(snapshot.explainStats!.rawData.value).to.not.be.undefined; + + expect(snapshot.results.length).to.equal(10); + expect(snapshot.pipeline).to.equal(ppl); + expectResults( + snapshot, + 'book1', + 'book10', + 'book2', + 'book3', + 'book4', + 'book5', + 'book6', + 'book7', + 'book8', + 'book9', + ); + }); + + it('mode: analyze, format: unspecified', async () => { + const ppl = firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('__name__')); + const snapshot = await ppl.execute({ + explainOptions: { + mode: 'analyze', + }, + }); + expect(snapshot.explainStats).not.to.be.undefined; + expect(snapshot.explainStats!.text.length).to.be.greaterThan(0); + expect(snapshot.explainStats!.text.charAt(0)).not.to.equal('{'); + + expect(snapshot.explainStats!.rawData.type_url).to.equal( + 'type.googleapis.com/google.protobuf.StringValue', + ); + expect(snapshot.explainStats!.rawData.value).to.not.be.null; + expect(snapshot.explainStats!.rawData.value).to.not.be.undefined; + + expect(snapshot.results.length).to.equal(10); + expect(snapshot.pipeline).to.equal(ppl); + expectResults( + snapshot, + 'book1', + 'book10', + 'book2', + 'book3', + 'book4', + 'book5', + 'book6', + 'book7', + 'book8', + 'book9', + ); + }); + + it('mode: execute, format: text', async () => { + const ppl = firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('__name__')); + const snapshot = await ppl.execute({ + explainOptions: { + mode: 'execute', + outputFormat: 'text', + }, + }); + expect(snapshot.explainStats).to.be.undefined; + + expect(snapshot.results.length).to.equal(10); + expect(snapshot.pipeline).to.equal(ppl); + expectResults( + snapshot, + 'book1', + 'book10', + 'book2', + 'book3', + 'book4', + 'book5', + 'book6', + 'book7', + 'book8', + 'book9', + ); + }); + + it('mode: unspecified, format: text', async () => { + const ppl = firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('__name__')); + const snapshot = await ppl.execute({ + explainOptions: { + mode: undefined, + }, + }); + expect(snapshot.explainStats).to.be.undefined; + + expect(snapshot.results.length).to.equal(10); + expect(snapshot.pipeline).to.equal(ppl); + expectResults( + snapshot, + 'book1', + 'book10', + 'book2', + 'book3', + 'book4', + 'book5', + 'book6', + 'book7', + 'book8', + 'book9', + ); + }); + }); + + describe('pipeline sources', () => { + it('supports CollectionReference as source', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol) + .execute(); + expect(snapshot.results.length).to.equal(10); + }); + + it('supports list of documents as source', async () => { + const collName = randomCol.id; + + const snapshot = await firestore + .pipeline() + .documents([ + `${collName}/book1`, + randomCol.doc('book2'), + randomCol.doc('book3').path, + ]) + .execute(); + expect(snapshot.results.length).to.equal(3); + }); + + it('reject CollectionReference for another DB', async () => { + const db2 = getTestDb({databaseId: 'notDefault', projectId: 'random'}); + + expect(() => { + firestore.pipeline().collection(db2.collection('foo')); + }).to.throw(/Invalid CollectionReference/); + + await db2.terminate(); + }); + + it('reject DocumentReference for another DB', async () => { + const db2 = getTestDb({databaseId: 'notDefault', projectId: 'random'}); + + expect(() => { + firestore.pipeline().documents([db2.doc('foo/bar')]); + }).to.throw(/Invalid DocumentReference/); + + await db2.terminate(); + }); + + it('supports collection group as source', async () => { + const randomSubCollectionId = Math.random().toString(16).slice(2); + const doc1 = await randomCol + .doc('book1') + .collection(randomSubCollectionId) + .add({order: 1}); + const doc2 = await randomCol + .doc('book2') + .collection(randomSubCollectionId) + .add({order: 2}); + const snapshot = await firestore + .pipeline() + .collectionGroup(randomSubCollectionId) + .sort(ascending('order')) + .execute(); + expectResults(snapshot, doc1.id, doc2.id); + }); + + it('supports database as source', async () => { + const randomId = Math.random().toString(16).slice(2); + const doc1 = await randomCol.doc('book1').collection('sub').add({ + order: 1, + randomId, + }); + const doc2 = await randomCol.doc('book2').collection('sub').add({ + order: 2, + randomId, + }); + const snapshot = await firestore + .pipeline() + .database() + .where(equal('randomId', randomId)) + .sort(ascending('order')) + .execute(); + 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(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])).as('bytes'), + constant(firestore.doc('foo/bar')).as('documentReference'), + constant(FieldValue.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: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]), + documentReference: firestore.doc('foo/bar'), + vectorValue: FieldValue.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, + new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]), + firestore.doc('foo/bar'), + FieldValue.vector([1, 2, 3]), + { + number: 2, + string: 'b string', + }, + ]).as('array'), + ]; + + const snapshots = await firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(constants[0], ...constants.slice(1)) + .execute(); + + 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: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]), + documentReference: firestore.collection('foo').doc('bar'), + vectorValue: FieldValue.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: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]), + documentReference: firestore.collection('foo').doc('bar'), + vectorValue: FieldValue.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), + new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]), + firestore.collection('foo').doc('bar'), + FieldValue.vector([1, 2, 3]), + { + number: 2, + string: 'b string', + }, + ], + }); + }); + + it('throws on undefined in a map', async () => { + try { + await firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + map({ + number: 1, + bad: undefined, + }).as('foo'), + ) + .execute(); + expect.fail('The statement above was expected to throw.'); + } catch (e: unknown) { + const error = e as Error; + console.log(error.message); + expect(error.message).to.contain( + 'Value for argument "value" is not a valid map value. Cannot use "undefined" as a Firestore value (found in field "bad").', + ); + } + }); + + it('throws on undefined in an array', async () => { + try { + await firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(array([1, undefined]).as('foo')) + .execute(); + expect.fail('The statement above was expected to throw.'); + } catch (e: unknown) { + const error = e as Error; + console.log(error.message); + expect(error.message).to.contain( + 'Value for argument "value" is not a valid array value. Cannot use "undefined" as a Firestore value', + ); + } + }); + + it('ignores undefined in a map if ignoreUndefinedProperties is true', async () => { + const customFirestore = getTestDb({ + ignoreUndefinedProperties: true, + }); + + const snapshot = await customFirestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + map({ + number: 1, + bad: undefined, + }).as('foo'), + ) + .execute(); + + const data = snapshot.results[0].data(); + expect(data).to.deep.equal({foo: {number: 1}}); + await customFirestore.terminate(); + }); + + it('ignores undefined in an array if ignoreUndefinedProperties is true', async () => { + const customFirestore = getTestDb({ + ignoreUndefinedProperties: true, + }); + + const snapshot = await customFirestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(array([1, undefined, 3]).as('foo')) + .execute(); + + const data = snapshot.results[0].data(); + expect(data).to.deep.equal({foo: [1, 3]}); + await customFirestore.terminate(); + }); + + it('converts arrays and plain objects to functionValues if the customer intent is unspecified', async () => { + const snapshot = await 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'), + }, + }), + ), + ) + .execute(); + + 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, + }, + }, + }); + }); + }); + + describe('stages', () => { + describe('aggregate stage', () => { + it('supports aggregate', async () => { + let snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countAll().as('count')) + .execute(); + expectResults(snapshot, {count: 10}); + + snapshot = await 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'), + ) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .aggregate({ + accumulators: [countAll().as('count')], + }) + .execute(); + expectResults(snapshot, {count: 10}); + + snapshot = await 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'), + ) + .execute(); + expectResults(snapshot, { + count: 2, + avgRating: 4.4, + maxRating: 4.6, + sumRating: 8.8, + }); + }); + + it('rejects groups without accumulators', async () => { + void expect(async () => { + await firestore + .pipeline() + .collection(randomCol.path) + .where(lessThan('published', 1900)) + .aggregate({ + accumulators: [], + groups: ['genre'], + }) + .execute(); + }).to.throw; + }); + + it('returns group and accumulate results', async () => { + const snapshot = await 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()) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .aggregate( + count('cost').as('booksWithCost'), + countAll().as('count'), + maximum('rating').as('maxRating'), + minimum('published').as('minPublished'), + ) + .execute(); + expectResults(snapshot, { + booksWithCost: 1, + count: 10, + maxRating: 4.7, + minPublished: 1813, + }); + }); + + it('returns countif accumulation', async () => { + let snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countIf(field('rating').greaterThan(4.3)).as('count')) + .execute(); + const expectedResults = { + count: 3, + }; + expectResults(snapshot, expectedResults); + + snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .aggregate(field('rating').greaterThan(4.3).countIf().as('count')) + .execute(); + expectResults(snapshot, expectedResults); + }); + + it('returns countDistinct accumulation', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countDistinct('genre').as('distinctGenres')) + .execute(); + expectResults(snapshot, {distinctGenres: 8}); + }); + }); + + describe('distinct stage', () => { + it('returns distinct values as expected', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .distinct('genre', 'author') + .sort(field('genre').ascending(), field('author').ascending()) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .distinct('genre', 'author') + .sort({ + orderings: [ + field('genre').ascending(), + field('author').ascending(), + ], + }) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .sort(field('author').ascending()) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .select({selections: ['title', field('author').as('auth0r')]}) + .sort(field('auth0r').ascending()) + .limit(2) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .addFields(constant('bar').as('foo')) + .sort(field('author').ascending()) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .addFields({ + fields: [constant('bar').as('foo')], + }) + .sort(field('author').ascending()) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .sort(field('author').ascending()) + .removeFields(field('author')) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'genre') + .sort(field('author').ascending()) + .removeFields({ + fields: [field('author'), 'genre'], + }) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .sort(field('author').ascending()) + .removeFields(field('author')) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'genre') + .sort(field('author').ascending()) + .removeFields({ + fields: [field('author'), 'genre'], + }) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .where( + and( + greaterThan('rating', 4.5), + equalAny('genre', ['Science Fiction', 'Romance', 'Fantasy']), + ), + ) + .execute(); + expectResults(snapshot, 'book10', 'book4'); + }); + + it('where with and (3 conditions)', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where( + and( + greaterThan('rating', 4.5), + equalAny('genre', ['Science Fiction', 'Romance', 'Fantasy']), + lessThan('published', 1965), + ), + ) + .execute(); + expectResults(snapshot, 'book4'); + }); + + it('where with or', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where( + or( + equal('genre', 'Romance'), + equal('genre', 'Dystopian'), + equal('genre', 'Fantasy'), + ), + ) + .sort(ascending('title')) + .select('title') + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .where( + xor( + equal('genre', 'Romance'), + equal('genre', 'Dystopian'), + equal('genre', 'Fantasy'), + equal('published', 1949), + ), + ) + .select('title') + .execute(); + expectResults( + snapshot, + {title: 'Pride and Prejudice'}, + {title: 'The Lord of the Rings'}, + {title: "The Handmaid's Tale"}, + ); + }); + + it('supports options', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where({ + condition: and( + greaterThan('rating', 4.5), + equalAny('genre', ['Science Fiction', 'Romance', 'Fantasy']), + ), + }) + .execute(); + expectResults(snapshot, 'book10', 'book4'); + }); + }); + + describe('sort, offset, and limit stages', () => { + it('supports sort, offset, and limits', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('author').ascending()) + .offset(5) + .limit(3) + .select('title', 'author') + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .sort({ + orderings: [field('author').ascending()], + }) + .offset({offset: 5}) + .limit({limit: 3}) + .select('title', 'author') + .execute(); + 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('error handling', () => { + it('error properties are propagated from the firestore backend', async () => { + try { + await firestore + .pipeline() + .collection(randomCol.path) + .rawStage('select', [ + // incorrect parameter type + field('title'), + ]) + .execute(); + + expect.fail('expected pipeline.execute() to throw'); + } catch (e: unknown) { + expect(e instanceof Error).to.be.true; + const err = e as ServiceError; + expect(err['code']).to.equal(3); + expect(typeof err['message']).to.equal('string'); + expect(typeof err['details']).to.equal('string'); + expect(typeof err['stack']).to.equal('string'); + expect(err['metadata'] instanceof Object).to.be.true; + + expect(err['message']).to.equal( + `${err.code} INVALID_ARGUMENT: ${err.details}`, + ); + } + }); + + it('can access explain stats in error condition', async () => { + try { + await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').ascending()) + .execute({ + explainOptions: { + mode: 'analyze', + }, + rawOptions: { + memory_limit: 1, + }, + }); + + expect.fail('expected pipeline.execute() to throw'); + } catch (e: unknown) { + const err = e as {[k: string]: unknown}; + expect(err instanceof Error).to.be.true; + + expect(err['code']).to.equal(8); + expect(typeof err['message']).to.equal('string'); + expect(typeof err['details']).to.equal('string'); + expect(typeof err['stack']).to.equal('string'); + expect(err['metadata'] instanceof Object).to.be.true; + + expect(err['message']).to.equal( + `${err.code} RESOURCE_EXHAUSTED: ${err.details}`, + ); + + expect('statusDetails' in err).to.be.true; + expect(Array.isArray(err['statusDetails'])).to.be.true; + + const statusDetails = err['statusDetails'] as Array; + + const foundExplainStats = statusDetails.find(x => { + return ( + 'type_url' in x && + x['type_url'] === + 'type.googleapis.com/google.firestore.v1.ExplainStats' + ); + }); + expect(foundExplainStats).to.not.be.undefined; + } + }); + }); + + describe('raw stage', () => { + it('can select fields', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .rawStage('select', [ + { + title: field('title'), + metadata: { + author: field('author'), + }, + }, + ]) + .sort(field('author').ascending()) + .limit(1) + .execute(); + expectResults(snapshot, { + metadata: { + author: 'Frank Herbert', + }, + title: 'Dune', + }); + }); + + it('can add fields', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('author').ascending()) + .limit(1) + .select('title', 'author') + .rawStage('add_fields', [ + { + display: stringConcat('title', ' - ', field('author')), + }, + ]) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .rawStage('where', [field('author').equal('Douglas Adams')]) + .execute(); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + }); + }); + + it('can limit, offset, and sort', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .rawStage('sort', [ + { + direction: 'ascending', + expression: field('author'), + }, + ]) + .rawStage('offset', [3]) + .rawStage('limit', [1]) + .execute(); + expectResults(snapshot, { + author: 'Fyodor Dostoevsky', + title: 'Crime and Punishment', + }); + }); + + it('can perform aggregate query', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'rating') + .rawStage('aggregate', [ + {averageRating: field('rating').average()}, + {}, + ]) + .execute(); + expectResults(snapshot, { + averageRating: 4.3100000000000005, + }); + }); + + it('can perform distinct query', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'rating') + .rawStage('distinct', [{rating: field('rating')}]) + .sort(field('rating').descending()) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol) + .rawStage( + 'find_nearest', + [ + field('embedding'), + FieldValue.vector([10, 1, 2, 1, 1, 1, 1, 1, 1, 1]), + 'euclidean', + ], + { + distance_field: field('computedDistance'), + limit: 2, + }, + ) + .select('title', 'computedDistance') + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .replaceWith('awards') + .execute(); + expectResults(snapshot, { + hugo: true, + nebula: false, + others: {unknown: {year: 1980}}, + }); + }); + + it('run pipeline with replaceWith Expression result', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .replaceWith( + map({ + foo: 'bar', + baz: { + title: field('title'), + }, + }), + ) + .execute(); + expectResults(snapshot, { + foo: 'bar', + baz: {title: "The Hitchhiker's Guide to the Galaxy"}, + }); + }); + + it('supports options', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .replaceWith({map: 'awards'}) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .sample(3) + .execute(); + expect(snapshot.results.length).to.equal(3); + }); + + it('run pipeline with sample limit of {documents: 3}', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sample({documents: 3}) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .sample({percentage: 0.6}) + .execute(); + + 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 firestore + .pipeline() + .collection(randomCol.path) + .union(firestore.pipeline().collection(randomCol.path)) + .sort(field(FieldPath.documentId()).ascending()) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .union({other: firestore.pipeline().collection(randomCol.path)}) + .sort(field(FieldPath.documentId()).ascending()) + .execute(); + 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 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', + ) + .execute(); + 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 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', + ) + .execute(); + 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 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', + ) + .execute(); + 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 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', + ) + .execute(); + 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< + Pipelines.FindNearestStageOptions['distanceMeasure'] + > = ['euclidean', 'dot_product', 'cosine']; + for (const measure of measures) { + const snapshot = await firestore + .pipeline() + .collection(randomCol) + .findNearest({ + field: 'embedding', + vectorValue: FieldValue.vector([10, 1, 3, 1, 2, 1, 1, 1, 1, 1]), + limit: 3, + distanceMeasure: measure, + }) + .select('title') + .execute(); + 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 firestore + .pipeline() + .collection(randomCol) + .findNearest({ + field: 'embedding', + vectorValue: FieldValue.vector([10, 1, 2, 1, 1, 1, 1, 1, 1, 1]), + limit: 2, + distanceMeasure: 'euclidean', + distanceField: 'computedDistance', + }) + .select('title', 'computedDistance') + .execute(); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + computedDistance: 1, + }, + { + title: 'One Hundred Years of Solitude', + computedDistance: 12.041594578792296, + }, + ); + }); + }); + }); + + describe('function expressions', () => { + it('logical max works', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .select( + 'title', + logicalMaximum(constant(1960), field('published'), 1961).as( + 'published-safe', + ), + ) + .sort(field('title').ascending()) + .limit(3) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .select( + 'title', + logicalMinimum(constant(1960), field('published'), 1961).as( + 'published-safe', + ), + ) + .sort(field('title').ascending()) + .limit(3) + .execute(); + 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 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) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .where(equalAny('published', [1979, 1999, 1967])) + .sort(descending('title')) + .select('title') + .execute(); + expectResults( + snapshot, + {title: "The Hitchhiker's Guide to the Galaxy"}, + {title: 'One Hundred Years of Solitude'}, + ); + }); + + it('notEqualAny works', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where( + notEqualAny( + 'published', + [1965, 1925, 1949, 1960, 1866, 1985, 1954, 1967, 1979], + ), + ) + .select('title') + .execute(); + expectResults(snapshot, {title: 'Pride and Prejudice'}); + }); + + it('arrayContains works', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(arrayContains('tags', 'comedy')) + .select('title') + .execute(); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy", + }); + }); + + it('arrayContainsAny works', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(arrayContainsAny('tags', ['comedy', 'classic'])) + .sort(descending('title')) + .select('title') + .execute(); + expectResults( + snapshot, + {title: "The Hitchhiker's Guide to the Galaxy"}, + {title: 'Pride and Prejudice'}, + ); + }); + + it('arrayContainsAll works', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(arrayContainsAll('tags', ['adventure', 'magic'])) + .select('title') + .execute(); + expectResults(snapshot, {title: 'The Lord of the Rings'}); + }); + + it('arrayLength works', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .select(arrayLength('tags').as('tagsCount')) + .where(equal('tagsCount', 3)) + .execute(); + expect(snapshot.results.length).to.equal(10); + }); + + it('testStrConcat', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('author')) + .select( + field('author').stringConcat(' - ', field('title')).as('bookInfo'), + ) + .limit(1) + .execute(); + expectResults(snapshot, { + bookInfo: "Douglas Adams - The Hitchhiker's Guide to the Galaxy", + }); + }); + + it('testStartsWith', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(startsWith('title', 'The')) + .select('title') + .sort(field('title').ascending()) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .where(endsWith('title', 'y')) + .select('title') + .sort(field('title').descending()) + .execute(); + expectResults( + snapshot, + {title: "The Hitchhiker's Guide to the Galaxy"}, + {title: 'The Great Gatsby'}, + ); + }); + + it('testStrContains', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(stringContains('title', "'s")) + .select('title') + .sort(field('title').ascending()) + .execute(); + expectResults( + snapshot, + {title: "The Handmaid's Tale"}, + {title: "The Hitchhiker's Guide to the Galaxy"}, + ); + }); + + it('testLength', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .select(charLength('title').as('titleLength'), field('title')) + .where(greaterThan('titleLength', 20)) + .sort(field('title').ascending()) + .execute(); + + 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 firestore + .pipeline() + .collection(randomCol.path) + .where(like('title', '%Guide%')) + .select('title') + .execute(); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy", + }); + }); + + it('testRegexContains', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(regexContains('title', '(?i)(the|of)')) + .execute(); + expect(snapshot.results.length).to.equal(5); + }); + + it('testRegexMatches', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(regexMatch('title', '.*(?i)(the|of).*')) + .execute(); + expect(snapshot.results.length).to.equal(5); + }); + + it('testArithmeticOperations', async () => { + const snapshot = await 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) + .execute(); + 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 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()) + .execute(); + 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 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()) + .execute(); + expectResults( + snapshot, + {title: 'Crime and Punishment'}, + {title: 'Dune'}, + {title: 'Pride and Prejudice'}, + ); + }); + + it('testChecks', async () => { + let snapshot = await 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'), + ) + .execute(); + 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 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'), + ) + .execute(); + 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 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)) + .execute(); + 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 = FieldValue.vector([0.1, 0.1]); + const targetVector = FieldValue.vector([0.5, 0.8]); + let snapshot = await 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) + .execute(); + + expectResults(snapshot, { + cosineDistance: 0.02560880430538015, + dotProductDistance: 0.13, + euclideanDistance: 0.806225774829855, + }); + + snapshot = await 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) + .execute(); + + expectResults(snapshot, { + cosineDistance: 0.02560880430538015, + dotProductDistance: 0.13, + euclideanDistance: 0.806225774829855, + }); + }); + + it('testVectorLength', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + vectorLength(constant(FieldValue.vector([1, 2, 3]))).as( + 'vectorLength', + ), + ) + .execute(); + expectResults(snapshot, { + vectorLength: 3, + }); + }); + + it('testNestedFields', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(equal('awards.hugo', true)) + .sort(descending('title')) + .select('title', 'awards.hugo') + .execute(); + 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 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'), + ) + .execute(); + + expectResults(snapshot, { + title: 'foo', + 'nested.level.`1`': 'bar', + nested: 'baz', + }); + }); + + describe('genericFunction', () => { + it('add selectable', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(descending('rating')) + .limit(1) + .select( + new FunctionExpression('add', [field('rating'), constant(1)]).as( + 'rating', + ), + ) + .execute(); + expectResults(snapshot, { + rating: 5.7, + }); + }); + + it('and (variadic) selectable', async () => { + const snapshot = await 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') + .execute(); + expectResults(snapshot, { + title: '1984', + }); + }); + + it('array contains any', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where( + new FunctionExpression('array_contains_any', [ + field('tags'), + array(['politics']), + ]).asBoolean(), + ) + .select('title') + .execute(); + expectResults(snapshot, { + title: 'Dune', + }); + }); + + it('countif aggregate', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .aggregate( + new AggregateFunction('count_if', [ + field('rating').greaterThanOrEqual(4.5), + ]).as('countOfBest'), + ) + .execute(); + expectResults(snapshot, { + countOfBest: 3, + }); + }); + + it('sort by char_len', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort( + new FunctionExpression('char_length', [field('title')]).ascending(), + descending('__name__'), + ) + .limit(3) + .select('title') + .execute(); + expectResults( + snapshot, + { + title: '1984', + }, + { + title: 'Dune', + }, + { + title: 'The Great Gatsby', + }, + ); + }); + }); + + it('supports array', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(array([1, 2, 3, 4]).as('metadata')) + .execute(); + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: [1, 2, 3, 4], + }); + }); + + it('evaluates expression in array', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + array([1, 2, field('genre'), multiply('rating', 10)]).as('metadata'), + ) + .execute(); + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: [1, 2, 'Fantasy', 47], + }); + }); + + it('supports arrayGet', async () => { + let snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(3) + .select(arrayGet('tags', 0).as('firstTag')) + .execute(); + const expectedResults = [ + { + firstTag: 'adventure', + }, + { + firstTag: 'politics', + }, + { + firstTag: 'classic', + }, + ]; + expectResults(snapshot, ...expectedResults); + + snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(3) + .select(field('tags').arrayGet(0).as('firstTag')) + .execute(); + expectResults(snapshot, ...expectedResults); + }); + + it('supports map', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + map({ + foo: 'bar', + }).as('metadata'), + ) + .execute(); + + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: { + foo: 'bar', + }, + }); + }); + + it('evaluates expression in map', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + map({ + genre: field('genre'), + rating: field('rating').multiply(10), + }).as('metadata'), + ) + .execute(); + + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: { + genre: 'Fantasy', + rating: 47, + }, + }); + }); + + it('supports mapRemove', async () => { + let snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(mapRemove('awards', 'hugo').as('awards')) + .execute(); + expectResults(snapshot, { + awards: {nebula: false}, + }); + snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('awards').mapRemove('hugo').as('awards')) + .execute(); + expectResults(snapshot, { + awards: {nebula: false}, + }); + }); + + it('supports mapMerge', async () => { + let snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(mapMerge('awards', {fakeAward: true}).as('awards')) + .execute(); + expectResults(snapshot, { + awards: {nebula: false, hugo: false, fakeAward: true}, + }); + snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('awards').mapMerge({fakeAward: true}).as('awards')) + .execute(); + expectResults(snapshot, { + awards: {nebula: false, hugo: false, fakeAward: true}, + }); + }); + + it('supports timestamp conversions', async () => { + const snapshot = await 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'), + ) + .execute(); + 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 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'), + ) + .execute(); + 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), + }); + }); + + it('supports byteLength', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol) + .limit(1) + .select(constant(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])).as('bytes')) + .select(byteLength('bytes').as('byteLength')) + .execute(); + + expectResults(snapshot, { + byteLength: 8, + }); + }); + + it('supports not', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol) + .limit(1) + .select(constant(true).as('trueField')) + .select('trueField', not(equal('trueField', true)).as('falseField')) + .execute(); + + expectResults(snapshot, { + trueField: true, + falseField: false, + }); + }); + + it('can reverse an array', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('tags').arrayReverse().as('reversedTags')) + .execute(); + expectResults(snapshot, { + reversedTags: ['adventure', 'space', 'comedy'], + }); + }); + + it('can reverse an array with the top-level function', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(arrayReverse('tags').as('reversedTags')) + .execute(); + expectResults(snapshot, { + reversedTags: ['adventure', 'space', 'comedy'], + }); + }); + + it('can compute the ceiling of a numeric value', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').ceil().as('ceilingRating')) + .execute(); + expectResults(snapshot, { + ceilingRating: 5, + }); + }); + + it('can compute the ceiling of a numeric value with the top-level function', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(ceil('rating').as('ceilingRating')) + .execute(); + expectResults(snapshot, { + ceilingRating: 5, + }); + }); + + it('can compute the floor of a numeric value', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').floor().as('floorRating')) + .execute(); + expectResults(snapshot, { + floorRating: 4, + }); + }); + + it('can compute the floor of a numeric value with the top-level function', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(floor('rating').as('floorRating')) + .execute(); + expectResults(snapshot, { + floorRating: 4, + }); + }); + + it('can compute e to the power of a numeric value', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(field('rating').exp().as('expRating')) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(exp('rating').as('expRating')) + .execute(); + 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 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')) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(pow('rating', 2).as('powerRating')) + .execute(); + expect(snapshot.results[0].get('powerRating')).to.be.approximately( + 17.64, + 0.0001, + ); + }); + + it('can round a numeric value', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').round().as('roundedRating')) + .execute(); + expectResults(snapshot, { + roundedRating: 4, + }); + }); + + it('can round a numeric value with the top-level function', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(round('rating').as('roundedRating')) + .execute(); + expectResults(snapshot, { + roundedRating: 4, + }); + }); + + it('can round a numeric value away from zero for positive half-way values', async () => { + const snapshot = await 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')) + .execute(); + expectResults(snapshot, { + roundedRating: 2, + }); + }); + + it('can round a numeric value away from zero for negative half-way values', async () => { + const snapshot = await 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')) + .execute(); + expectResults(snapshot, { + roundedRating: -2, + }); + }); + + it('can round a numeric value to specified precision', async () => { + const snapshot = await 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'), + ) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(field('__name__').collectionId().as('collectionId')) + .execute(); + expectResults(snapshot, { + collectionId: randomCol.id, + }); + }); + + it('can get the collectionId from a path with the top-level function', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(collectionId('__name__').as('collectionId')) + .execute(); + expectResults(snapshot, { + collectionId: randomCol.id, + }); + }); + + it('can compute the length of a string value', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('title').length().as('titleLength')) + .execute(); + expectResults(snapshot, { + titleLength: 36, + }); + }); + + it('can compute the length of a string value with the top-level function', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(length('title').as('titleLength')) + .execute(); + expectResults(snapshot, { + titleLength: 36, + }); + }); + + it('can compute the length of an array value', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('tags').length().as('tagsLength')) + .execute(); + expectResults(snapshot, { + tagsLength: 3, + }); + }); + + it('can compute the length of an array value with the top-level function', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(length('tags').as('tagsLength')) + .execute(); + expectResults(snapshot, { + tagsLength: 3, + }); + }); + + it('can compute the length of a map value', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('awards').length().as('awardsLength')) + .execute(); + expectResults(snapshot, { + awardsLength: 3, + }); + }); + + it('can compute the length of a vector value', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('embedding').length().as('embeddingLength')) + .execute(); + expectResults(snapshot, { + embeddingLength: 10, + }); + }); + + it('can compute the length of a bytes value', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .select(constant('12é').as('value')) + .limit(1) + .select(field('value').byteLength().as('valueLength')) + .execute(); + expectResults(snapshot, { + valueLength: 4, + }); + }); + + it('can compute the natural logarithm of a numeric value', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').ln().as('lnRating')) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(ln('rating').as('lnRating')) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(ln('rating').as('lnRating')) + .execute(); + expectResults(snapshot, { + lnRating: 1.4350845252893227, + }); + }); + + it('can round a numeric value', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').round().as('roundedRating')) + .execute(); + expectResults(snapshot, { + roundedRating: 4, + }); + }); + + it('can round a numeric value with the top-level function', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(round('rating').as('roundedRating')) + .execute(); + expectResults(snapshot, { + roundedRating: 4, + }); + }); + + it('can compute the square root of a numeric value', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').sqrt().as('sqrtRating')) + .execute(); + expectResults(snapshot, { + sqrtRating: 2.04939015319192, + }); + }); + + it('can compute the square root of a numeric value with the top-level function', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(sqrt('rating').as('sqrtRating')) + .execute(); + expectResults(snapshot, { + sqrtRating: 2.04939015319192, + }); + }); + + it('can reverse a string', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('title').stringReverse().as('reversedTitle')) + .execute(); + expectResults(snapshot, { + reversedTitle: "yxalaG eht ot ediuG s'rekihhctiH ehT", + }); + }); + + it('can reverse a string with the top-level function', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(stringReverse('title').as('reversedTitle')) + .execute(); + expectResults(snapshot, { + reversedTitle: "yxalaG eht ot ediuG s'rekihhctiH ehT", + }); + }); + + it('supports Document_id', async () => { + let snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + documentId(field('__name__')).as('docId'), + documentId(field('__path__')).as('noDocId'), + documentId(randomCol.doc('foo')).as('fromDocRef'), + ) + .execute(); + expectResults(snapshot, { + docId: 'book4', + noDocId: null, + fromDocRef: 'foo', + }); + snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('__name__').documentId().as('docId')) + .execute(); + expectResults(snapshot, { + docId: 'book4', + }); + }); + + it('supports substring', async () => { + let snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(substring('title', 9, 2).as('of')) + .execute(); + expectResults(snapshot, { + of: 'of', + }); + snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('title').substring(9, 2).as('of')) + .execute(); + expectResults(snapshot, { + of: 'of', + }); + }); + + it('supports substring without length', async () => { + let snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(substring('title', 9).as('of')) + .execute(); + expectResults(snapshot, { + of: 'of the Rings', + }); + snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('title').substring(9).as('of')) + .execute(); + expectResults(snapshot, { + of: 'of the Rings', + }); + }); + + it('arrayConcat works', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .select( + arrayConcat('tags', ['newTag1', 'newTag2'], field('tags'), [null]).as( + 'modifiedTags', + ), + ) + .limit(1) + .execute(); + expectResults(snapshot, { + modifiedTags: [ + 'comedy', + 'space', + 'adventure', + 'newTag1', + 'newTag2', + 'comedy', + 'space', + 'adventure', + null, + ], + }); + }); + + it('test toLower', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('title')) + .select(toLower('author').as('lowercaseAuthor')) + .limit(1) + .execute(); + expectResults(snapshot, { + lowercaseAuthor: 'george orwell', + }); + }); + + it('test toUpper', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('title')) + .select(toUpper('author').as('uppercaseAuthor')) + .limit(1) + .execute(); + expectResults(snapshot, {uppercaseAuthor: 'GEORGE ORWELL'}); + }); + + it('testTrim', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .replaceWith( + map({ + spacedTitle: " The Hitchhiker's Guide to the Galaxy ", + userNameWithQuotes: '"alice"', + bytes: Uint8Array.from([0x00, 0x01, 0x02, 0x00, 0x00]), + }), + ) + .select( + trim('spacedTitle').as('trimmedTitle'), + field('spacedTitle'), + field('userNameWithQuotes').trim('"').as('userName'), + field('bytes') + .trim(Uint8Array.from([0x00])) + .as('bytes'), + ) + .limit(1) + .execute(); + expectResults(snapshot, { + spacedTitle: " The Hitchhiker's Guide to the Galaxy ", + trimmedTitle: "The Hitchhiker's Guide to the Galaxy", + userName: 'alice', + bytes: Uint8Array.from([0x01, 0x02]), + }); + }); + + it('test reverse', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', '1984')) + .limit(1) + .select(reverse('title').as('reverseTitle')) + .execute(); + expectResults(snapshot, {reverseTitle: '4891'}); + }); + + it('testAbs', async () => { + const snapshot = await 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'), + ) + .execute(); + expectResults(snapshot, { + '10': 10, + '22': 22.22, + '1': 1, + }); + }); + + it('can compute the base-10 logarithm of a numeric value', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(field('rating').log10().as('log10Rating')) + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(log10('rating').as('log10Rating')) + .execute(); + expect(snapshot.results[0]!.data().log10Rating).to.be.closeTo( + 0.672, + 0.001, + ); + }); + + it('can concat fields', async () => { + const snapshot = await 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') + .execute(); + 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 firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .addFields(currentTimestamp().as('now')) + .select('now') + .execute(); + 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 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'), + ) + .execute(); + + expectResults(snapshot, { + title: 'foo', + name: 'default name', + nameOrTitle: 'foo', + }); + }); + + it('supports join', async () => { + const snapshot = await firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .replaceWith( + map({ + tags: ['foo', 'bar', 'baz'], + delimeter: '|', + }), + ) + .select(join('tags', ',').as('csv'), field('tags').join('|').as('or')) + .execute(); + + 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 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')) + .execute(); + expectResults(snapshot, { + totalSales: 350, + }); + }); + + it('can compute the sum of the elements in an array with the top-level function', async () => { + const snapshot = await 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')) + .execute(); + expectResults(snapshot, { + totalSales: 350, + }); + }); + it('truncate timestamp', async () => { + const results = await 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'), + ) + .execute(); + + 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 firestore + .pipeline() + .collection(randomCol) + .limit(1) + .replaceWith( + map({ + csv: 'foo,bar,baz', + data: 'baz:bar:foo', + csvDelimeter: ',', + bytes: Uint8Array.from([0x01, 0x00, 0x02, 0x00, 0x03]), + }), + ) + .select( + split('csv', field('csvDelimeter')).as('csv'), + split(field('data'), ':').as('data'), + field('bytes') + .split(constant(Uint8Array.from([0x00]))) + .as('bytes'), + ) + .execute(); + + expectResults(results, { + csv: ['foo', 'bar', 'baz'], + data: ['baz', 'bar', 'foo'], + bytes: [ + Uint8Array.from([0x01]), + Uint8Array.from([0x02]), + Uint8Array.from([0x03]), + ], + }); + + void expect( + firestore + .pipeline() + .collection(randomCol) + .limit(1) + .replaceWith( + map({ + csv: 'foo,bar,baz', + }), + ) + .select( + field('csv') + .split(constant(Uint8Array.from([0x00]))) + .as('dontSplitStringAndBytes'), + ) + .execute(), + ).to.be.rejected; + }); + + it('supports type', async () => { + const result = await 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(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])), + docRef: constant(firestore.doc('foo/bar')), + vector: constant(FieldValue.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'), + ) + .execute(); + + 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 = collectionReference.doc('book11'); + addedDocs.push(docRef); + await docRef.set({ + 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 = collectionReference.doc('book12'); + addedDocs.push(collectionReference.doc('book12')); + await docRef.set({ + 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 = collectionReference.doc('book13'); + addedDocs.push(docRef); + await docRef.set({ + 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 addedDocs[i].delete(); + } + 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 pipeline.limit(pageSize).execute(); + + snapshot.results.forEach(r => console.log(JSON.stringify(r.data()))); + + 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 pipeline + .where( + or( + and( + field('rating').equal(lastDoc.get('rating')), + field('__name__').greaterThan(lastDoc.ref), + ), + field('rating').lessThan(lastDoc.get('rating')), + ), + ) + .limit(pageSize) + .execute(); + 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 pipeline + .offset(currPage++ * pageSize) + .limit(pageSize) + .execute(); + + expectResults( + snapshot, + { + title: 'The Lord of the Rings', + rating: 4.7, + }, + {title: 'Dune', rating: 4.6}, + ); + + snapshot = await pipeline + .offset(currPage++ * pageSize) + .limit(pageSize) + .execute(); + expectResults( + snapshot, + { + title: 'Jonathan Strange & Mr Norrell', + rating: 4.6, + }, + {title: 'The Master and Margarita', rating: 4.6}, + ); + + snapshot = await pipeline + .offset(currPage++ * pageSize) + .limit(pageSize) + .execute(); + 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 + it.skip('Collection Stage', async () => { + const snapshot = await firestore + .pipeline() + .collection({ + collection: randomCol, + forceIndex: 'unknown', + }) + .execute(); + expect(snapshot.results.length).to.equal(10); + }); + + // SKIP: requires pre-existing index + it.skip('CollectionGroup Stage', async () => { + const snapshot = await firestore + .pipeline() + .collectionGroup({ + collectionId: randomCol.id, + forceIndex: 'unknown', + }) + .execute(); + expect(snapshot.results.length).to.equal(10); + }); + }); + }); + + describe('stream', () => { + it('full results as expected', done => { + const ppl = firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('__name__')); + const snapshotStream = ppl.stream(); + + const expected = [ + 'book1', + 'book10', + 'book2', + 'book3', + 'book4', + 'book5', + 'book6', + 'book7', + 'book8', + 'book9', + ]; + + let received = 0; + snapshotStream + .on('data', d => { + expect(d).to.be.an.instanceOf(PipelineResult); + const rslt = d as PipelineResult; + expect(rslt.id).to.equal(expected.shift()); + ++received; + }) + .on('end', () => { + expect(received).to.equal(10); + done(); + }); + }); + + it('empty snapshot', done => { + const ppl = firestore.pipeline().collection(randomCol.path).limit(0); + const snapshotStream = ppl.stream(); + + let received = 0; + snapshotStream + .on('data', _ => { + ++received; + }) + .on('end', () => { + expect(received).to.equal(0); + done(); + }); + }); + + it('document transform', done => { + const ppl = firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('__name__')) + .limit(2) + .select('title'); + const snapshotStream = ppl.stream(); + + const expected = [ + {title: "The Hitchhiker's Guide to the Galaxy"}, + {title: 'Dune'}, + ]; + + let received = 0; + snapshotStream + .on('data', d => { + expect(d).to.be.an.instanceOf(PipelineResult); + const rslt = d as PipelineResult; + expect(rslt.data()).to.deep.equal(expected.shift()); + ++received; + }) + .on('end', () => { + expect(received).to.equal(2); + done(); + }); + }); + }); +}); + +// This is the Query integration tests from the lite API (no cache support) +// with some additional test cases added for more complete coverage. +// eslint-disable-next-line no-restricted-properties +describe.skipClassic('Query to Pipeline', () => { + async function execute(ppl: Pipeline): Promise { + return ppl.execute(); + } + + async function testCollectionWithDocs( + docs: { + [id: string]: DocumentData; + }, + callback: (collRef: CollectionReference, db: Firestore) => Promise, + ): Promise { + const randomCol = getTestRoot(); + const firestore = randomCol.firestore; + for (const id in docs) { + const ref = randomCol.doc(id); + await ref.set(docs[id]); + } + + try { + await callback(randomCol, firestore); + } finally { + await firestore.terminate(); + } + } + + 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 testCollectionWithDocs({1: {foo: 1}}, async (collRef, db) => { + const snapshot = await execute(db.pipeline().createFrom(collRef)); + verifyResults(snapshot, {foo: 1}); + }); + }); + + it('supports filtered query', () => { + return testCollectionWithDocs( + { + 1: {foo: 1}, + 2: {foo: 2}, + }, + async (collRef, db) => { + const query1 = collRef.where('foo', '==', 1); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, {foo: 1}); + }, + ); + }); + + it('supports filtered query (with FieldPath)', () => { + return testCollectionWithDocs( + { + 1: {foo: 1}, + 2: {foo: 2}, + }, + async (collRef, db) => { + const query1 = 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 testCollectionWithDocs( + { + 1: {foo: 1}, + 2: {foo: 2}, + }, + async (collRef, db) => { + const query1 = collRef.orderBy('foo'); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, {foo: 1}, {foo: 2}); + }, + ); + }); + + it('supports ordered query (with asc)', () => { + return testCollectionWithDocs( + { + 1: {foo: 1}, + 2: {foo: 2}, + }, + async (collRef, db) => { + const query1 = 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 testCollectionWithDocs( + { + 1: {foo: 1}, + 2: {foo: 2}, + }, + async (collRef, db) => { + const query1 = collRef.orderBy('foo', 'desc'); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, {foo: 2}, {foo: 1}); + }, + ); + }); + + it('supports limit query', () => { + return testCollectionWithDocs( + { + 1: {foo: 1}, + 2: {foo: 2}, + }, + async (collRef, db) => { + const query1 = collRef.orderBy('foo').limit(1); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, {foo: 1}); + }, + ); + }); + + it('supports limitToLast query', () => { + return testCollectionWithDocs( + { + 1: {foo: 1}, + 2: {foo: 2}, + 3: {foo: 3}, + }, + async (collRef, db) => { + const query1 = collRef.orderBy('foo').limitToLast(2); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, {foo: 2}, {foo: 3}); + }, + ); + }); + + it('supports startAt', () => { + return testCollectionWithDocs( + { + 1: {foo: 1}, + 2: {foo: 2}, + }, + async (collRef, db) => { + const query1 = collRef.orderBy('foo').startAt(2); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, {foo: 2}); + }, + ); + }); + + it('supports startAt with limitToLast', () => { + return testCollectionWithDocs( + { + 1: {foo: 1}, + 2: {foo: 2}, + 3: {foo: 3}, + 4: {foo: 4}, + 5: {foo: 5}, + }, + async (collRef, db) => { + const query1 = 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 testCollectionWithDocs( + { + 1: {foo: 1}, + 2: {foo: 2}, + 3: {foo: 3}, + 4: {foo: 4}, + 5: {foo: 5}, + }, + async (collRef, db) => { + const query1 = 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 testCollectionWithDocs( + { + 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 collRef.doc('2').get(); + let query1 = 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 collRef.doc('3').get(); + query1 = 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 testCollectionWithDocs( + { + 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 collRef.doc('2').get(); + let query1 = 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 collRef.doc('3').get(); + query1 = 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 testCollectionWithDocs( + { + 1: {foo: 1}, + 2: {foo: 2}, + }, + async (collRef, db) => { + const query1 = collRef.orderBy('foo').startAfter(1); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, {foo: 2}); + }, + ); + }); + + it('supports endAt', () => { + return testCollectionWithDocs( + { + 1: {foo: 1}, + 2: {foo: 2}, + }, + async (collRef, db) => { + const query1 = collRef.orderBy('foo').endAt(1); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, {foo: 1}); + }, + ); + }); + + it('supports endBefore', () => { + return testCollectionWithDocs( + { + 1: {foo: 1}, + 2: {foo: 2}, + }, + async (collRef, db) => { + const query1 = collRef.orderBy('foo').endBefore(2); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, {foo: 1}); + }, + ); + }); + + it('supports pagination', () => { + return testCollectionWithDocs( + { + 1: {foo: 1}, + 2: {foo: 2}, + }, + async (collRef, db) => { + let query1 = 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 = query1.startAfter(snapshot.results[0].get('foo')); + snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, {foo: 2}); + }, + ); + }); + + it('supports pagination on DocumentIds', () => { + return testCollectionWithDocs( + { + 1: {foo: 1}, + 2: {foo: 2}, + }, + async (collRef, db) => { + let query1 = collRef + .orderBy('foo') + .orderBy(FieldPath.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 = 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 testCollectionWithDocs({}, async (collRef, db) => { + const collectionGroupId = `${collRef.id}group`; + + const fooDoc = collRef.firestore.doc( + `${collRef.id}/foo/${collectionGroupId}/doc1`, + ); + const barDoc = collRef.firestore.doc( + `${collRef.id}/bar/baz/boo/${collectionGroupId}/doc2`, + ); + await fooDoc.set({foo: 1}); + await barDoc.set({bar: 1}); + + const query1 = collRef.firestore.collectionGroup(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 testCollectionWithDocs({}, async (collRef, db) => { + const docWithSpecials = collRef.doc('so!@#$%^&*()_+special'); + + const collectionWithSpecials = docWithSpecials.collection( + 'so!@#$%^&*()_+special', + ); + await collectionWithSpecials.add({foo: 1}); + await collectionWithSpecials.add({foo: 2}); + + const snapshot = await execute( + db.pipeline().createFrom(collectionWithSpecials.orderBy('foo', 'asc')), + ); + + verifyResults(snapshot, {foo: 1}, {foo: 2}); + }); + }); + + it('supports multiple inequality on same field', () => { + return testCollectionWithDocs( + { + '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 = collRef.where( + Filter.and(Filter.where('id', '>', 2), Filter.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 testCollectionWithDocs( + { + '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 = collRef.where( + Filter.and(Filter.where('id', '>=', 2), Filter.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 testCollectionWithDocs({1: {foo: 1}}, async (collRef, db) => { + const snapshot = await execute( + db.pipeline().createFrom(db.collectionGroup(collRef.id)), + ); + verifyResults(snapshot, {foo: 1}); + }); + }); + + it('supports equal nan', () => { + return testCollectionWithDocs( + { + 1: {foo: 1, bar: NaN}, + 2: {foo: 2, bar: 1}, + 3: {foo: 3, bar: 'bar'}, + }, + async (collRef, db) => { + const query1 = collRef.where('bar', '==', NaN); + const classicSnapshot = await query1.get(); + const classicData = classicSnapshot.docs.map(d => d.data()); + + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, ...classicData); + }, + ); + }); + + it('supports notEqual nan', () => { + return testCollectionWithDocs( + { + 1: {foo: 1, bar: NaN}, + 2: {foo: 2, bar: 1}, + 3: {foo: 3, bar: 'bar'}, + }, + async (collRef, db) => { + const query1 = collRef.where('bar', '!=', NaN); + + const classicSnapshot = await query1.get(); + const classicData = classicSnapshot.docs.map(d => d.data()); + + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, ...classicData); + }, + ); + }); + + it('supports equal null', () => { + return testCollectionWithDocs( + { + 1: {foo: 1, bar: null}, + 2: {foo: 2, bar: 1}, + }, + async (collRef, db) => { + const query1 = collRef.where('bar', '==', null); + const classicSnapshot = await query1.get(); + const classicData = classicSnapshot.docs.map(d => d.data()); + + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, ...classicData); + }, + ); + }); + + it('supports notEqual null', () => { + return testCollectionWithDocs( + { + 1: {foo: 1, bar: null}, + 2: {foo: 2, bar: 1}, + }, + async (collRef, db) => { + const query1 = collRef.where('bar', '!=', null); + const classicSnapshot = await query1.get(); + const classicData = classicSnapshot.docs.map(d => d.data()); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, ...classicData); + }, + ); + }); + + it('supports notEqual', () => { + return testCollectionWithDocs( + { + 1: {foo: 1, bar: 0}, + 2: {foo: 2, bar: 1}, + }, + async (collRef, db) => { + const query1 = collRef.where('bar', '!=', 0); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, {foo: 2, bar: 1}); + }, + ); + }); + + it('supports array contains', () => { + return testCollectionWithDocs( + { + 1: {foo: 1, bar: [0, 2, 4, 6]}, + 2: {foo: 2, bar: [1, 3, 5, 7]}, + }, + async (collRef, db) => { + const query1 = 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 testCollectionWithDocs( + { + 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 = 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 testCollectionWithDocs( + { + 1: {foo: 1, bar: 2}, + 2: {foo: 2}, + 3: {foo: 3, bar: 10}, + }, + async (collRef, db) => { + const query1 = 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 testCollectionWithDocs( + { + 1: {foo: 1, bar: 2}, + 2: {foo: 2}, + 3: {foo: 3, bar: 10}, + }, + async (collRef, db) => { + const query1 = collRef.where('bar', 'in', [2]); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, {foo: 1, bar: 2}); + }, + ); + }); + + it('supports not in', () => { + return testCollectionWithDocs( + { + 1: {foo: 1, bar: 2}, + 2: {foo: 2, bar: 1}, + 3: {foo: 3, bar: 10}, + }, + async (collRef, db) => { + const query1 = 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 testCollectionWithDocs( + { + 1: {foo: 1, bar: 2}, + 2: {foo: 2}, + 3: {foo: 3, bar: 10}, + }, + async (collRef, db) => { + const query1 = collRef.where('bar', 'not-in', [2]); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, {foo: 2}, {foo: 3, bar: 10}); + }, + ); + }); + + it('supports or operator', () => { + return testCollectionWithDocs( + { + 1: {foo: 1, bar: 2}, + 2: {foo: 2, bar: 0}, + 3: {foo: 3, bar: 10}, + }, + async (collRef, db) => { + const query1 = collRef + .where( + Filter.or( + Filter.where('bar', '==', 2), + Filter.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/dev/system-test/query.ts b/dev/system-test/query.ts new file mode 100644 index 000000000..7a0205d6f --- /dev/null +++ b/dev/system-test/query.ts @@ -0,0 +1,2295 @@ +// 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 { + DocumentData, + QuerySnapshot, + VectorValue, +} from '@google-cloud/firestore'; + +import {expect} from 'chai'; +import {afterEach, beforeEach, describe, it} from 'mocha'; +import '../test/util/mocha_extensions'; +import { + CollectionReference, + DocumentReference, + DocumentSnapshot, + FieldPath, + FieldValue, + Filter, + Firestore, + Query, + QueryDocumentSnapshot, + VectorQuery, + VectorQuerySnapshot, + setLogFunction, +} from '../src'; +import {verifyInstance} from '../test/util/helpers'; +import {DeferredPromise, getTestRoot} from './firestore'; +import {IndexTestHelper} from './index_test_helper'; + +describe.skipClassic('Query and Pipeline Compare - Enterprise DB', () => { + interface PaginatedResults { + pages: number; + docs: QueryDocumentSnapshot[]; + } + + let firestore: Firestore; + let randomCol: CollectionReference; + + const paginateResults = ( + query: Query, + startAfter?: unknown, + ): Promise => { + return (startAfter ? query.startAfter(startAfter) : query) + .get() + .then(snapshot => { + if (snapshot.empty) { + return {pages: 0, docs: []}; + } else { + const docs = snapshot.docs; + return paginateResults(query, docs[docs.length - 1]).then( + nextPage => { + return { + pages: nextPage.pages + 1, + docs: docs.concat(nextPage.docs), + }; + }, + ); + } + }); + }; + + async function addDocs( + ...docs: DocumentData[] + ): Promise { + let id = 0; // Guarantees consistent ordering for the first documents + const refs: DocumentReference[] = []; + for (const doc of docs) { + const ref = randomCol.doc('doc' + id++); + await ref.set(doc); + refs.push(ref); + } + return refs; + } + + async function testCollectionWithDocs(docs: { + [id: string]: DocumentData; + }): Promise> { + for (const id in docs) { + const ref = randomCol.doc(id); + await ref.set(docs[id]); + } + return randomCol; + } + + function expectDocs(result: QuerySnapshot, ...docs: string[]): void; + function expectDocs(result: QuerySnapshot, ...data: DocumentData[]): void; + + function expectDocs( + result: QuerySnapshot, + ...data: DocumentData[] | string[] + ): void { + expect(result.size).to.equal(data.length); + + if (data.length > 0) { + if (typeof data[0] === 'string') { + const actualIds = result.docs.map(docSnapshot => docSnapshot.id); + expect(actualIds).to.deep.equal(data); + } else { + result.forEach(doc => { + expect(doc.data()).to.deep.equal(data.shift()); + }); + } + } + } + + async function compareQueryAndPipeline(query: Query): Promise { + const queryResults = await query.get(); + const pipeline = query.firestore.pipeline().createFrom(query); + const pipelineResults = await pipeline.execute(); + + expect(pipelineResults.results.map(r => r._fieldsProto)).to.deep.equal( + queryResults.docs.map(s => s._fieldsProto), + ); + return queryResults; + } + + async function compareVectorQueryAndPipeline( + query: VectorQuery, + ): Promise { + const queryResults = await query.get(); + const pipeline = query.query.firestore.pipeline().createFrom(query); + const pipelineResults = await pipeline.execute(); + + expect(pipelineResults.results.map(r => r._fieldsProto)).to.deep.equal( + queryResults.docs.map(s => s._fieldsProto), + ); + return queryResults; + } + + beforeEach(() => { + randomCol = getTestRoot(); + firestore = randomCol.firestore; + }); + + afterEach(() => verifyInstance(firestore)); + + it('has firestore property', () => { + const ref = randomCol.limit(0); + expect(ref.firestore).to.be.an.instanceOf(Firestore); + }); + + it('has select() method', () => { + const ref = randomCol.doc('doc'); + return ref + .set({foo: 'bar', bar: 'foo'}) + .then(() => { + return randomCol.select('foo').get(); + }) + .then(res => { + expect(res.docs[0].data()).to.deep.equal({foo: 'bar'}); + }); + }); + + it('select() supports empty fields', () => { + const ref = randomCol.doc('doc'); + return ref + .set({foo: 'bar', bar: 'foo'}) + .then(() => { + return randomCol.select().get(); + }) + .then(res => { + expect(res.docs[0].ref.id).to.deep.equal('doc'); + expect(res.docs[0].data()).to.deep.equal({}); + }); + }); + + it('has where() method', () => { + const ref = randomCol.doc('doc'); + return ref + .set({foo: 'bar'}) + .then(() => { + return compareQueryAndPipeline(randomCol.where('foo', '==', 'bar')); + }) + .then(res => { + expect(res.docs[0].data()).to.deep.equal({foo: 'bar'}); + }); + }); + + it('supports NaN and Null', () => { + const ref = randomCol.doc('doc'); + return ref + .set({foo: NaN, bar: null}) + .then(() => { + return compareQueryAndPipeline( + randomCol.where('foo', '==', NaN).where('bar', '==', null), + ); + }) + .then(res => { + expect( + typeof res.docs[0].get('foo') === 'number' && + isNaN(res.docs[0].get('foo')), + ); + expect(res.docs[0].get('bar')).to.equal(null); + }); + }); + + it('supports array-contains', () => { + return Promise.all([ + randomCol.add({foo: ['bar']}), + randomCol.add({foo: []}), + ]) + .then(() => + compareQueryAndPipeline( + randomCol.where('foo', 'array-contains', 'bar'), + ), + ) + .then(res => { + expect(res.size).to.equal(1); + expect(res.docs[0].get('foo')).to.deep.equal(['bar']); + }); + }); + + it('supports findNearest by EUCLIDEAN distance', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionReference = await indexTestHelper.createTestDocs([ + {foo: 'bar'}, + {foo: 'xxx', embedding: FieldValue.vector([10, 10])}, + {foo: 'bar', embedding: FieldValue.vector([1, 1])}, + {foo: 'bar', embedding: FieldValue.vector([10, 0])}, + {foo: 'bar', embedding: FieldValue.vector([20, 0])}, + {foo: 'bar', embedding: FieldValue.vector([100, 100])}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionReference) + .where('foo', '==', 'bar') + .findNearest('embedding', [10, 10], { + limit: 3, + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + expect(res.size).to.equal(3); + expect(res.docs[0].get('embedding').isEqual(FieldValue.vector([10, 0]))).to + .be.true; + expect(res.docs[1].get('embedding').isEqual(FieldValue.vector([1, 1]))).to + .be.true; + expect(res.docs[2].get('embedding').isEqual(FieldValue.vector([20, 0]))).to + .be.true; + }); + + it('supports findNearest by COSINE distance', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionReference = await indexTestHelper.setTestDocs({ + '1': {foo: 'bar'}, + '2': {foo: 'xxx', embedding: FieldValue.vector([10, 10])}, + '3': {foo: 'bar', embedding: FieldValue.vector([1, 1])}, + '4': {foo: 'bar', embedding: FieldValue.vector([20, 0])}, + '5': {foo: 'bar', embedding: FieldValue.vector([10, 0])}, + '6': {foo: 'bar', embedding: FieldValue.vector([100, 100])}, + }); + + const vectorQuery = indexTestHelper + .query(collectionReference) + .where('foo', '==', 'bar') + .findNearest('embedding', [10, 10], { + limit: 3, + distanceMeasure: 'COSINE', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + + expect(res.size).to.equal(3); + + if (res.docs[0].get('embedding').isEqual(FieldValue.vector([1, 1]))) { + expect( + res.docs[1].get('embedding').isEqual(FieldValue.vector([100, 100])), + ).to.be.true; + } else { + expect( + res.docs[0].get('embedding').isEqual(FieldValue.vector([100, 100])), + ).to.be.true; + expect(res.docs[1].get('embedding').isEqual(FieldValue.vector([1, 1]))).to + .be.true; + } + + expect( + res.docs[2].get('embedding').isEqual(FieldValue.vector([20, 0])) || + res.docs[2].get('embedding').isEqual(FieldValue.vector([10, 0])), + ).to.be.true; + }); + + it('supports findNearest by DOT_PRODUCT distance', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionReference = await indexTestHelper.createTestDocs([ + {foo: 'bar'}, + {foo: 'xxx', embedding: FieldValue.vector([10, 10])}, + {foo: 'bar', embedding: FieldValue.vector([1, 1])}, + {foo: 'bar', embedding: FieldValue.vector([10, 0])}, + {foo: 'bar', embedding: FieldValue.vector([20, 0])}, + {foo: 'bar', embedding: FieldValue.vector([100, 100])}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionReference) + .where('foo', '==', 'bar') + .findNearest('embedding', [10, 10], { + limit: 3, + distanceMeasure: 'DOT_PRODUCT', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + expect(res.size).to.equal(3); + expect(res.docs[0].get('embedding').isEqual(FieldValue.vector([100, 100]))) + .to.be.true; + expect(res.docs[1].get('embedding').isEqual(FieldValue.vector([20, 0]))).to + .be.true; + expect(res.docs[2].get('embedding').isEqual(FieldValue.vector([10, 0]))).to + .be.true; + }); + + it('findNearest works with converters', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + class FooDistance { + constructor( + readonly foo: string, + readonly embedding: Array, + ) {} + } + + const fooConverter = { + toFirestore(d: FooDistance): DocumentData { + return {title: d.foo, embedding: FieldValue.vector(d.embedding)}; + }, + fromFirestore(snapshot: QueryDocumentSnapshot): FooDistance { + const data = snapshot.data(); + return new FooDistance(data.foo, data.embedding.toArray()); + }, + }; + + const collectionRef = await indexTestHelper.createTestDocs([ + {foo: 'bar', embedding: FieldValue.vector([5, 5])}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionRef) + .withConverter(fooConverter) + .where('foo', '==', 'bar') + .findNearest('embedding', [10, 10], { + limit: 3, + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + + expect(res.size).to.equal(1); + expect(res.docs[0].data().foo).to.equal('bar'); + expect(res.docs[0].data().embedding).to.deep.equal([5, 5]); + }); + + it('supports findNearest skipping fields of wrong types', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionRef = await indexTestHelper.createTestDocs([ + {foo: 'bar'}, + + // These documents are skipped because it is not really a vector value + {foo: 'bar', embedding: [10, 10]}, + {foo: 'bar', embedding: 'not actually a vector'}, + {foo: 'bar', embedding: null}, + + // Actual vector values + {foo: 'bar', embedding: FieldValue.vector([9, 9])}, + {foo: 'bar', embedding: FieldValue.vector([50, 50])}, + {foo: 'bar', embedding: FieldValue.vector([100, 100])}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionRef) + .where('foo', '==', 'bar') + .findNearest('embedding', [10, 10], { + limit: 100, // Intentionally large to get all matches. + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + expect(res.size).to.equal(3); + expect(res.docs[0].get('embedding').isEqual(FieldValue.vector([9, 9]))).to + .be.true; + expect(res.docs[1].get('embedding').isEqual(FieldValue.vector([50, 50]))).to + .be.true; + expect(res.docs[2].get('embedding').isEqual(FieldValue.vector([100, 100]))) + .to.be.true; + }); + + it('findNearest ignores mismatching dimensions', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionRef = await indexTestHelper.createTestDocs([ + {foo: 'bar'}, + + // Vectors with dimension mismatch + {foo: 'bar', embedding: FieldValue.vector([10])}, + + // Vectors with dimension match + {foo: 'bar', embedding: FieldValue.vector([9, 9])}, + {foo: 'bar', embedding: FieldValue.vector([50, 50])}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionRef) + .where('foo', '==', 'bar') + .findNearest('embedding', [10, 10], { + limit: 3, + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + expect(res.size).to.equal(2); + expect(res.docs[0].get('embedding').isEqual(FieldValue.vector([9, 9]))).to + .be.true; + expect(res.docs[1].get('embedding').isEqual(FieldValue.vector([50, 50]))).to + .be.true; + }); + + it('supports findNearest on non-existent field', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionRef = await indexTestHelper.createTestDocs([ + {foo: 'bar'}, + {foo: 'bar', otherField: [10, 10]}, + {foo: 'bar', otherField: 'not actually a vector'}, + {foo: 'bar', otherField: null}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionRef) + .where('foo', '==', 'bar') + .findNearest('embedding', [10, 10], { + limit: 3, + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + + expect(res.size).to.equal(0); + }); + + it('supports findNearest on vector nested in a map', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionReference = await indexTestHelper.createTestDocs([ + {nested: {foo: 'bar'}}, + {nested: {foo: 'xxx', embedding: FieldValue.vector([10, 10])}}, + {nested: {foo: 'bar', embedding: FieldValue.vector([1, 1])}}, + {nested: {foo: 'bar', embedding: FieldValue.vector([10, 0])}}, + {nested: {foo: 'bar', embedding: FieldValue.vector([20, 0])}}, + {nested: {foo: 'bar', embedding: FieldValue.vector([100, 100])}}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionReference) + .findNearest('nested.embedding', [10, 10], { + limit: 3, + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + expect(res.size).to.equal(3); + expect( + res.docs[0].get('nested.embedding').isEqual(FieldValue.vector([10, 10])), + ).to.be.true; + expect( + res.docs[1].get('nested.embedding').isEqual(FieldValue.vector([10, 0])), + ).to.be.true; + expect( + res.docs[2].get('nested.embedding').isEqual(FieldValue.vector([1, 1])), + ).to.be.true; + }); + + it('supports findNearest with select to exclude vector data in response', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionReference = await indexTestHelper.createTestDocs([ + {foo: 1}, + {foo: 2, embedding: FieldValue.vector([10, 10])}, + {foo: 3, embedding: FieldValue.vector([1, 1])}, + {foo: 4, embedding: FieldValue.vector([10, 0])}, + {foo: 5, embedding: FieldValue.vector([20, 0])}, + {foo: 6, embedding: FieldValue.vector([100, 100])}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionReference) + .where('foo', 'in', [1, 2, 3, 4, 5, 6]) + .select('foo') + .findNearest('embedding', [10, 10], { + limit: 10, + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await vectorQuery.get(); + expect(res.size).to.equal(5); + expect(res.docs[0].get('foo')).to.equal(2); + expect(res.docs[1].get('foo')).to.equal(4); + expect(res.docs[2].get('foo')).to.equal(3); + expect(res.docs[3].get('foo')).to.equal(5); + expect(res.docs[4].get('foo')).to.equal(6); + + res.docs.forEach(ds => expect(ds.get('embedding')).to.be.undefined); + }); + + it('supports findNearest limits', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const embeddingVector = []; + const queryVector = []; + for (let i = 0; i < 2048; i++) { + embeddingVector.push(i + 1); + queryVector.push(i - 1); + } + + const collectionReference = await indexTestHelper.createTestDocs([ + {embedding: FieldValue.vector(embeddingVector)}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionReference) + .findNearest('embedding', queryVector, { + limit: 1000, + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + expect(res.size).to.equal(1); + expect( + (res.docs[0].get('embedding') as VectorValue).toArray(), + ).to.deep.equal(embeddingVector); + }); + + // TODO waiting on implicit sort order decision + it.skipEnterprise('supports !=', async () => { + await addDocs( + {zip: NaN}, + {zip: 91102}, + {zip: 98101}, + {zip: 98103}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}}, + {zip: null}, + ); + + let res = await compareQueryAndPipeline( + randomCol.where('zip', '!=', 98101), + ); + expectDocs( + res, + {zip: NaN}, + {zip: 91102}, + {zip: 98103}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}}, + ); + + res = await compareQueryAndPipeline(randomCol.where('zip', '!=', NaN)); + expectDocs( + res, + {zip: 91102}, + {zip: 98101}, + {zip: 98103}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}}, + ); + + res = await compareQueryAndPipeline(randomCol.where('zip', '!=', null)); + expectDocs( + res, + {zip: NaN}, + {zip: 91102}, + {zip: 98101}, + {zip: 98103}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}}, + ); + }); + + // TODO waiting on implicit sort order decision + it.skipEnterprise('supports != with document ID', async () => { + const refs = await addDocs({count: 1}, {count: 2}, {count: 3}); + const res = await compareQueryAndPipeline( + randomCol.where(FieldPath.documentId(), '!=', refs[0].id), + ); + expectDocs(res, {count: 2}, {count: 3}); + }); + + it('supports not-in', async () => { + await addDocs( + {zip: 98101}, + {zip: 91102}, + {zip: 98103}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}}, + ); + let res = await compareQueryAndPipeline( + randomCol + .where('zip', 'not-in', [98101, 98103]) + .orderBy(FieldPath.documentId()), + ); + expectDocs( + res, + {zip: 91102}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}}, + ); + + res = await compareQueryAndPipeline( + randomCol + .where('zip', 'not-in', [NaN]) + .orderBy('zip') + .orderBy(FieldPath.documentId()), + ); + expectDocs( + res, + {zip: 91102}, + {zip: 98101}, + {zip: 98103}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}}, + ); + + res = await compareQueryAndPipeline( + randomCol.where('zip', 'not-in', [null]).orderBy(FieldPath.documentId()), + ); + expectDocs( + res, + {zip: 98101}, + {zip: 91102}, + {zip: 98103}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}}, + ); + }); + + it('supports not-in with document ID array', async () => { + const refs = await addDocs({count: 1}, {count: 2}, {count: 3}); + const res = await compareQueryAndPipeline( + randomCol.where(FieldPath.documentId(), 'not-in', [refs[0].id, refs[1]]), + ); + expectDocs(res, {count: 3}); + }); + + // TODO waiting on implicit sort order decision + it.skipEnterprise('supports "in"', async () => { + await addDocs( + {zip: 98101}, + {zip: 91102}, + {zip: 98103}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}}, + ); + const res = await compareQueryAndPipeline( + randomCol.where('zip', 'in', [98101, 98103]), + ); + expectDocs(res, {zip: 98101}, {zip: 98103}); + }); + + it('supports "in" with document ID array', async () => { + const refs = await addDocs({count: 1}, {count: 2}, {count: 3}); + const res = await compareQueryAndPipeline( + randomCol.where(FieldPath.documentId(), 'in', [refs[0].id, refs[1]]), + ); + expectDocs(res, {count: 1}, {count: 2}); + }); + + // TODO waiting fix for `The function array_contains(...) requires `Array` but got `LONG`. + it.skipEnterprise('supports array-contains-any', async () => { + await addDocs( + {array: [42]}, + {array: ['a', 42, 'c']}, + {array: [41.999, '42', {a: [42]}]}, + {array: [42], array2: ['sigh']}, + {array: [43]}, + {array: [{a: 42}]}, + {array: 42}, + ); + + const res = await compareQueryAndPipeline( + randomCol.where('array', 'array-contains-any', [42, 43]), + ); + + expectDocs( + res, + {array: [42]}, + {array: ['a', 42, 'c']}, + { + array: [42], + array2: ['sigh'], + }, + {array: [43]}, + ); + }); + + it('can query by FieldPath.documentId()', () => { + const ref = randomCol.doc('foo'); + + return ref + .set({}) + .then(() => { + return compareQueryAndPipeline( + randomCol.where(FieldPath.documentId(), '>=', 'bar'), + ); + }) + .then(res => { + expect(res.docs.length).to.equal(1); + }); + }); + + it('has orderBy() method', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + + let res = await compareQueryAndPipeline(randomCol.orderBy('foo')); + expectDocs(res, {foo: 'a'}, {foo: 'b'}); + + res = await compareQueryAndPipeline(randomCol.orderBy('foo', 'desc')); + expectDocs(res, {foo: 'b'}, {foo: 'a'}); + }); + + it('can order by FieldPath.documentId()', () => { + const ref1 = randomCol.doc('doc1'); + const ref2 = randomCol.doc('doc2'); + + return Promise.all([ref1.set({foo: 'a'}), ref2.set({foo: 'b'})]) + .then(() => { + return compareQueryAndPipeline( + randomCol.orderBy(FieldPath.documentId()), + ); + }) + .then(res => { + expect(res.docs[0].data()).to.deep.equal({foo: 'a'}); + expect(res.docs[1].data()).to.deep.equal({foo: 'b'}); + }); + }); + + it('can run get() on empty collection', async () => { + return compareQueryAndPipeline(randomCol).then(res => { + return expect(res.empty); + }); + }); + + it('can run stream() on empty collection', async () => { + let received = 0; + const stream = randomCol.stream(); + + for await (const doc of stream) { + expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); + ++received; + } + + expect(received).to.equal(0); + }); + + it('has limit() method on get()', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await compareQueryAndPipeline( + randomCol.orderBy('foo').limit(1), + ); + expectDocs(res, {foo: 'a'}); + }); + + it('has limit() method on stream()', async () => { + let received = 0; + await addDocs({foo: 'a'}, {foo: 'b'}); + + const stream = randomCol.orderBy('foo').limit(1).stream(); + for await (const doc of stream) { + expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); + ++received; + } + + expect(received).to.equal(1); + }); + + it('can run limit(num), where num is larger than the collection size on get()', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await compareQueryAndPipeline( + randomCol.orderBy('foo').limit(3), + ); + expectDocs(res, {foo: 'a'}, {foo: 'b'}); + }); + + it('can run limit(num), where num is larger than the collection size on stream()', async () => { + let received = 0; + await addDocs({foo: 'a'}, {foo: 'b'}); + + const stream = randomCol.orderBy('foo').limit(3).stream(); + for await (const doc of stream) { + expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); + ++received; + } + + expect(received).to.equal(2); + }); + + it('has limitToLast() method', async () => { + await addDocs({doc: 1}, {doc: 2}, {doc: 3}); + // const res = await compareQueryAndPipeline(randomCol.orderBy('doc').limitToLast(2)); + const res = await randomCol.orderBy('doc').limitToLast(2).get(); + expectDocs(res, {doc: 2}, {doc: 3}); + }); + + it('limitToLast() supports Query cursors', async () => { + await addDocs({doc: 1}, {doc: 2}, {doc: 3}, {doc: 4}, {doc: 5}); + const res = await randomCol + .orderBy('doc') + .startAt(2) + .endAt(4) + .limitToLast(5) + .get(); + expectDocs(res, {doc: 2}, {doc: 3}, {doc: 4}); + }); + + it('can use offset() method with get()', async () => { + setLogFunction(console.log); + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await compareQueryAndPipeline( + randomCol.orderBy('foo').offset(1), + ); + expectDocs(res, {foo: 'b'}); + }); + + it('can use offset() method with stream()', async () => { + let received = 0; + await addDocs({foo: 'a'}, {foo: 'b'}); + + const stream = randomCol.orderBy('foo').offset(1).stream(); + for await (const doc of stream) { + expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); + ++received; + } + + expect(received).to.equal(1); + }); + + it('can run offset(num), where num is larger than the collection size on get()', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await compareQueryAndPipeline( + randomCol.orderBy('foo').offset(3), + ); + expect(res.empty); + }); + + it('can run offset(num), where num is larger than the collection size on stream()', async () => { + let received = 0; + await addDocs({foo: 'a'}, {foo: 'b'}); + const stream = randomCol.orderBy('foo').offset(3).stream(); + for await (const doc of stream) { + expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); + ++received; + } + expect(received).to.equal(0); + }); + + it('supports Unicode in document names', async () => { + const collRef = randomCol.doc('доброеутро').collection('coll'); + await collRef.add({}); + const snapshot = await compareQueryAndPipeline(collRef); + expect(snapshot.size).to.equal(1); + }); + + it('supports pagination', () => { + const batch = firestore.batch(); + + for (let i = 0; i < 10; ++i) { + batch.set(randomCol.doc('doc' + i), {val: i}); + } + + const query = randomCol.orderBy('val').limit(3); + + return batch + .commit() + .then(() => paginateResults(query)) + .then(results => { + expect(results.pages).to.equal(4); + expect(results.docs).to.have.length(10); + }); + }); + + // TODO (enterprise) waiting for implicit sort order support + it('supports pagination with where() clauses', () => { + const batch = firestore.batch(); + + for (let i = 0; i < 10; ++i) { + batch.set(randomCol.doc('doc' + i), {val: i}); + } + + const query = randomCol + .where('val', '>=', 1) + .limit(3) + .orderBy('val') + .orderBy(FieldPath.documentId()); + + return batch + .commit() + .then(() => paginateResults(query)) + .then(results => { + expect(results.pages).to.equal(3); + expect(results.docs).to.have.length(9); + }); + }); + + it('supports pagination with array-contains filter', () => { + const batch = firestore.batch(); + + for (let i = 0; i < 10; ++i) { + batch.set(randomCol.doc('doc' + i), {array: ['foo']}); + } + + const query = randomCol.where('array', 'array-contains', 'foo').limit(3); + + return batch + .commit() + .then(() => paginateResults(query)) + .then(results => { + expect(results.pages).to.equal(4); + expect(results.docs).to.have.length(10); + }); + }); + + it('has startAt() method', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await randomCol.orderBy('foo').startAt('b').get(); + expectDocs(res, {foo: 'b'}); + }); + + it('startAt() adds implicit order by for DocumentSnapshot', async () => { + const references = await addDocs({foo: 'a'}, {foo: 'b'}); + const docSnap = await references[1].get(); + const res = await randomCol.startAt(docSnap).get(); + expectDocs(res, {foo: 'b'}); + }); + + it('has startAfter() method', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await randomCol.orderBy('foo').startAfter('a').get(); + expectDocs(res, {foo: 'b'}); + }); + + it('has endAt() method', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await randomCol.orderBy('foo').endAt('b').get(); + expectDocs(res, {foo: 'a'}, {foo: 'b'}); + }); + + it('has endBefore() method', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await randomCol.orderBy('foo').endBefore('b').get(); + expectDocs(res, {foo: 'a'}); + }); + + it('has stream() method', done => { + let received = 0; + const ref1 = randomCol.doc('doc1'); + const ref2 = randomCol.doc('doc2'); + + void Promise.all([ref1.set({foo: 'a'}), ref2.set({foo: 'b'})]).then(() => { + return randomCol + .stream() + .on('data', d => { + expect(d).to.be.an.instanceOf(DocumentSnapshot); + ++received; + }) + .on('end', () => { + expect(received).to.equal(2); + done(); + }); + }); + }); + + it('stream() supports readable[Symbol.asyncIterator]()', async () => { + let received = 0; + await randomCol.doc().set({foo: 'bar'}); + await randomCol.doc().set({foo: 'bar'}); + + const stream = randomCol.stream(); + for await (const doc of stream) { + expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); + ++received; + } + + expect(received).to.equal(2); + }); + + // TODO (enterprise) waiting on implicit sor order decision + it.skipEnterprise('can query collection groups', async () => { + // Use `randomCol` to get a random collection group name to use but ensure + // it starts with 'b' for predictable ordering. + const collectionGroup = 'b' + randomCol.id; + + const docPaths = [ + `abc/123/${collectionGroup}/cg-doc1`, + `abc/123/${collectionGroup}/cg-doc2`, + `${collectionGroup}/cg-doc3`, + `${collectionGroup}/cg-doc4`, + `def/456/${collectionGroup}/cg-doc5`, + `${collectionGroup}/virtual-doc/nested-coll/not-cg-doc`, + `x${collectionGroup}/not-cg-doc`, + `${collectionGroup}x/not-cg-doc`, + `abc/123/${collectionGroup}x/not-cg-doc`, + `abc/123/x${collectionGroup}/not-cg-doc`, + `abc/${collectionGroup}`, + ]; + const batch = firestore.batch(); + for (const docPath of docPaths) { + batch.set(firestore.doc(docPath), {x: 1}); + } + await batch.commit(); + + const querySnapshot = await compareQueryAndPipeline( + firestore.collectionGroup(collectionGroup), + ); + expect(querySnapshot.docs.map(d => d.id)).to.deep.equal([ + 'cg-doc1', + 'cg-doc2', + 'cg-doc3', + 'cg-doc4', + 'cg-doc5', + ]); + }); + + // TODO (enterprise) wait for implicit sort order support + it.skipEnterprise( + 'can query collection groups with startAt / endAt by arbitrary documentId', + async () => { + // Use `randomCol` to get a random collection group name to use but + // ensure it starts with 'b' for predictable ordering. + const collectionGroup = 'b' + randomCol.id; + + const docPaths = [ + `a/a/${collectionGroup}/cg-doc1`, + `a/b/a/b/${collectionGroup}/cg-doc2`, + `a/b/${collectionGroup}/cg-doc3`, + `a/b/c/d/${collectionGroup}/cg-doc4`, + `a/c/${collectionGroup}/cg-doc5`, + `${collectionGroup}/cg-doc6`, + 'a/b/nope/nope', + ]; + const batch = firestore.batch(); + for (const docPath of docPaths) { + batch.set(firestore.doc(docPath), {x: 1}); + } + await batch.commit(); + + let querySnapshot = await firestore + .collectionGroup(collectionGroup) + .orderBy(FieldPath.documentId()) + .startAt('a/b') + .endAt('a/b0') + .get(); + expect(querySnapshot.docs.map(d => d.id)).to.deep.equal([ + 'cg-doc2', + 'cg-doc3', + 'cg-doc4', + ]); + + querySnapshot = await firestore + .collectionGroup(collectionGroup) + .orderBy(FieldPath.documentId()) + .startAfter('a/b') + .endBefore(`a/b/${collectionGroup}/cg-doc3`) + .get(); + expect(querySnapshot.docs.map(d => d.id)).to.deep.equal(['cg-doc2']); + }, + ); + + // TODO (enterprise) wait for implicit sort order support + it.skipEnterprise( + 'can query collection groups with where filters on arbitrary documentId', + async () => { + // Use `randomCol` to get a random collection group name to use but + // ensure it starts with 'b' for predictable ordering. + const collectionGroup = 'b' + randomCol.id; + + const docPaths = [ + `a/a/${collectionGroup}/cg-doc1`, + `a/b/a/b/${collectionGroup}/cg-doc2`, + `a/b/${collectionGroup}/cg-doc3`, + `a/b/c/d/${collectionGroup}/cg-doc4`, + `a/c/${collectionGroup}/cg-doc5`, + `${collectionGroup}/cg-doc6`, + 'a/b/nope/nope', + ]; + const batch = firestore.batch(); + for (const docPath of docPaths) { + batch.set(firestore.doc(docPath), {x: 1}); + } + await batch.commit(); + + let querySnapshot = await compareQueryAndPipeline( + firestore + .collectionGroup(collectionGroup) + .where(FieldPath.documentId(), '>=', 'a/b') + .where(FieldPath.documentId(), '<=', 'a/b0'), + ); + expect(querySnapshot.docs.map(d => d.id)).to.deep.equal([ + 'cg-doc2', + 'cg-doc3', + 'cg-doc4', + ]); + + querySnapshot = await compareQueryAndPipeline( + firestore + .collectionGroup(collectionGroup) + .where(FieldPath.documentId(), '>', 'a/b') + .where(FieldPath.documentId(), '<', `a/b/${collectionGroup}/cg-doc3`), + ); + expect(querySnapshot.docs.map(d => d.id)).to.deep.equal(['cg-doc2']); + }, + ); + + it('can query large collections', async () => { + // @grpc/grpc-js v0.4.1 failed to deliver the full set of query results for + // larger collections (https://github.com/grpc/grpc-node/issues/895); + const batch = firestore.batch(); + for (let i = 0; i < 100; ++i) { + batch.create(randomCol.doc(), {}); + } + await batch.commit(); + + const snapshot = await compareQueryAndPipeline(randomCol); + expect(snapshot.size).to.equal(100); + }); + + // TODO (enterprise) wait for implicit sort order support + it.skipEnterprise('supports OR queries', async () => { + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: 0}, + doc2: {a: 2, b: 1}, + doc3: {a: 3, b: 2}, + doc4: {a: 1, b: 3}, + doc5: {a: 1, b: 1}, + }); + + // Two equalities: a==1 || b==1. + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)), + ), + ), + 'doc1', + 'doc2', + 'doc4', + 'doc5', + ); + + // (a==1 && b==0) || (a==3 && b==2) + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.or( + Filter.and(Filter.where('a', '==', 1), Filter.where('b', '==', 0)), + Filter.and(Filter.where('a', '==', 3), Filter.where('b', '==', 2)), + ), + ), + ), + 'doc1', + 'doc3', + ); + + // a==1 && (b==0 || b==3). + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.and( + Filter.where('a', '==', 1), + Filter.or(Filter.where('b', '==', 0), Filter.where('b', '==', 3)), + ), + ), + ), + 'doc1', + 'doc4', + ); + + // (a==2 || b==2) && (a==3 || b==3) + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.and( + Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 2)), + Filter.or(Filter.where('a', '==', 3), Filter.where('b', '==', 3)), + ), + ), + ), + 'doc3', + ); + + // Test with limits without orderBy (the __name__ ordering is the tie breaker). + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 1)), + ) + .limit(1), + ), + 'doc2', + ); + }); + + // Skip this test if running against production because it results in a 'missing index' error. + // The Firestore Emulator, however, does serve these queries. + // TODO (enterprise) wait for implicit sort order support + it.skipEnterprise('supports OR queries with composite indexes', async () => { + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: 0}, + doc2: {a: 2, b: 1}, + doc3: {a: 3, b: 2}, + doc4: {a: 1, b: 3}, + doc5: {a: 1, b: 1}, + }); + + // with one inequality: a>2 || b==1. + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.or(Filter.where('a', '>', 2), Filter.where('b', '==', 1)), + ), + ), + 'doc5', + 'doc2', + 'doc3', + ); + + // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2 + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '==', 1), Filter.where('b', '>', 0)), + ) + .limit(2), + ), + 'doc1', + 'doc2', + ); + + // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2 + // Note: The public query API does not allow implicit ordering when limitToLast is used. + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '==', 1), Filter.where('b', '>', 0)), + ) + .limitToLast(2) + .orderBy('b'), + ), + 'doc3', + 'doc4', + ); + + // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1 + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 1)), + ) + .limit(1) + .orderBy('a'), + ), + 'doc5', + ); + + // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT 1 + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 1)), + ) + .limit(1) + .orderBy('a', 'desc'), + ), + 'doc2', + ); + }); + + it('supports OR queries on documents with missing fields', async () => { + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: 0}, + doc2: {b: 1}, + doc3: {a: 3, b: 2}, + doc4: {a: 1, b: 3}, + doc5: {a: 1}, + doc6: {a: 2}, + }); + + // Query: a==1 || b==1 + // There's no explicit nor implicit orderBy. Documents with missing 'a' or missing 'b' should be + // allowed if the document matches at least one disjunction term. + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)), + ) + .orderBy(FieldPath.documentId()), + ), + 'doc1', + 'doc2', + 'doc4', + 'doc5', + ); + }); + + // Skip this test if running against production because it results in a 'missing index' error. + // TODO (enterprise) wait for implicit sort order support + it.skip('supports OR queries on documents with missing fields', async () => { + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: 0}, + doc2: {b: 1}, + doc3: {a: 3, b: 2}, + doc4: {a: 1, b: 3}, + doc5: {a: 1}, + doc6: {a: 2}, + }); + + // Query: a==1 || b==1 order by a. + // doc2 should not be included because it's missing the field 'a', and we have "orderBy a". + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)), + ) + .orderBy('a'), + ), + 'doc1', + 'doc4', + 'doc5', + ); + + // Query: a==1 || b==1 order by b. + // doc5 should not be included because it's missing the field 'b', and we have "orderBy b". + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)), + ) + .orderBy('b'), + ), + 'doc1', + 'doc2', + 'doc4', + ); + + // Query: a>2 || b==1. + // This query has an implicit 'order by a'. + // doc2 should not be included because it's missing the field 'a'. + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.or(Filter.where('a', '>', 2), Filter.where('b', '==', 1)), + ), + ), + 'doc3', + ); + + // Query: a>1 || b==1 order by a order by b. + // doc6 should not be included because it's missing the field 'b'. + // doc2 should not be included because it's missing the field 'a'. + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '>', 1), Filter.where('b', '==', 1)), + ) + .orderBy('a') + .orderBy('b'), + ), + 'doc3', + ); + }); + + it('supports OR queries with in', async () => { + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: 0}, + doc2: {b: 1}, + doc3: {a: 3, b: 2}, + doc4: {a: 1, b: 3}, + doc5: {a: 1}, + doc6: {a: 2}, + }); + + // Query: a==2 || b in [2, 3] + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or( + Filter.where('a', '==', 2), + Filter.where('b', 'in', [2, 3]), + ), + ) + .orderBy(FieldPath.documentId()), + ), + 'doc3', + 'doc4', + 'doc6', + ); + }); + + // Skip this test if running against production because it results in a 'missing index' error. + it('supports OR queries with not-in', async () => { + setLogFunction(console.log); + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: 0}, + doc2: {b: 1}, + doc3: {a: 3, b: 2}, + doc4: {a: 1, b: 3}, + doc5: {a: 1}, + doc6: {a: 2}, + }); + + // a==2 || (b != 2 && b != 3) + // Has implicit "orderBy b" + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or( + Filter.where('a', '==', 2), + Filter.where('b', 'not-in', [2, 3]), + ), + ) + .orderBy(FieldPath.documentId()), + ), + 'doc1', + 'doc2', + 'doc5', + 'doc6', + ); + }); + + // TODO (enterprise) wait for implicit sort order support + it.skipEnterprise('supports OR queries with array membership', async () => { + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: [0]}, + doc2: {b: [1]}, + doc3: {a: 3, b: [2, 7]}, + doc4: {a: 1, b: [3, 7]}, + doc5: {a: 1}, + doc6: {a: 2}, + }); + + // Query: a==2 || b array-contains 7 + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.or( + Filter.where('a', '==', 2), + Filter.where('b', 'array-contains', 7), + ), + ), + ), + 'doc3', + 'doc4', + 'doc6', + ); + + // a==2 || b array-contains-any [0, 3] + // Has implicit "orderBy b" + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.or( + Filter.where('a', '==', 2), + Filter.where('b', 'array-contains-any', [0, 3]), + ), + ), + ), + 'doc1', + 'doc4', + 'doc6', + ); + }); + + describe('watch', () => { + interface ExpectedChange { + type: string; + doc: DocumentSnapshot; + } + + const currentDeferred = new DeferredPromise(); + + const snapshot = (id: string, data: DocumentData) => { + const ref = randomCol.doc(id); + const fields = ref.firestore._serializer!.encodeFields(data); + return randomCol.firestore.snapshot_( + { + name: + 'projects/ignored/databases/(default)/documents/' + + ref._path.relativeName, + fields, + createTime: {seconds: 0, nanos: 0}, + updateTime: {seconds: 0, nanos: 0}, + }, + {seconds: 0, nanos: 0}, + ); + }; + + const docChange = ( + type: string, + id: string, + data: DocumentData, + ): ExpectedChange => { + return { + type, + doc: snapshot(id, data), + }; + }; + + const added = (id: string, data: DocumentData) => + docChange('added', id, data); + const modified = (id: string, data: DocumentData) => + docChange('modified', id, data); + const removed = (id: string, data: DocumentData) => + docChange('removed', id, data); + + function resetPromise() { + currentDeferred.promise = new Promise((resolve, reject) => { + currentDeferred.resolve = resolve; + currentDeferred.reject = reject; + }); + } + + function waitForSnapshot(): Promise { + return currentDeferred.promise!.then(snapshot => { + resetPromise(); + return snapshot; + }); + } + + function snapshotsEqual( + actual: QuerySnapshot, + expected: {docs: DocumentSnapshot[]; docChanges: ExpectedChange[]}, + ) { + let i; + expect(actual.size).to.equal(expected.docs.length); + for (i = 0; i < expected.docs.length && i < actual.size; i++) { + expect(actual.docs[i].ref.id).to.equal(expected.docs[i].ref.id); + expect(actual.docs[i].data()).to.deep.equal(expected.docs[i].data()); + } + const actualDocChanges = actual.docChanges(); + expect(actualDocChanges.length).to.equal(expected.docChanges.length); + for (i = 0; i < expected.docChanges.length; i++) { + expect(actualDocChanges[i].type).to.equal(expected.docChanges[i].type); + expect(actualDocChanges[i].doc.ref.id).to.equal( + expected.docChanges[i].doc.ref.id, + ); + expect(actualDocChanges[i].doc.data()).to.deep.equal( + expected.docChanges[i].doc.data(), + ); + expect(actualDocChanges[i].doc.readTime).to.exist; + expect(actualDocChanges[i].doc.createTime).to.exist; + expect(actualDocChanges[i].doc.updateTime).to.exist; + } + expect(actual.readTime).to.exist; + } + + beforeEach(() => resetPromise()); + + it('handles changing a doc', () => { + const ref1 = randomCol.doc('doc1'); + const ref2 = randomCol.doc('doc2'); + + const unsubscribe = randomCol.onSnapshot( + snapshot => { + currentDeferred.resolve(snapshot); + }, + err => { + currentDeferred.reject!(err); + }, + ); + + return waitForSnapshot() + .then(results => { + snapshotsEqual(results, {docs: [], docChanges: []}); + // Add a result. + return ref1.set({foo: 'a'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [snapshot('doc1', {foo: 'a'})], + docChanges: [added('doc1', {foo: 'a'})], + }); + // Add another result. + return ref2.set({foo: 'b'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [snapshot('doc1', {foo: 'a'}), snapshot('doc2', {foo: 'b'})], + docChanges: [added('doc2', {foo: 'b'})], + }); + // Change a result. + return ref2.set({bar: 'c'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [snapshot('doc1', {foo: 'a'}), snapshot('doc2', {bar: 'c'})], + docChanges: [modified('doc2', {bar: 'c'})], + }); + unsubscribe(); + }); + }); + + it("handles changing a doc so it doesn't match", () => { + const ref1 = randomCol.doc('doc1'); + const ref2 = randomCol.doc('doc2'); + + const query = randomCol.where('included', '==', 'yes'); + const unsubscribe = query.onSnapshot( + snapshot => { + currentDeferred.resolve(snapshot); + }, + err => { + currentDeferred.reject(err); + }, + ); + + return waitForSnapshot() + .then(results => { + snapshotsEqual(results, {docs: [], docChanges: []}); + // Add a result. + return ref1.set({included: 'yes'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [snapshot('doc1', {included: 'yes'})], + docChanges: [added('doc1', {included: 'yes'})], + }); + // Add another result. + return ref2.set({included: 'yes'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [ + snapshot('doc1', {included: 'yes'}), + snapshot('doc2', {included: 'yes'}), + ], + docChanges: [added('doc2', {included: 'yes'})], + }); + // Change a result. + return ref2.set({included: 'no'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [snapshot('doc1', {included: 'yes'})], + docChanges: [removed('doc2', {included: 'yes'})], + }); + unsubscribe(); + }); + }); + + it('handles deleting a doc', () => { + const ref1 = randomCol.doc('doc1'); + const ref2 = randomCol.doc('doc2'); + + const unsubscribe = randomCol.onSnapshot( + snapshot => { + currentDeferred.resolve(snapshot); + }, + err => { + currentDeferred.reject(err); + }, + ); + + return waitForSnapshot() + .then(results => { + snapshotsEqual(results, {docs: [], docChanges: []}); + // Add a result. + return ref1.set({included: 'yes'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [snapshot('doc1', {included: 'yes'})], + docChanges: [added('doc1', {included: 'yes'})], + }); + // Add another result. + return ref2.set({included: 'yes'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [ + snapshot('doc1', {included: 'yes'}), + snapshot('doc2', {included: 'yes'}), + ], + docChanges: [added('doc2', {included: 'yes'})], + }); + // Delete a result. + return ref2.delete(); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [snapshot('doc1', {included: 'yes'})], + docChanges: [removed('doc2', {included: 'yes'})], + }); + unsubscribe(); + }); + }); + + it('orders limitToLast() correctly', async () => { + const ref1 = randomCol.doc('doc1'); + const ref2 = randomCol.doc('doc2'); + const ref3 = randomCol.doc('doc3'); + + await ref1.set({doc: 1}); + await ref2.set({doc: 2}); + await ref3.set({doc: 3}); + + const unsubscribe = randomCol + .orderBy('doc') + .limitToLast(2) + .onSnapshot(snapshot => currentDeferred.resolve(snapshot)); + + const results = await waitForSnapshot(); + snapshotsEqual(results, { + docs: [snapshot('doc2', {doc: 2}), snapshot('doc3', {doc: 3})], + docChanges: [added('doc2', {doc: 2}), added('doc3', {doc: 3})], + }); + + unsubscribe(); + }); + + it('SDK orders vector field same way as backend', async () => { + // We validate that the SDK orders the vector field the same way as the backend + // by comparing the sort order of vector fields from a Query.get() and + // Query.onSnapshot(). Query.onSnapshot() will return sort order of the SDK, + // and Query.get() will return sort order of the backend. + + // Test data in the order that we expect the backend to sort it. + const docsInOrder = [ + {embedding: [1, 2, 3, 4, 5, 6]}, + {embedding: [100]}, + {embedding: FieldValue.vector([Number.NEGATIVE_INFINITY])}, + {embedding: FieldValue.vector([-100])}, + {embedding: FieldValue.vector([100])}, + {embedding: FieldValue.vector([Number.POSITIVE_INFINITY])}, + {embedding: FieldValue.vector([1, 2])}, + {embedding: FieldValue.vector([2, 2])}, + {embedding: FieldValue.vector([1, 2, 3])}, + {embedding: FieldValue.vector([1, 2, 3, 4])}, + {embedding: FieldValue.vector([1, 2, 3, 4, 5])}, + {embedding: FieldValue.vector([1, 2, 100, 4, 4])}, + {embedding: FieldValue.vector([100, 2, 3, 4, 5])}, + {embedding: {HELLO: 'WORLD'}}, + {embedding: {hello: 'world'}}, + ]; + + const expectedSnapshots = []; + const expectedChanges = []; + + for (let i = 0; i < docsInOrder.length; i++) { + const dr = await randomCol.add(docsInOrder[i]); + expectedSnapshots.push(snapshot(dr.id, docsInOrder[i])); + expectedChanges.push(added(dr.id, docsInOrder[i])); + } + + const orderedQuery = randomCol.orderBy('embedding'); + + const unsubscribe = orderedQuery.onSnapshot( + snapshot => { + currentDeferred.resolve(snapshot); + }, + err => { + currentDeferred.reject!(err); + }, + ); + + const watchSnapshot = await waitForSnapshot(); + unsubscribe(); + + const getSnapshot = await orderedQuery.get(); + + // Compare the snapshot (including sort order) of a snapshot + // from Query.onSnapshot() to an actual snapshot from Query.get() + snapshotsEqual(watchSnapshot, { + docs: getSnapshot.docs, + docChanges: getSnapshot.docChanges(), + }); + + // Compare the snapshot (including sort order) of a snapshot + // from Query.onSnapshot() to the expected sort order from + // the backend. + snapshotsEqual(watchSnapshot, { + docs: expectedSnapshots, + docChanges: expectedChanges, + }); + }); + }); + + (process.env.FIRESTORE_EMULATOR_HOST === undefined + ? describe.skip + : describe)('multiple inequality', () => { + it('supports multiple inequality queries', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 0, v: 0}, + doc2: {key: 'b', sort: 3, v: 1}, + doc3: {key: 'c', sort: 1, v: 3}, + doc4: {key: 'd', sort: 2, v: 2}, + }); + + // Multiple inequality fields + let results = await compareQueryAndPipeline( + collection + .where('key', '!=', 'a') + .where('sort', '<=', 2) + .where('v', '>', 2), + ); + expectDocs(results, 'doc3'); + + // Duplicate inequality fields + results = await compareQueryAndPipeline( + collection + .where('key', '!=', 'a') + .where('sort', '<=', 2) + .where('sort', '>', 1), + ); + expectDocs(results, 'doc4'); + + // With multiple IN + results = await compareQueryAndPipeline( + collection + .where('key', '>=', 'a') + .where('sort', '<=', 2) + .where('v', 'in', [2, 3, 4]) + .where('sort', 'in', [2, 3]), + ); + expectDocs(results, 'doc4'); + + // With NOT-IN + results = await compareQueryAndPipeline( + collection + .where('key', '>=', 'a') + .where('sort', '<=', 2) + .where('v', 'not-in', [2, 4, 5]), + ); + expectDocs(results, 'doc1', 'doc3'); + + // With orderby + results = await compareQueryAndPipeline( + collection + .where('key', '>=', 'a') + .where('sort', '<=', 2) + .orderBy('v', 'desc'), + ); + expectDocs(results, 'doc3', 'doc4', 'doc1'); + + // With limit + results = await compareQueryAndPipeline( + collection + .where('key', '>=', 'a') + .where('sort', '<=', 2) + .orderBy('v', 'desc') + .limit(2), + ); + expectDocs(results, 'doc3', 'doc4'); + + // With limitToLast + results = await compareQueryAndPipeline( + collection + .where('key', '>=', 'a') + .where('sort', '<=', 2) + .orderBy('v', 'desc') + .limitToLast(2), + ); + expectDocs(results, 'doc4', 'doc1'); + }); + + it('can use on special values', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 0, v: 0}, + doc2: {key: 'b', sort: NaN, v: 1}, + doc3: {key: 'c', sort: null, v: 3}, + doc4: {key: 'd', v: 0}, + doc5: {key: 'e', sort: 1}, + doc6: {key: 'f', sort: 1, v: 1}, + }); + + let results = await compareQueryAndPipeline( + collection.where('key', '!=', 'a').where('sort', '<=', 2), + ); + expectDocs(results, 'doc5', 'doc6'); + + results = await compareQueryAndPipeline( + collection + .where('key', '!=', 'a') + .where('sort', '<=', 2) + .where('v', '<=', 1), + ); + expectDocs(results, 'doc6'); + }); + + it('can use with array membership', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 0, v: [0]}, + doc2: {key: 'b', sort: 1, v: [0, 1, 3]}, + doc3: {key: 'c', sort: 1, v: []}, + doc4: {key: 'd', sort: 2, v: [1]}, + doc5: {key: 'e', sort: 3, v: [2, 4]}, + doc6: {key: 'f', sort: 4, v: [NaN]}, + doc7: {key: 'g', sort: 4, v: [null]}, + }); + + let results = await compareQueryAndPipeline( + collection + .where('key', '!=', 'a') + .where('sort', '>=', 1) + .where('v', 'array-contains', 0), + ); + expectDocs(results, 'doc2'); + + results = await compareQueryAndPipeline( + collection + .where('key', '!=', 'a') + .where('sort', '>=', 1) + .where('v', 'array-contains-any', [0, 1]), + ); + expectDocs(results, 'doc2', 'doc4'); + }); + + // Use cursor in following test cases to add implicit order by fields in the sdk and compare the + // result with the query fields normalized in the server. + it('can use with nested field', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const testData = (n?: number): any => { + n = n || 1; + return { + name: 'room ' + n, + metadata: { + createdAt: n, + }, + field: 'field ' + n, + 'field.dot': n, + 'field\\slash': n, + }; + }; + + const collection = await testCollectionWithDocs({ + doc1: testData(400), + doc2: testData(200), + doc3: testData(100), + doc4: testData(300), + }); + + // ordered by: name asc, metadata.createdAt asc, __name__ asc + let query = collection + .where('metadata.createdAt', '<=', 500) + .where('metadata.createdAt', '>', 100) + .where('name', '!=', 'room 200') + .orderBy('name'); + let docSnap = await collection.doc('doc4').get(); + let queryWithCursor = query.startAt(docSnap); + expectDocs(await compareQueryAndPipeline(query), 'doc4', 'doc1'); + expectDocs(await queryWithCursor.get(), 'doc4', 'doc1'); + + // ordered by: name desc, field desc, field.dot desc, field\\slash desc, __name__ desc + query = collection + .where('field', '>=', 'field 100') + .where(new FieldPath('field.dot'), '!=', 300) + .where('field\\slash', '<', 400) + .orderBy('name', 'desc'); + docSnap = await collection.doc('doc2').get(); + queryWithCursor = query.startAt(docSnap); + expectDocs(await compareQueryAndPipeline(query), 'doc2', 'doc3'); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc3'); + }); + + it('can use with nested composite filters', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 0, v: 5}, + doc2: {key: 'aa', sort: 4, v: 4}, + doc3: {key: 'c', sort: 3, v: 3}, + doc4: {key: 'b', sort: 2, v: 2}, + doc5: {key: 'b', sort: 2, v: 1}, + doc6: {key: 'b', sort: 0, v: 0}, + }); + + // Implicitly ordered by: 'key' asc, 'sort' asc, 'v' asc, __name__ asc + let query = collection.where( + Filter.or( + Filter.and( + Filter.where('key', '==', 'b'), + Filter.where('sort', '<=', 2), + ), + Filter.and(Filter.where('key', '!=', 'b'), Filter.where('v', '>', 4)), + ), + ); + let docSnap = await collection.doc('doc1').get(); + let queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc1', + 'doc6', + 'doc5', + 'doc4', + ); + expectDocs(await queryWithCursor.get(), 'doc1', 'doc6', 'doc5', 'doc4'); + + // Ordered by: 'sort' desc, 'key' asc, 'v' asc, __name__ asc + query = collection + .where( + Filter.or( + Filter.and( + Filter.where('key', '==', 'b'), + Filter.where('sort', '<=', 2), + ), + Filter.and( + Filter.where('key', '!=', 'b'), + Filter.where('v', '>', 4), + ), + ), + ) + .orderBy('sort', 'desc') + .orderBy('key'); + docSnap = await collection.doc('doc5').get(); + queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc5', + 'doc4', + 'doc1', + 'doc6', + ); + expectDocs(await queryWithCursor.get(), 'doc5', 'doc4', 'doc1', 'doc6'); + + // Implicitly ordered by: 'key' asc, 'sort' asc, 'v' asc, __name__ asc + query = collection.where( + Filter.and( + Filter.or( + Filter.and( + Filter.where('key', '==', 'b'), + Filter.where('sort', '<=', 4), + ), + Filter.and( + Filter.where('key', '!=', 'b'), + Filter.where('v', '>=', 4), + ), + ), + Filter.or( + Filter.and( + Filter.where('key', '>', 'b'), + Filter.where('sort', '>=', 1), + ), + Filter.and( + Filter.where('key', '<', 'b'), + Filter.where('v', '>', 0), + ), + ), + ), + ); + docSnap = await collection.doc('doc1').get(); + queryWithCursor = query.startAt(docSnap); + expectDocs(await compareQueryAndPipeline(query), 'doc1', 'doc2'); + expectDocs(await queryWithCursor.get(), 'doc1', 'doc2'); + }); + + it('inequality fields will be implicitly ordered lexicographically by the server', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 0, v: 5}, + doc2: {key: 'aa', sort: 4, v: 4}, + doc3: {key: 'b', sort: 3, v: 3}, + doc4: {key: 'b', sort: 2, v: 2}, + doc5: {key: 'b', sort: 2, v: 1}, + doc6: {key: 'b', sort: 0, v: 0}, + }); + + const docSnap = await collection.doc('doc2').get(); + + // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc + let query = collection + .where('key', '!=', 'a') + .where('sort', '>', 1) + .where('v', 'in', [1, 2, 3, 4]); + let queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc2', + 'doc4', + 'doc5', + 'doc3', + ); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc4', 'doc5', 'doc3'); + + // Changing filters order will not effect implicit order. + // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc + query = collection + .where('sort', '>', 1) + .where('key', '!=', 'a') + .where('v', 'in', [1, 2, 3, 4]); + queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc2', + 'doc4', + 'doc5', + 'doc3', + ); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc4', 'doc5', 'doc3'); + }); + + it('can use multiple explicit order by field', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 5, v: 0}, + doc2: {key: 'aa', sort: 4, v: 0}, + doc3: {key: 'b', sort: 3, v: 1}, + doc4: {key: 'b', sort: 2, v: 1}, + doc5: {key: 'bb', sort: 1, v: 1}, + doc6: {key: 'c', sort: 0, v: 2}, + }); + + let docSnap = await collection.doc('doc2').get(); + + // Ordered by: 'v' asc, 'key' asc, 'sort' asc, __name__ asc + let query = collection + .where('key', '>', 'a') + .where('sort', '>=', 1) + .orderBy('v'); + let queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc2', + 'doc4', + 'doc3', + 'doc5', + ); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc4', 'doc3', 'doc5'); + + // Ordered by: 'v asc, 'sort' asc, 'key' asc, __name__ asc + query = collection + .where('key', '>', 'a') + .where('sort', '>=', 1) + .orderBy('v') + .orderBy('sort'); + queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc2', + 'doc5', + 'doc4', + 'doc3', + ); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc5', 'doc4', 'doc3'); + + docSnap = await collection.doc('doc5').get(); + + // Implicit order by matches the direction of last explicit order by. + // Ordered by: 'v' desc, 'key' desc, 'sort' desc, __name__ desc + query = collection + .where('key', '>', 'a') + .where('sort', '>=', 1) + .orderBy('v', 'desc'); + queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc5', + 'doc3', + 'doc4', + 'doc2', + ); + expectDocs(await queryWithCursor.get(), 'doc5', 'doc3', 'doc4', 'doc2'); + + // Ordered by: 'v desc, 'sort' asc, 'key' asc, __name__ asc + query = collection + .where('key', '>', 'a') + .where('sort', '>=', 1) + .orderBy('v', 'desc') + .orderBy('sort'); + queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc5', + 'doc4', + 'doc3', + 'doc2', + ); + expectDocs(await queryWithCursor.get(), 'doc5', 'doc4', 'doc3', 'doc2'); + }); + + it('can use in aggregate query', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 5, v: 0}, + doc2: {key: 'aa', sort: 4, v: 0}, + doc3: {key: 'b', sort: 3, v: 1}, + doc4: {key: 'b', sort: 2, v: 1}, + doc5: {key: 'bb', sort: 1, v: 1}, + }); + + const results = await collection + .where('key', '>', 'a') + .where('sort', '>=', 1) + .orderBy('v') + .count() + .get(); + expect(results.data().count).to.be.equal(4); + //TODO(MIEQ): Add sum and average when they are public. + }); + + it('can use document ID im multiple inequality query', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 5}, + doc2: {key: 'aa', sort: 4}, + doc3: {key: 'b', sort: 3}, + doc4: {key: 'b', sort: 2}, + doc5: {key: 'bb', sort: 1}, + }); + + const docSnap = await collection.doc('doc2').get(); + + // Document Key in inequality field will implicitly ordered to the last. + // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc + let query = collection + .where('sort', '>=', 1) + .where('key', '!=', 'a') + .where(FieldPath.documentId(), '<', 'doc5'); + let queryWithCursor = query.startAt(docSnap); + expectDocs(await compareQueryAndPipeline(query), 'doc2', 'doc4', 'doc3'); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc4', 'doc3'); + + // Changing filters order will not effect implicit order. + // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc + query = collection + .where(FieldPath.documentId(), '<', 'doc5') + .where('sort', '>=', 1) + .where('key', '!=', 'a'); + queryWithCursor = query.startAt(docSnap); + expectDocs(await compareQueryAndPipeline(query), 'doc2', 'doc4', 'doc3'); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc4', 'doc3'); + + // Ordered by: 'sort' desc,'key' desc, __name__ desc + query = collection + .where(FieldPath.documentId(), '<', 'doc5') + .where('sort', '>=', 1) + .where('key', '!=', 'a') + .orderBy('sort', 'desc'); + queryWithCursor = query.startAt(docSnap); + expectDocs(await compareQueryAndPipeline(query), 'doc2', 'doc3', 'doc4'); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc3', 'doc4'); + }); + }); +}); diff --git a/dev/system-test/tracing.ts b/dev/system-test/tracing.ts index 3b7a520bd..8f18613aa 100644 --- a/dev/system-test/tracing.ts +++ b/dev/system-test/tracing.ts @@ -93,9 +93,11 @@ const REST_TEST_SUITE_TITLE = 'REST'; diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); // Enable Firestore debug messages for local debugging. -setLogFunction((msg: string) => { - console.log(`LOG: ${msg}`); -}); +if (process.env.NODE_ENV === 'DEBUG') { + setLogFunction((msg: string) => { + console.log(`LOG: ${msg}`); + }); +} interface TestConfig { // In-Memory tests check trace correctness by inspecting traces in memory by @@ -154,7 +156,7 @@ class SpanData { } } -describe('Tracing Tests', () => { +describe.skipEnterprise('Tracing Tests', () => { let firestore: Firestore; let tracerProvider: NodeTracerProvider; let inMemorySpanExporter: InMemorySpanExporter; @@ -869,7 +871,8 @@ describe('Tracing Tests', () => { ); }); - it('collection reference list documents', async () => { + // Enterprise: field mask is not supported + it.skipEnterprise('collection reference list documents', async () => { await runFirestoreOperationInRootSpan(() => firestore.collection('foo').listDocuments(), ); @@ -961,7 +964,8 @@ describe('Tracing Tests', () => { expectSpanHierarchy(SPAN_NAME_TEST_ROOT, SPAN_NAME_BATCH_COMMIT); }); - it('partition query', async () => { + // Service not implemented for Enterprise DB: PartitionQuery + it.skipEnterprise('partition query', async () => { await runFirestoreOperationInRootSpan(async () => { const query = firestore.collectionGroup('foo'); let numPartitions = 0; diff --git a/dev/test/options-util.ts b/dev/test/options-util.ts new file mode 100644 index 000000000..348144f77 --- /dev/null +++ b/dev/test/options-util.ts @@ -0,0 +1,227 @@ +/** + * @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 {Serializer} from '../src/serializer'; +import {createInstance} from './util/helpers'; +import {Firestore} from '../src'; +import {OptionsUtil} from '../src/pipelines/options-util'; + +describe('OptionsUtil', () => { + let db: Firestore | undefined; + beforeEach(async () => { + db = await createInstance(); + }); + + afterEach(async () => { + if (db) { + await db.terminate(); + db = undefined; + } + }); + + it('should support known options', () => { + const optionsUtil = new OptionsUtil({ + fooBar: { + serverName: 'foo_bar', + }, + }); + const proto = optionsUtil.getOptionsProto(new Serializer(db!), { + fooBar: 'recommended', + }); + + expect(proto).deep.equal({ + foo_bar: { + stringValue: 'recommended', + }, + }); + }); + + it('should support unknown options', () => { + const optionsUtil = new OptionsUtil({}); + const proto = optionsUtil.getOptionsProto( + new Serializer(db!), + {}, + {baz: 'foo'}, + ); + + expect(proto).to.deep.equal({ + baz: { + stringValue: 'foo', + }, + }); + }); + + it('should support unknown nested options', () => { + const optionsUtil = new OptionsUtil({}); + const proto = optionsUtil.getOptionsProto( + new Serializer(db!), + {}, + {'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( + new Serializer(db!), + { + 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( + new Serializer(db!), + { + 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( + new Serializer(db!), + { + 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( + new Serializer(db!), + { + 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/dev/test/path.ts b/dev/test/path.ts index 95d151ee9..a42e9f6e9 100644 --- a/dev/test/path.ts +++ b/dev/test/path.ts @@ -45,12 +45,46 @@ describe('ResourcePath', () => { }); it('parses strings', () => { + // parse reference to db root let path = QualifiedResourcePath.fromSlashSeparatedString(DATABASE_ROOT); expect(path.formattedName).to.equal(`${DATABASE_ROOT}/documents`); + expect(path.isCollection).to.equal(false); + expect(path.isDocument).to.equal(false); + + // parse reference to db root with `/documents` + path = QualifiedResourcePath.fromSlashSeparatedString( + `${DATABASE_ROOT}/documents`, + ); + expect(path.formattedName).to.equal(`${DATABASE_ROOT}/documents`); + expect(path.isCollection).to.equal(false); + expect(path.isDocument).to.equal(false); + + // parse reference to collection path = QualifiedResourcePath.fromSlashSeparatedString( `${DATABASE_ROOT}/documents/foo`, ); expect(path.formattedName).to.equal(`${DATABASE_ROOT}/documents/foo`); + expect(path.isCollection).to.equal(true); + expect(path.isDocument).to.equal(false); + + // parse reference to document + path = QualifiedResourcePath.fromSlashSeparatedString( + `${DATABASE_ROOT}/documents/foo/bar`, + ); + expect(path.formattedName).to.equal(`${DATABASE_ROOT}/documents/foo/bar`); + expect(path.isCollection).to.equal(false); + expect(path.isDocument).to.equal(true); + + // parse reference to nested collection + path = QualifiedResourcePath.fromSlashSeparatedString( + `${DATABASE_ROOT}/documents/foo/bar/baz`, + ); + expect(path.formattedName).to.equal( + `${DATABASE_ROOT}/documents/foo/bar/baz`, + ); + expect(path.isCollection).to.equal(true); + expect(path.isDocument).to.equal(false); + expect(() => { path = QualifiedResourcePath.fromSlashSeparatedString( 'projects/project/databases', @@ -58,6 +92,26 @@ describe('ResourcePath', () => { }).to.throw("Resource name 'projects/project/databases' is not valid"); }); + it('does not parse invalid paths', () => { + const invalidPaths: string[] = [ + 'projects/PPP/databases/DDD/wrong', + 'projects/PPP/databases/DDD//', + 'projects/PPP/databases/DDD/documentsBAD', + 'projects/PPP/databases/DDD/documents//', + 'projects/PPP/databases/DDD/documents/ok//ok', + 'projects/PPP/databases//DDD/documents', + 'projects/PPP/databases/DDD/documents/', + 'projects/PPP/databases//documents', + 'projects//databases/DDD/documents', + ]; + + invalidPaths.forEach(invalidPath => { + expect(() => { + QualifiedResourcePath.fromSlashSeparatedString(invalidPath); + }).to.throw(`Resource name '${invalidPath}' is not valid`); + }); + }); + it('accepts newlines', () => { const path = QualifiedResourcePath.fromSlashSeparatedString( `${DATABASE_ROOT}/documents/foo\nbar`, diff --git a/dev/test/pipelines/pipeline.ts b/dev/test/pipelines/pipeline.ts new file mode 100644 index 000000000..c9e759ce0 --- /dev/null +++ b/dev/test/pipelines/pipeline.ts @@ -0,0 +1,429 @@ +/** + * @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 {createInstance, stream} from '../util/helpers'; +import {google} from '../../protos/firestore_v1_proto_api'; +import {Timestamp, Pipelines, Firestore} from '../../src'; +import IExecutePipelineRequest = google.firestore.v1.IExecutePipelineRequest; +import IExecutePipelineResponse = google.firestore.v1.IExecutePipelineResponse; +import Pipeline = Pipelines.Pipeline; +import field = Pipelines.field; +import sum = Pipelines.sum; +import descending = Pipelines.descending; +import IValue = google.firestore.v1.IValue; + +const FIRST_CALL = 0; +const EXECUTE_PIPELINE_REQUEST = 0; + +describe('execute(Pipeline|PipelineExecuteOptions)', () => { + it('returns execution time with empty results', async () => { + const executeTime = Timestamp.now(); + const results: IExecutePipelineResponse[] = [ + { + executionTime: executeTime.toProto().timestampValue, + results: [], + }, + ]; + + const firestore = await createInstance({ + executePipeline: () => stream(...results), + }); + + const pipelineSnapshot = await firestore + .pipeline() + .collection('foo') + .execute(); + + expect(pipelineSnapshot.results.length).to.equal(0); + + expect(pipelineSnapshot.executionTime.toProto()).to.deep.equal( + executeTime.toProto(), + ); + }); + + it('serializes the pipeline', async () => { + const spy = sinon.fake.returns(stream()); + const firestore = await createInstance({ + executePipeline: spy, + }); + + await firestore.pipeline().collection('foo').execute(); + + const executePipelineRequest: IExecutePipelineRequest = { + database: 'projects/test-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 spy = sinon.fake.returns(stream()); + const firestore = await createInstance({ + executePipeline: spy, + }); + + await firestore + .pipeline() + .collection('foo') + .execute({ + indexMode: 'recommended', + explainOptions: { + mode: 'analyze', + }, + }); + + const executePipelineRequest: IExecutePipelineRequest = { + database: 'projects/test-project/databases/(default)', + structuredPipeline: { + options: { + index_mode: { + stringValue: 'recommended', + }, + explain_options: { + mapValue: { + fields: { + mode: { + stringValue: 'analyze', + }, + }, + }, + }, + }, + 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 spy = sinon.fake.returns(stream()); + const firestore = await createInstance({ + executePipeline: spy, + }); + + await firestore + .pipeline() + .collection('foo') + .execute({ + rawOptions: { + foo: 'bar', + }, + }); + + const executePipelineRequest: IExecutePipelineRequest = { + database: 'projects/test-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, + ); + }); +}); + +describe('stage option serialization', () => { + // Default rawOptions + const rawOptions: Record = { + foo: 'bar1', + }; + // Default expected serialized options + const expectedSerializedOptions: Record = { + foo: { + stringValue: 'bar1', + }, + }; + + const testDefinitions: Array<{ + name: string; + pipeline: (firestore: Firestore) => Pipeline; + stageIndex?: number; + expectedOptions?: Record; + }> = [ + { + name: 'collection stage', + pipeline: firestore => + firestore.pipeline().collection({ + collection: 'foo', + rawOptions, + forceIndex: 'foo-index', + }), + expectedOptions: { + ...expectedSerializedOptions, + force_index: { + stringValue: 'foo-index', + }, + }, + }, + { + name: 'collection group stage', + pipeline: firestore => + firestore.pipeline().collectionGroup({ + collectionId: 'foo', + rawOptions, + forceIndex: 'bar-index', + }), + expectedOptions: { + ...expectedSerializedOptions, + force_index: { + stringValue: 'bar-index', + }, + }, + }, + { + name: 'documents stage', + pipeline: firestore => + firestore.pipeline().documents({ + docs: ['foo/bar'], + rawOptions, + }), + }, + { + name: 'database stage', + pipeline: firestore => + firestore.pipeline().database({ + rawOptions, + }), + }, + { + name: 'distinct stage', + pipeline: firestore => + firestore + .pipeline() + .database() + .distinct({ + groups: ['foo'], + rawOptions, + }), + stageIndex: 1, + }, + { + name: 'findNearest stage', + pipeline: firestore => + firestore + .pipeline() + .database() + .findNearest({ + field: 'foo', + vectorValue: [0], + distanceMeasure: 'euclidean', + rawOptions, + }), + stageIndex: 1, + }, + { + name: 'select stage', + pipeline: firestore => + firestore + .pipeline() + .database() + .select({ + selections: ['foo'], + rawOptions, + }), + stageIndex: 1, + }, + { + name: 'unnest stage', + pipeline: firestore => + firestore + .pipeline() + .database() + .unnest({ + selectable: field('foo'), + rawOptions, + }), + stageIndex: 1, + }, + { + name: 'addFields stage', + pipeline: firestore => + firestore + .pipeline() + .database() + .addFields({ + fields: [field('foo')], + rawOptions, + }), + stageIndex: 1, + }, + { + name: 'aggregate stage', + pipeline: firestore => + firestore + .pipeline() + .database() + .aggregate({ + accumulators: [sum('foo').as('fooSum')], + rawOptions, + }), + stageIndex: 1, + }, + { + name: 'limit stage', + pipeline: firestore => + firestore.pipeline().database().limit({ + limit: 1, + rawOptions, + }), + stageIndex: 1, + }, + { + name: 'offset stage', + pipeline: firestore => + firestore.pipeline().database().offset({ + offset: 1, + rawOptions, + }), + stageIndex: 1, + }, + { + name: 'removeFields stage', + pipeline: firestore => + firestore + .pipeline() + .database() + .removeFields({ + fields: ['foo'], + rawOptions, + }), + stageIndex: 1, + }, + { + name: 'replaceWith stage', + pipeline: firestore => + firestore.pipeline().database().replaceWith({ + map: 'foo', + rawOptions, + }), + stageIndex: 1, + }, + { + name: 'sample stage', + pipeline: firestore => + firestore.pipeline().database().sample({ + documents: 100, + rawOptions, + }), + stageIndex: 1, + }, + { + name: 'sample stage', + pipeline: firestore => + firestore + .pipeline() + .database() + .sort({ + orderings: [descending('foo')], + rawOptions, + }), + stageIndex: 1, + }, + { + name: 'union stage', + pipeline: firestore => + firestore.pipeline().database().union({ + other: firestore.pipeline().database(), + rawOptions, + }), + stageIndex: 1, + }, + { + name: 'where stage', + pipeline: firestore => + firestore + .pipeline() + .database() + .where({ + condition: field('foo').equal(1), + rawOptions, + }), + stageIndex: 1, + }, + ]; + + testDefinitions.forEach(testDefinition => { + it(testDefinition.name, async () => { + const spy = sinon.fake.returns(stream()); + const firestore = await createInstance({ + executePipeline: spy, + }); + + await testDefinition.pipeline(firestore).execute(); + + const expectedOptions = testDefinition.expectedOptions + ? testDefinition.expectedOptions + : expectedSerializedOptions; + + expect( + spy.args[FIRST_CALL][EXECUTE_PIPELINE_REQUEST]['structuredPipeline'][ + 'pipeline' + ]['stages'][testDefinition.stageIndex ?? 0]['options'], + ).to.deep.equal(expectedOptions); + }); + }); +}); diff --git a/dev/test/serializer.ts b/dev/test/serializer.ts index 9391de215..46ed3639b 100644 --- a/dev/test/serializer.ts +++ b/dev/test/serializer.ts @@ -14,7 +14,10 @@ import {it} from 'mocha'; import {expect} from 'chai'; -import {validateUserInput} from '../src/serializer'; +import * as sinon from 'sinon'; +import {validateUserInput, Serializer} from '../src/serializer'; +import {DocumentReference, Firestore} from '../src'; +import {SinonStubbedInstance} from 'sinon'; describe('validateUserInput', () => { it('validates the depth of nested objects and arrays - 20', () => { @@ -244,3 +247,56 @@ describe('validateUserInput', () => { ).to.throw(/Input object is deeper than 20 levels/i); }); }); + +describe('serializer', () => { + const PROJECT_ID = 'test-project'; + const DATABASE_ROOT = `projects/${PROJECT_ID}/databases/(default)`; + + let serializer: Serializer | undefined; + let firestoreStub: SinonStubbedInstance | undefined; + + const mockResult = {}; + + beforeEach(() => { + firestoreStub = sinon.stub({ + doc: (_: string) => { + return mockResult; + }, + _settings: {}, + } as Firestore); + firestoreStub.doc.returns(mockResult as DocumentReference); + + serializer = new Serializer(firestoreStub); + }); + + describe('decodeValue', () => { + it('decodes reference to document', () => { + const result = serializer!.decodeValue({ + referenceValue: `${DATABASE_ROOT}/documents/foo/bar`, + }) as DocumentReference; + + expect(result).to.equal(mockResult); + expect(firestoreStub!.doc.calledOnceWith('foo/bar')).to.be.true; + }); + + it('throws when given a reference to collection', () => { + expect(() => { + serializer!.decodeValue({ + referenceValue: `${DATABASE_ROOT}/documents/foo`, + }) as DocumentReference; + }).to.throw( + 'The SDK does not currently support decoding referenceValues for collections or partitions.', + ); + }); + + it('throws when given a reference to db root', () => { + expect(() => { + serializer!.decodeValue({ + referenceValue: `${DATABASE_ROOT}/documents`, + }) as DocumentReference; + }).to.throw( + 'The SDK does not currently support decoding referenceValues for collections or partitions.', + ); + }); + }); +}); diff --git a/dev/test/structured-pipeline.ts b/dev/test/structured-pipeline.ts new file mode 100644 index 000000000..036e3fd3d --- /dev/null +++ b/dev/test/structured-pipeline.ts @@ -0,0 +1,181 @@ +/** + * @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 {ProtoSerializable, Serializer} from '../src/serializer'; +import {google} from '../protos/firestore_v1_proto_api'; +import IPipeline = google.firestore.v1.IPipeline; +import {StructuredPipeline} from '../src/pipelines/structured-pipeline'; +import {createInstance} from './util/helpers'; +import {Firestore} from '../src'; + +describe('StructuredPipeline', () => { + let db: Firestore | undefined; + beforeEach(async () => { + db = await createInstance(); + }); + + afterEach(async () => { + if (db) { + await db.terminate(); + db = undefined; + } + }); + + it('should serialize the pipeline argument', async () => { + const pipeline: ProtoSerializable = { + _toProto: sinon.fake.returns({} as IPipeline), + }; + const structuredPipeline = new StructuredPipeline(pipeline, {}, {}); + + const proto = structuredPipeline._toProto(new Serializer(db!)); + + 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 IPipeline), + }; + const structuredPipeline = new StructuredPipeline( + pipeline, + { + indexMode: 'recommended', + explainOptions: { + mode: 'explain', + outputFormat: 'text', + }, + }, + {}, + ); + + const proto = structuredPipeline._toProto(new Serializer(db!)); + + expect(proto).deep.equal({ + pipeline: {}, + options: { + index_mode: { + stringValue: 'recommended', + }, + explain_options: { + mapValue: { + fields: { + mode: { + stringValue: 'explain', + }, + output_format: { + stringValue: 'text', + }, + }, + }, + }, + }, + }); + + expect((pipeline._toProto as sinon.SinonSpy).calledOnce).to.be.true; + }); + + it('should support unknown options', () => { + const pipeline: ProtoSerializable = { + _toProto: sinon.fake.returns({} as IPipeline), + }; + const structuredPipeline = new StructuredPipeline( + pipeline, + {}, + { + foo_bar: 'baz', + }, + ); + + const proto = structuredPipeline._toProto(new Serializer(db!)); + + 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 IPipeline), + }; + const structuredPipeline = new StructuredPipeline( + pipeline, + {}, + { + 'foo.bar': 'baz', + }, + ); + + const proto = structuredPipeline._toProto(new Serializer(db!)); + + 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 IPipeline), + }; + const structuredPipeline = new StructuredPipeline( + pipeline, + { + indexMode: 'recommended', + }, + { + index_mode: 'baz', + }, + ); + + const proto = structuredPipeline._toProto(new Serializer(db!)); + + expect(proto).to.deep.equal({ + pipeline: {}, + options: { + index_mode: { + stringValue: 'baz', + }, + }, + }); + + expect((pipeline._toProto as sinon.SinonSpy).calledOnce).to.be.true; + }); +}); diff --git a/dev/test/util/helpers.ts b/dev/test/util/helpers.ts index 0bef94950..b10c81c5d 100644 --- a/dev/test/util/helpers.ts +++ b/dev/test/util/helpers.ts @@ -467,3 +467,10 @@ export function isPreferRest(): boolean { process.env.FIRESTORE_PREFER_REST === 'true' ); } + +/** + * Returns a value indicating whether the tests are running against an Enterprise edition DB + */ +export function isEnterprise(): boolean { + return !!process.env.RUN_ENTERPRISE_TESTS; +} diff --git a/dev/test/util/mocha_extensions.ts b/dev/test/util/mocha_extensions.ts new file mode 100644 index 000000000..15532ba95 --- /dev/null +++ b/dev/test/util/mocha_extensions.ts @@ -0,0 +1,103 @@ +/** + * @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. + */ + +/* eslint-disable no-restricted-properties */ + +import {describe, it} from 'mocha'; + +// Helper to make a type itselt (T) and optionally union that with (T['skip']) +type tOrSkipT = T | (T extends {skip: unknown} ? T['skip'] : T); + +declare module 'mocha' { + // eslint-disable-next-line @typescript-eslint/no-namespace + interface TestFunction { + skipEnterprise: tOrSkipT; + skipEmulator: tOrSkipT; + skipClassic: tOrSkipT; + } + + interface PendingTestFunction { + skipEnterprise: tOrSkipT; + skipEmulator: tOrSkipT; + skipClassic: tOrSkipT; + } + + interface SuiteFunction { + skipEnterprise: tOrSkipT; + skipEmulator: tOrSkipT; + skipClassic: tOrSkipT; + } + + interface PendingSuiteFunction { + skipEnterprise: tOrSkipT; + skipEmulator: tOrSkipT; + skipClassic: tOrSkipT; + } +} + +// Define helpers +export function mixinSkipImplementations(obj: unknown): void { + 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, 'skipEmulator', { + get(): unknown { + if (this === it.skip) { + return this; + } + if (this === describe.skip) { + return this; + } + if (process.env.FIRESTORE_EMULATOR_HOST) { + 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.describe, it, it.skip, describe, describe.skip].forEach( + mixinSkipImplementations, +); diff --git a/dev/test/watch.ts b/dev/test/watch.ts index a92dba391..a4ba584bd 100644 --- a/dev/test/watch.ts +++ b/dev/test/watch.ts @@ -2418,7 +2418,7 @@ describe('DocumentReference watch', () => { streamHelper.write({ documentChange: { document: { - name: `projects/${PROJECT_ID}/databases/(default)/col/wrong`, + name: `projects/${PROJECT_ID}/databases/(default)/documents/col/wrong`, fields: {}, createTime: {seconds: 1, nanos: 2}, updateTime: {seconds: 3, nanos: 4}, diff --git a/package.json b/package.json index d0d2c9e56..36c5aa3b8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@google-cloud/firestore", "description": "Firestore Client Library for Node.js", - "version": "8.0.0", + "version": "8.1.0-eap-pipelines.2", "license": "Apache-2.0", "author": "Google Inc.", "engines": { @@ -14,7 +14,8 @@ "build/protos", "build/src", "!build/src/**/*.map", - "types" + "types", + "pipelines" ], "keywords": [ "google apis client", @@ -91,6 +92,7 @@ "chai-as-promised": "^7.1.2", "codecov": "^3.8.3", "duplexify": "^4.1.3", + "eslint-plugin-node": "^11.1.0", "execa": "^9.6.0", "extend": "^3.0.2", "fs-extra": "^11.3.0", @@ -111,5 +113,17 @@ "through2": "^4.0.2", "ts-node": "^10.9.2", "typescript": "^5.9.2" + }, + "exports": { + ".": { + "types": "./types/firestore.d.ts", + "default": "./build/src/index.js" + }, + "./pipelines": { + "types": "./types/pipelines.d.ts", + "default": "./build/src/pipelines/index.js" + }, + "./package.json": "./package.json", + "./build/src/path": "./build/src/path.js" } } diff --git a/pipelines/package.json b/pipelines/package.json new file mode 100644 index 000000000..1515c5eb2 --- /dev/null +++ b/pipelines/package.json @@ -0,0 +1,10 @@ +{ + "name": "@google-cloud/firestore/pipelines", + "description": "pipelines", + "main": "../build/src/pipelines/index.js", + "typings": "../types/pipelines.d.ts", + "private": true, + "engines": { + "node": ">=14.0.0" + } +} diff --git a/samples/README.md b/samples/README.md index 3a6b9d555..36bf3ae3f 100644 --- a/samples/README.md +++ b/samples/README.md @@ -21,6 +21,7 @@ Applications that use Google's Server SDKs should not be used in end-user e * [Before you begin](#before-you-begin) * [Samples](#samples) * [Limit-to-last-query](#limit-to-last-query) + * [Pipelines-quickstart](#pipelines-quickstart) * [Quickstart](#quickstart) * [Solution-counters](#solution-counters) @@ -56,6 +57,23 @@ __Usage:__ +### Pipelines-quickstart + +View the [source code](https://github.com/googleapis/nodejs-firestore/blob/main/samples/pipelines-quickstart.js). + +[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-firestore&page=editor&open_in_editor=samples/pipelines-quickstart.js,samples/README.md) + +__Usage:__ + + +`node samples/pipelines-quickstart.js` + + +----- + + + + ### Quickstart View the [source code](https://github.com/googleapis/nodejs-firestore/blob/main/samples/quickstart.js). diff --git a/samples/pipelines-quickstart.js b/samples/pipelines-quickstart.js new file mode 100644 index 000000000..552dca1bc --- /dev/null +++ b/samples/pipelines-quickstart.js @@ -0,0 +1,58 @@ +// 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. + +'use strict'; + +/* eslint-disable n/no-missing-require */ + +// [START firestore_pipelines_quickstart] +const {Firestore} = require('@google-cloud/firestore'); +const {field} = require('@google-cloud/firestore/pipelines'); + +// Create a new client +const firestore = new Firestore(); + +async function quickstartPipelines() { + // Obtain a collection reference. + const collection = firestore.collection('posts'); + + // Create a few new posts + for (let i = 0; i < 5; i++) { + await collection.add({ + title: `Post ${i}`, + rating: Math.random() * 10, // random rating on a 10 point scale + }); + } + console.log('Entered new data into the collection'); + + // Create a Pipeline that queries the 'posts' collection. + // Select the fields 'rating' and 'title', and convert the title to uppercase. + // Filter the results to only include posts with rating > 5. + const myPipeline = firestore + .pipeline() + .collection('posts') + .select('rating', field('title').toUpper().as('lowercaseTitle')) + .where(field('rating').greaterThan(5)); + + // Execute the Pipeline against the Firestore server. + const pipelineSnapshot = await myPipeline.execute(); + + // Iterate over each result in the PipelineSnapshot, printing the + // post to the console. + pipelineSnapshot.results.forEach(pipelineResult => { + console.log(pipelineResult.data()); + }); +} +quickstartPipelines(); +// [END firestore_pipelines_quickstart] diff --git a/types/firestore.d.ts b/types/firestore.d.ts index 34ca19faf..512e91f15 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -13,24 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - // We deliberately use `any` in the external API to not impose type-checking // on end users. /* eslint-disable @typescript-eslint/no-explicit-any */ - // Declare a global (ambient) namespace // (used when not using import statement, but just script include). - declare namespace FirebaseFirestore { /** Alias for `any` but used where a Firestore field value would be provided. */ export type DocumentFieldValue = any; - /** * Document data (for use with `DocumentReference.set()`) consists of fields * mapped to values. */ - export type DocumentData = {[field: string]: DocumentFieldValue}; - + export type DocumentData = { + [field: string]: DocumentFieldValue; + }; /** * Similar to Typescript's `Partial`, but allows nested fields to be * omitted and FieldValues to be passed in as property values. @@ -46,7 +43,6 @@ declare namespace FirebaseFirestore { : PartialWithFieldValue | FieldValue; } : never); - /** * Allows FieldValues to be passed in as a property value while maintaining * type safety. @@ -62,7 +58,6 @@ declare namespace FirebaseFirestore { : WithFieldValue | FieldValue; } : never); - /** * Update data (for use with [update]{@link DocumentReference#update}) * that contains paths mapped to values. Fields that contain dots reference @@ -105,7 +100,6 @@ declare namespace FirebaseFirestore { /** Primitive types. */ export type Primitive = string | number | boolean | undefined | null; - /** * For each field (e.g. 'bar'), find all nested keys (e.g. {'bar.baz': T1, * 'bar.qux': T2}). Intersect them together to make a single map containing @@ -123,7 +117,6 @@ declare namespace FirebaseFirestore { : ChildUpdateFields; }[keyof T & string] // Also include the generated prefix-string keys. >; - /** * Helper for calculating the nested fields for a given type T1. This is needed * to distribute union types such as `undefined | {...}` (happens for optional @@ -140,9 +133,7 @@ declare namespace FirebaseFirestore { ? // Recurse into the map and add the prefix in front of each key // (for example prefix 'bar.' to create: 'bar.baz' and 'bar.qux'). AddPrefixToKeys> - : // UpdateData is always a map of values. - never; - + : never; /** * Returns a new map where every key is prefixed with the outer key appended * to a dot. @@ -182,7 +173,20 @@ declare namespace FirebaseFirestore { ) extends (k: infer I) => void ? I : never; - + /** + * 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]; /** * Sets or disables the log function for all active Firestore instances. * @@ -190,7 +194,6 @@ declare namespace FirebaseFirestore { * `null` to turn off logging. */ function setLogFunction(logger: ((msg: string) => void) | null): void; - /** * Converter used by `withConverter()` to transform user objects of type * `AppModelType` into Firestore data of type `DbModelType`. @@ -393,7 +396,6 @@ declare namespace FirebaseFirestore { toFirestore( modelObject: WithFieldValue, ): WithFieldValue; - /** * Called by the Firestore SDK to convert a custom model object of type * `AppModelType` into a plain Javascript object (suitable for writing @@ -411,7 +413,6 @@ declare namespace FirebaseFirestore { modelObject: PartialWithFieldValue, options: SetOptions, ): PartialWithFieldValue; - /** * Called by the Firestore SDK to convert Firestore data into an object of * type `AppModelType`. You can access your data by calling: @@ -427,7 +428,6 @@ declare namespace FirebaseFirestore { */ fromFirestore(snapshot: QueryDocumentSnapshot): AppModelType; } - /** * Settings used to directly configure a `Firestore` instance. */ @@ -440,18 +440,14 @@ declare namespace FirebaseFirestore { * Default Credentials} */ projectId?: string; - /** * The database name. If omitted, the default database will be used. */ databaseId?: string; - /** The hostname to connect to. */ host?: string; - /** The port to connect to. */ port?: number; - /** * Local file containing the Service Account credentials as downloaded from * the Google Developers Console. Can be omitted in environments that @@ -461,7 +457,6 @@ declare namespace FirebaseFirestore { * `private_key` of your service account. */ keyFilename?: string; - /** * The 'client_email' and 'private_key' properties of the service account * to use with your Firestore project. Can be omitted in environments that @@ -469,11 +464,12 @@ declare namespace FirebaseFirestore { * Default Credentials}. If your credentials are stored in a JSON file, you * can specify a `keyFilename` instead. */ - credentials?: {client_email?: string; private_key?: string}; - + credentials?: { + client_email?: string; + private_key?: string; + }; /** Whether to use SSL when connecting. */ ssl?: boolean; - /** * The maximum number of idle GRPC channels to keep. A smaller number of idle * channels reduces memory usage but increases request latency for clients @@ -481,7 +477,6 @@ declare namespace FirebaseFirestore { * when the client becomes idle. Defaults to 1. */ maxIdleChannels?: number; - /** * Whether to use `BigInt` for integer types when deserializing Firestore * Documents. Regardless of magnitude, all integer values are returned as @@ -489,7 +484,6 @@ declare namespace FirebaseFirestore { * numbers continue to use JavaScript's `number` type. */ useBigInt?: boolean; - /** * Whether to skip nested properties that are set to `undefined` during * object serialization. If set to `true`, these properties are skipped @@ -497,7 +491,6 @@ declare namespace FirebaseFirestore { * an exception when it encounters properties of type `undefined`. */ ignoreUndefinedProperties?: boolean; - /** * Whether to force the use of HTTP/1.1 REST transport until a method that requires gRPC * is called. When a method requires gRPC, this Firestore client will load dependent gRPC @@ -507,16 +500,13 @@ declare namespace FirebaseFirestore { * or `Query.onSnapshot()`. */ preferRest?: boolean; - /** * Settings related to telemetry collection by this client. * @beta */ openTelemetry?: FirestoreOpenTelemetryOptions; - [key: string]: any; // Accept other properties, such as GRPC settings. } - /** * Options to configure telemetry collection. * This is a 'beta' interface and may change in backwards incompatible ways. @@ -533,7 +523,6 @@ declare namespace FirebaseFirestore { */ tracerProvider?: any; } - /** Options to configure a read-only transaction. */ export interface ReadOnlyTransactionOptions { /** Set to true to indicate a read-only transaction. */ @@ -545,7 +534,6 @@ declare namespace FirebaseFirestore { */ readTime?: Timestamp; } - /** Options to configure a read-write transaction. */ export interface ReadWriteTransactionOptions { /** Set to false or omit to indicate a read-write transaction. */ @@ -555,7 +543,6 @@ declare namespace FirebaseFirestore { */ maxAttempts?: number; } - /** * `Firestore` represents a Firestore Database and is the entry point for all * Firestore operations. @@ -566,7 +553,6 @@ declare namespace FirebaseFirestore { * {@link https://firebase.google.com/docs/firestore/} */ public constructor(settings?: Settings); - /** * Specifies custom settings to be used to configure the `Firestore` * instance. Can only be invoked once and before any other Firestore @@ -580,12 +566,10 @@ declare namespace FirebaseFirestore { * operations. */ settings(settings: Settings): void; - /** * Returns the Database ID for this Firestore instance. */ get databaseId(): string; - /** * Gets a `CollectionReference` instance that refers to the collection at * the specified path. @@ -594,7 +578,6 @@ declare namespace FirebaseFirestore { * @return The `CollectionReference` instance. */ collection(collectionPath: string): CollectionReference; - /** * Gets a `DocumentReference` instance that refers to the document at the * specified path. @@ -603,7 +586,6 @@ declare namespace FirebaseFirestore { * @return The `DocumentReference` instance. */ doc(documentPath: string): DocumentReference; - /** * Creates and returns a new Query that includes all documents in the * database that are contained in a collection or subcollection with the @@ -615,7 +597,6 @@ declare namespace FirebaseFirestore { * @return The created `CollectionGroup`. */ collectionGroup(collectionId: string): CollectionGroup; - /** * Retrieves multiple documents from Firestore. * @@ -632,7 +613,6 @@ declare namespace FirebaseFirestore { getAll( ...documentRefsOrReadOptions: Array ): Promise>; - /** * Recursively deletes all documents and subcollections at and under the * specified level. @@ -672,14 +652,12 @@ declare namespace FirebaseFirestore { ref: CollectionReference | DocumentReference, bulkWriter?: BulkWriter, ): Promise; - /** * Terminates the Firestore client and closes all open streams. * * @return A Promise that resolves when the client is terminated. */ terminate(): Promise; - /** * Fetches the root collections that are associated with this Firestore * database. @@ -687,7 +665,6 @@ declare namespace FirebaseFirestore { * @returns A Promise that resolves with an array of CollectionReferences. */ listCollections(): Promise>; - /** * Executes the given updateFunction and commits the changes applied within * the transaction. @@ -732,13 +709,11 @@ declare namespace FirebaseFirestore { | ReadWriteTransactionOptions | ReadOnlyTransactionOptions, ): Promise; - /** * Creates a write batch, used for performing multiple writes as a single * atomic operation. */ batch(): WriteBatch; - /** * Creates a [BulkWriter]{@link BulkWriter}, used for performing * multiple writes in parallel. Gradually ramps up writes as specified @@ -750,7 +725,6 @@ declare namespace FirebaseFirestore { * behavior for the underlying BulkWriter. */ bulkWriter(options?: BulkWriterOptions): BulkWriter; - /** * Creates a new `BundleBuilder` instance to package selected Firestore data into * a bundle. @@ -771,8 +745,20 @@ declare namespace FirebaseFirestore { * // Save `bundleBuffer` to CDN or stream it to clients. */ bundle(bundleId?: string): BundleBuilder; + /** + * Define a pipeline query. Every pipeline is defined as a series of stages + * starting with a source stage. + * + * @example + * ``` + * let goodBooksPipeline: Pipeline = + * myFirestore.pipeline() + * .collection('books') + * .where(field('rating').greaterThan(4)); + * ``` + */ + pipeline(): Pipelines.PipelineSource; } - /** * An immutable object representing a geo point in Firestore. The geo point * is represented as latitude/longitude pair. @@ -788,10 +774,8 @@ declare namespace FirebaseFirestore { * @param longitude The longitude as number between -180 and 180. */ constructor(latitude: number, longitude: number); - readonly latitude: number; readonly longitude: number; - /** * Returns true if this `GeoPoint` is equal to the provided one. * @@ -800,7 +784,6 @@ declare namespace FirebaseFirestore { */ isEqual(other: GeoPoint): boolean; } - /** * A reference to a transaction. * The `Transaction` object passed to a transaction's updateFunction provides @@ -809,7 +792,6 @@ declare namespace FirebaseFirestore { */ export class Transaction { private constructor(); - /** * Retrieves a query result. Holds a pessimistic lock on all returned * documents. @@ -820,7 +802,6 @@ declare namespace FirebaseFirestore { get( query: Query, ): Promise>; - /** * Reads the document referenced by the provided `DocumentReference.` * Holds a pessimistic lock on the returned document. @@ -831,7 +812,6 @@ declare namespace FirebaseFirestore { get( documentRef: DocumentReference, ): Promise>; - /** * Retrieves an aggregate query result. Holds a pessimistic lock on all * documents that were matched by the underlying query. @@ -852,7 +832,6 @@ declare namespace FirebaseFirestore { ): Promise< AggregateQuerySnapshot >; - /** * Retrieves multiple documents from Firestore. Holds a pessimistic lock on * all returned documents. @@ -872,7 +851,6 @@ declare namespace FirebaseFirestore { DocumentReference | ReadOptions > ): Promise>>; - /** * Create the document referred to by the provided `DocumentReference`. * The operation will fail the transaction if a document exists at the @@ -887,7 +865,6 @@ declare namespace FirebaseFirestore { documentRef: DocumentReference, data: WithFieldValue, ): Transaction; - /** * Writes to the document referred to by the provided `DocumentReference`. * If the document does not exist yet, it will be created. If you pass @@ -916,7 +893,6 @@ declare namespace FirebaseFirestore { documentRef: DocumentReference, data: WithFieldValue, ): Transaction; - /** * Updates fields in the document referred to by the provided * `DocumentReference`. The update will fail if applied to a document that @@ -937,7 +913,6 @@ declare namespace FirebaseFirestore { data: UpdateData, precondition?: Precondition, ): Transaction; - /** * Updates fields in the document referred to by the provided * `DocumentReference`. The update will fail if applied to a document that @@ -964,7 +939,6 @@ declare namespace FirebaseFirestore { value: any, ...fieldsOrPrecondition: any[] ): Transaction; - /** * Deletes the document referred to by the provided `DocumentReference`. * @@ -976,8 +950,40 @@ declare namespace FirebaseFirestore { documentRef: DocumentReference, precondition?: Precondition, ): Transaction; + /** + * 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 in a {@link PipelineSnapshot} object, which 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 futureResults = await transaction + * .execute( + * firestore.pipeline().collection("books") + * .where(greaterThan(Field.of("rating"), 4.5)) + * .select("title", "author", "rating")); + * ``` + * + * @return A Promise representing the asynchronous pipeline execution. + */ + execute(pipeline: Pipelines.Pipeline): Promise; } - /** * A Firestore BulkWriter than can be used to perform a large number of writes * in parallel. Writes to the same document will be executed sequentially. @@ -986,7 +992,6 @@ declare namespace FirebaseFirestore { */ export class BulkWriter { private constructor(); - /** * Create a document with the provided data. This single operation will fail * if a document exists at its location. @@ -1003,7 +1008,6 @@ declare namespace FirebaseFirestore { documentRef: DocumentReference, data: WithFieldValue, ): Promise; - /** * Delete a document from the database. * @@ -1024,7 +1028,6 @@ declare namespace FirebaseFirestore { documentRef: DocumentReference, precondition?: Precondition, ): Promise; - /** * Write to the document referred to by the provided * [DocumentReference]{@link DocumentReference}. If the document does not @@ -1058,7 +1061,6 @@ declare namespace FirebaseFirestore { documentRef: DocumentReference, data: WithFieldValue, ): Promise; - /** * Update fields of the document referred to by the provided * [DocumentReference]{@link DocumentReference}. If the document doesn't yet @@ -1088,7 +1090,6 @@ declare namespace FirebaseFirestore { data: UpdateData, precondition?: Precondition, ): Promise; - /** * Update fields of the document referred to by the provided * [DocumentReference]{@link DocumentReference}. If the document doesn't yet @@ -1121,7 +1122,6 @@ declare namespace FirebaseFirestore { value: any, ...fieldsOrPrecondition: any[] ): Promise; - /** * Attaches a listener that is run every time a BulkWriter operation * successfully completes. @@ -1135,7 +1135,6 @@ declare namespace FirebaseFirestore { result: WriteResult, ) => void, ): void; - /** * Attaches an error handler listener that is run every time a BulkWriter * operation fails. @@ -1151,7 +1150,6 @@ declare namespace FirebaseFirestore { onWriteError( shouldRetryCallback: (error: BulkWriterError) => boolean, ): void; - /** * Commits all writes that have been enqueued up to this point in parallel. * @@ -1168,7 +1166,6 @@ declare namespace FirebaseFirestore { * up to this point have been committed. */ flush(): Promise; - /** * Commits all enqueued writes and marks the BulkWriter instance as closed. * @@ -1186,7 +1183,6 @@ declare namespace FirebaseFirestore { */ close(): Promise; } - /** * An options object to configure throttling on BulkWriter. */ @@ -1210,29 +1206,26 @@ declare namespace FirebaseFirestore { */ readonly throttling?: | boolean - | {initialOpsPerSecond?: number; maxOpsPerSecond?: number}; + | { + initialOpsPerSecond?: number; + maxOpsPerSecond?: number; + }; } - /** * The error thrown when a BulkWriter operation fails. */ export class BulkWriterError extends Error { /** The status code of the error. */ readonly code: GrpcStatus; - /** The error message of the error. */ readonly message: string; - /** The document reference the operation was performed on. */ readonly documentRef: DocumentReference; - /** The type of operation performed. */ readonly operationType: 'create' | 'set' | 'update' | 'delete'; - /** How many times this operation has been attempted unsuccessfully. */ readonly failedAttempts: number; } - /** * A write batch, used to perform multiple writes as a single atomic unit. * @@ -1246,7 +1239,6 @@ declare namespace FirebaseFirestore { */ export class WriteBatch { private constructor(); - /** * Create the document referred to by the provided `DocumentReference`. The * operation will fail the batch if a document exists at the specified @@ -1261,7 +1253,6 @@ declare namespace FirebaseFirestore { documentRef: DocumentReference, data: WithFieldValue, ): WriteBatch; - /** * Write to the document referred to by the provided `DocumentReference`. * If the document does not exist yet, it will be created. If you pass @@ -1290,7 +1281,6 @@ declare namespace FirebaseFirestore { documentRef: DocumentReference, data: WithFieldValue, ): WriteBatch; - /** * Update fields of the document referred to by the provided * `DocumentReference`. If the document doesn't yet exist, the update fails @@ -1311,7 +1301,6 @@ declare namespace FirebaseFirestore { data: UpdateData, precondition?: Precondition, ): WriteBatch; - /** * Updates fields in the document referred to by the provided * `DocumentReference`. The update will fail if applied to a document that @@ -1338,7 +1327,6 @@ declare namespace FirebaseFirestore { value: any, ...fieldsOrPrecondition: any[] ): WriteBatch; - /** * Deletes the document referred to by the provided `DocumentReference`. * @@ -1350,7 +1338,6 @@ declare namespace FirebaseFirestore { documentRef: DocumentReference, precondition?: Precondition, ): WriteBatch; - /** * Commits all of the writes in this write batch as a single atomic unit. * @@ -1359,7 +1346,6 @@ declare namespace FirebaseFirestore { */ commit(): Promise; } - /** * An options object that configures conditional behavior of `update()` and * `delete()` calls in `DocumentReference`, `WriteBatch`, and `Transaction`. @@ -1371,13 +1357,11 @@ declare namespace FirebaseFirestore { * If set, the last update time to enforce. */ readonly lastUpdateTime?: Timestamp; - /** * If set, enforces that the target document must or must not exist. */ readonly exists?: boolean; } - /** * An options object that configures the behavior of `set()` calls in * `DocumentReference`, `WriteBatch` and `Transaction`. These calls can be @@ -1401,7 +1385,6 @@ declare namespace FirebaseFirestore { | { readonly mergeFields?: Array; }; - /** * An options object that can be used to configure the behavior of `getAll()` * calls. By providing a `fieldMask`, these calls can be configured to only @@ -1418,19 +1401,16 @@ declare namespace FirebaseFirestore { */ readonly fieldMask?: (string | FieldPath)[]; } - /** * A WriteResult wraps the write time set by the Firestore servers on `sets()`, * `updates()`, and `creates()`. */ export class WriteResult { private constructor(); - /** * The write time as set by the Firestore servers. */ readonly writeTime: Timestamp; - /** * Returns true if this `WriteResult` is equal to the provided one. * @@ -1439,7 +1419,6 @@ declare namespace FirebaseFirestore { */ isEqual(other: WriteResult): boolean; } - /** * 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 @@ -1451,27 +1430,22 @@ declare namespace FirebaseFirestore { DbModelType extends DocumentData = DocumentData, > { private constructor(); - /** The identifier of the document within its collection. */ readonly id: string; - /** * The `Firestore` for the Firestore database (useful for performing * transactions, etc.). */ readonly firestore: Firestore; - /** * A reference to the Collection to which this DocumentReference belongs. */ readonly parent: CollectionReference; - /** * A string representing the path of the referenced document (relative * to the root of the database). */ readonly path: string; - /** * Gets a `CollectionReference` instance that refers to the collection at * the specified path. @@ -1480,14 +1454,12 @@ declare namespace FirebaseFirestore { * @return The `CollectionReference` instance. */ collection(collectionPath: string): CollectionReference; - /** * Fetches the subcollections that are direct children of this document. * * @returns A Promise that resolves with an array of CollectionReferences. */ listCollections(): Promise>; - /** * Creates a document referred to by this `DocumentReference` with the * provided object values. The write fails if the document already exists @@ -1497,7 +1469,6 @@ declare namespace FirebaseFirestore { * @return A Promise resolved with the write time of this create. */ create(data: WithFieldValue): Promise; - /** * Writes to the document referred to by this `DocumentReference`. If the * document does not yet exist, it will be created. If you pass @@ -1521,7 +1492,6 @@ declare namespace FirebaseFirestore { options: SetOptions, ): Promise; set(data: WithFieldValue): Promise; - /** * Updates fields in the document referred to by this `DocumentReference`. * The update will fail if applied to a document that does not exist. @@ -1539,7 +1509,6 @@ declare namespace FirebaseFirestore { data: UpdateData, precondition?: Precondition, ): Promise; - /** * Updates fields in the document referred to by this `DocumentReference`. * The update will fail if applied to a document that does not exist. @@ -1563,7 +1532,6 @@ declare namespace FirebaseFirestore { value: any, ...moreFieldsOrPrecondition: any[] ): Promise; - /** * Deletes the document referred to by this `DocumentReference`. * @@ -1571,7 +1539,6 @@ declare namespace FirebaseFirestore { * @return A Promise resolved with the write time of this delete. */ delete(precondition?: Precondition): Promise; - /** * Reads the document referred to by this `DocumentReference`. * @@ -1579,7 +1546,6 @@ declare namespace FirebaseFirestore { * current document contents. */ get(): Promise>; - /** * Attaches a listener for DocumentSnapshot events. * @@ -1594,7 +1560,6 @@ declare namespace FirebaseFirestore { onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: Error) => void, ): () => void; - /** * Returns true if this `DocumentReference` is equal to the provided one. * @@ -1602,7 +1567,6 @@ declare namespace FirebaseFirestore { * @return true if this `DocumentReference` is equal to the provided one. */ isEqual(other: DocumentReference): boolean; - /** * Applies a custom data converter to this DocumentReference, allowing you * to use your own custom model objects with Firestore. When you call @@ -1618,11 +1582,9 @@ declare namespace FirebaseFirestore { NewAppModelType, NewDbModelType extends DocumentData = DocumentData, >( - converter: FirestoreDataConverter, + converter: FirestoreDataConverter | null, ): DocumentReference; - withConverter(converter: null): DocumentReference; } - /** * A `DocumentSnapshot` contains data read from a document in your Firestore * database. The data can be extracted with `.data()` or `.get()` to @@ -1637,35 +1599,28 @@ declare namespace FirebaseFirestore { DbModelType extends DocumentData = DocumentData, > { protected constructor(); - /** True if the document exists. */ readonly exists: boolean; - /** A `DocumentReference` to the document location. */ readonly ref: DocumentReference; - /** * The ID of the document for which this `DocumentSnapshot` contains data. */ readonly id: string; - /** * The time the document was created. Not set for documents that don't * exist. */ readonly createTime?: Timestamp; - /** * The time the document was last updated (at the time the snapshot was * generated). Not set for documents that don't exist. */ readonly updateTime?: Timestamp; - /** * The time this snapshot was read. */ readonly readTime: Timestamp; - /** * Retrieves all fields in the document as an Object. Returns 'undefined' if * the document doesn't exist. @@ -1673,7 +1628,6 @@ declare namespace FirebaseFirestore { * @return An Object containing all fields in the document. */ data(): AppModelType | undefined; - /** * Retrieves the field specified by `fieldPath`. * @@ -1682,7 +1636,6 @@ declare namespace FirebaseFirestore { * field exists in the document. */ get(fieldPath: string | FieldPath): any; - /** * Returns true if the document's data and path in this `DocumentSnapshot` * is equal to the provided one. @@ -1692,7 +1645,6 @@ declare namespace FirebaseFirestore { */ isEqual(other: DocumentSnapshot): boolean; } - /** * A `QueryDocumentSnapshot` contains data read from a document in your * Firestore database as part of a query. The document is guaranteed to exist @@ -1709,18 +1661,15 @@ declare namespace FirebaseFirestore { DbModelType extends DocumentData = DocumentData, > extends DocumentSnapshot { private constructor(); - /** * The time the document was created. */ readonly createTime: Timestamp; - /** * The time the document was last updated (at the time the snapshot was * generated). */ readonly updateTime: Timestamp; - /** * Retrieves all fields in the document as an Object. * @@ -1729,13 +1678,11 @@ declare namespace FirebaseFirestore { */ data(): AppModelType; } - /** * The direction of a `Query.orderBy()` clause is specified as 'desc' or 'asc' * (descending or ascending). */ export type OrderByDirection = 'desc' | 'asc'; - /** * Filter conditions in a `Query.where()` clause are specified using the * strings '<', '<=', '==', '!=', '>=', '>', 'array-contains', 'in', 'not-in', @@ -1752,7 +1699,6 @@ declare namespace FirebaseFirestore { | 'in' | 'not-in' | 'array-contains-any'; - /** * 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. @@ -1762,13 +1708,11 @@ declare namespace FirebaseFirestore { DbModelType extends DocumentData = DocumentData, > { protected constructor(); - /** * The `Firestore` for the Firestore database (useful for performing * transactions, etc.). */ readonly firestore: Firestore; - /** * Creates and returns a new Query with the additional filter that documents * must contain the specified field and that its value should satisfy the @@ -1787,7 +1731,6 @@ declare namespace FirebaseFirestore { opStr: WhereFilterOp, value: any, ): Query; - /** * Creates and returns a new [Query]{@link Query} with the additional filter * that documents should satisfy the relation constraint provided. Documents @@ -1800,7 +1743,6 @@ declare namespace FirebaseFirestore { * @returns {Query} The created Query. */ where(filter: Filter): Query; - /** * Creates and returns a new Query that's additionally sorted by the * specified field, optionally in descending order instead of ascending. @@ -1817,7 +1759,6 @@ declare namespace FirebaseFirestore { fieldPath: string | FieldPath, directionStr?: OrderByDirection, ): Query; - /** * Creates and returns a new Query that only returns the first matching * documents. @@ -1829,7 +1770,6 @@ declare namespace FirebaseFirestore { * @return The created Query. */ limit(limit: number): Query; - /** * Creates and returns a new Query that only returns the last matching * documents. @@ -1844,7 +1784,6 @@ declare namespace FirebaseFirestore { * @return The created Query. */ limitToLast(limit: number): Query; - /** * Specifies the offset of the returned results. * @@ -1855,7 +1794,6 @@ declare namespace FirebaseFirestore { * @return The created Query. */ offset(offset: number): Query; - /** * Creates and returns a new Query instance that applies a field mask to * the result and returns only the specified subset of fields. You can @@ -1872,7 +1810,6 @@ declare namespace FirebaseFirestore { * @return The created Query. */ select(...field: (string | FieldPath)[]): Query; - /** * Creates and returns a new Query that starts at the provided document * (inclusive). The starting position is relative to the order of the query. @@ -1885,7 +1822,6 @@ declare namespace FirebaseFirestore { startAt( snapshot: DocumentSnapshot, ): Query; - /** * Creates and returns a new Query that starts at the provided fields * relative to the order of the query. The order of the field values @@ -1896,7 +1832,6 @@ declare namespace FirebaseFirestore { * @return The created Query. */ startAt(...fieldValues: any[]): Query; - /** * Creates and returns a new Query that starts after the provided document * (exclusive). The starting position is relative to the order of the query. @@ -1909,7 +1844,6 @@ declare namespace FirebaseFirestore { startAfter( snapshot: DocumentSnapshot, ): Query; - /** * Creates and returns a new Query that starts after the provided fields * relative to the order of the query. The order of the field values @@ -1920,7 +1854,6 @@ declare namespace FirebaseFirestore { * @return The created Query. */ startAfter(...fieldValues: any[]): Query; - /** * Creates and returns a new Query that ends before the provided document * (exclusive). The end position is relative to the order of the query. The @@ -1933,7 +1866,6 @@ declare namespace FirebaseFirestore { endBefore( snapshot: DocumentSnapshot, ): Query; - /** * Creates and returns a new Query that ends before the provided fields * relative to the order of the query. The order of the field values @@ -1944,7 +1876,6 @@ declare namespace FirebaseFirestore { * @return The created Query. */ endBefore(...fieldValues: any[]): Query; - /** * Creates and returns a new Query that ends at the provided document * (inclusive). The end position is relative to the order of the query. The @@ -1957,7 +1888,6 @@ declare namespace FirebaseFirestore { endAt( snapshot: DocumentSnapshot, ): Query; - /** * Creates and returns a new Query that ends at the provided fields * relative to the order of the query. The order of the field values @@ -1968,14 +1898,12 @@ declare namespace FirebaseFirestore { * @return The created Query. */ endAt(...fieldValues: any[]): Query; - /** * Executes the query and returns the results as a `QuerySnapshot`. * * @return A Promise that will be resolved with the results of the Query. */ get(): Promise>; - /** * Plans and optionally executes this query. Returns a Promise that will be * resolved with the planner information, statistics from the query execution (if any), @@ -1987,14 +1915,12 @@ declare namespace FirebaseFirestore { explain( options?: ExplainOptions, ): Promise>>; - /** * Executes the query and returns the results as Node Stream. * * @return A stream of QueryDocumentSnapshot. */ stream(): NodeJS.ReadableStream; - /** * Plans and optionally executes this query, and streams the results as Node Stream * of `{document?: DocumentSnapshot, metrics?: ExplainMetrics}` objects. @@ -2026,7 +1952,6 @@ declare namespace FirebaseFirestore { * objects. */ explainStream(options?: ExplainOptions): NodeJS.ReadableStream; - /** * Attaches a listener for `QuerySnapshot `events. * @@ -2041,7 +1966,6 @@ declare namespace FirebaseFirestore { onNext: (snapshot: QuerySnapshot) => void, onError?: (error: Error) => void, ): () => void; - /** * Returns a query that counts the documents in the result set of this * query. @@ -2060,11 +1984,12 @@ declare namespace FirebaseFirestore { * returned query. */ count(): AggregateQuery< - {count: AggregateField}, + { + count: AggregateField; + }, AppModelType, DbModelType >; - /** * Returns a query that can perform the given aggregations. * @@ -2098,7 +2023,6 @@ declare namespace FirebaseFirestore { aggregate( aggregateSpec: T, ): AggregateQuery; - /** * Returns a query that can perform vector distance (similarity) search with given parameters. * @@ -2135,7 +2059,6 @@ declare namespace FirebaseFirestore { distanceMeasure: 'EUCLIDEAN' | 'COSINE' | 'DOT_PRODUCT'; }, ): VectorQuery; - /** * Returns a query that can perform vector distance (similarity) search with given parameters. * @@ -2167,7 +2090,37 @@ declare namespace FirebaseFirestore { findNearest( options: VectorQueryOptions, ): VectorQuery; - + /** + * Returns a query that can perform vector distance (similarity) search with given parameters. + * + * The returned query, when executed, performs a distance (similarity) search on the specified + * `vectorField` against the given `queryVector` and returns the top documents that are closest + * to the `queryVector`. + * + * Only documents whose `vectorField` field is a {@link VectorValue} of the same dimension as `queryVector` + * participate in the query, all other documents are ignored. + * + * @example + * ``` + * // Returns the closest 10 documents whose Euclidean distance from their 'embedding' fields are closed to [41, 42]. + * const vectorQuery = col.findNearest({ + * vectorField: 'embedding', + * queryVector: [41, 42], + * limit: 10, + * distanceMeasure: 'EUCLIDEAN', + * distanceResultField: 'distance', + * distanceThreshold: 0.125 + * }); + * + * const querySnapshot = await aggregateQuery.get(); + * querySnapshot.forEach(...); + * ``` + * @param options - An argument specifying the behavior of the {@link VectorQuery} returned by this function. + * See {@link VectorQueryOptions}. + */ + findNearest( + options: VectorQueryOptions, + ): VectorQuery; /** * Returns true if this `Query` is equal to the provided one. * @@ -2175,7 +2128,6 @@ declare namespace FirebaseFirestore { * @return true if this `Query` is equal to the provided one. */ isEqual(other: Query): boolean; - /** * Applies a custom data converter to this Query, allowing you to use your * own custom model objects with Firestore. When you call get() on the @@ -2190,11 +2142,9 @@ declare namespace FirebaseFirestore { NewAppModelType, NewDbModelType extends DocumentData = DocumentData, >( - converter: FirestoreDataConverter, + converter: FirestoreDataConverter | null, ): Query; - withConverter(converter: null): Query; } - /** * A `QuerySnapshot` contains zero or more `QueryDocumentSnapshot` objects * representing the results of a query. The documents can be accessed as an @@ -2207,32 +2157,25 @@ declare namespace FirebaseFirestore { DbModelType extends DocumentData = DocumentData, > { private constructor(); - /** * The query on which you called `get` or `onSnapshot` in order to get this * `QuerySnapshot`. */ readonly query: Query; - /** An array of all the documents in the QuerySnapshot. */ readonly docs: Array>; - /** The number of documents in the QuerySnapshot. */ readonly size: number; - /** True if there are no documents in the QuerySnapshot. */ readonly empty: boolean; - /** The time this query snapshot was obtained. */ readonly readTime: Timestamp; - /** * Returns an array of the documents changes since the last snapshot. If * this is the first snapshot, all documents will be in the list as added * changes. */ docChanges(): DocumentChange[]; - /** * Enumerates all of the documents in the QuerySnapshot. * @@ -2246,7 +2189,6 @@ declare namespace FirebaseFirestore { ) => void, thisArg?: any, ): void; - /** * Returns true if the document data in this `QuerySnapshot` is equal to the * provided one. @@ -2256,7 +2198,6 @@ declare namespace FirebaseFirestore { */ isEqual(other: QuerySnapshot): boolean; } - /** * A `VectorQuerySnapshot` contains zero or more `QueryDocumentSnapshot` objects * representing the results of a query. The documents can be accessed as an @@ -2269,32 +2210,25 @@ declare namespace FirebaseFirestore { DbModelType extends DocumentData = DocumentData, > { private constructor(); - /** * The query on which you called `get` in order to get this * `VectorQuerySnapshot`. */ readonly query: VectorQuery; - /** An array of all the documents in the QuerySnapshot. */ readonly docs: Array>; - /** The number of documents in the QuerySnapshot. */ readonly size: number; - /** True if there are no documents in the QuerySnapshot. */ readonly empty: boolean; - /** The time this query snapshot was obtained. */ readonly readTime: Timestamp; - /** * Returns an array of the documents changes since the last snapshot. If * this is the first snapshot, all documents will be in the list as added * changes. */ docChanges(): DocumentChange[]; - /** * Enumerates all of the documents in the QuerySnapshot. * @@ -2308,7 +2242,6 @@ declare namespace FirebaseFirestore { ) => void, thisArg?: any, ): void; - /** * Returns true if the document data in this `VectorQuerySnapshot` is equal to the * provided one. @@ -2318,12 +2251,10 @@ declare namespace FirebaseFirestore { */ isEqual(other: VectorQuerySnapshot): boolean; } - /** * The type of `DocumentChange` may be 'added', 'removed', or 'modified'. */ export type DocumentChangeType = 'added' | 'removed' | 'modified'; - /** * A `DocumentChange` represents a change to the documents matching a query. * It contains the document affected and the type of change that occurred. @@ -2334,17 +2265,14 @@ declare namespace FirebaseFirestore { > { /** The type of change ('added', 'modified', or 'removed'). */ readonly type: DocumentChangeType; - /** The document affected by this change. */ readonly doc: QueryDocumentSnapshot; - /** * The index of the changed document in the result set immediately prior to * this DocumentChange (i.e. supposing that all prior DocumentChange objects * have been applied). Is -1 for 'added' events. */ readonly oldIndex: number; - /** * The index of the changed document in the result set immediately after * this DocumentChange (i.e. supposing that all prior DocumentChange @@ -2352,7 +2280,6 @@ declare namespace FirebaseFirestore { * Is -1 for 'removed' events. */ readonly newIndex: number; - /** * Returns true if the data in this `DocumentChange` is equal to the * provided one. @@ -2362,7 +2289,6 @@ declare namespace FirebaseFirestore { */ isEqual(other: DocumentChange): boolean; } - /** * A `CollectionReference` object can be used for adding documents, getting * document references, and querying for documents (using the methods @@ -2373,22 +2299,18 @@ declare namespace FirebaseFirestore { DbModelType extends DocumentData = DocumentData, > extends Query { private constructor(); - /** The identifier of the collection. */ readonly id: string; - /** * A reference to the containing Document if this is a subcollection, else * null. */ readonly parent: DocumentReference | null; - /** * A string representing the path of the referenced collection (relative * to the root of the database). */ readonly path: string; - /** * Retrieves the list of documents in this collection. * @@ -2404,7 +2326,6 @@ declare namespace FirebaseFirestore { listDocuments(): Promise< Array> >; - /** * Get a `DocumentReference` for a randomly-named document within this * collection. An automatically-generated unique ID will be used as the @@ -2413,7 +2334,6 @@ declare namespace FirebaseFirestore { * @return The `DocumentReference` instance. */ doc(): DocumentReference; - /** * Get a `DocumentReference` for the document within the collection at the * specified path. @@ -2422,7 +2342,6 @@ declare namespace FirebaseFirestore { * @return The `DocumentReference` instance. */ doc(documentPath: string): DocumentReference; - /** * Add a new document to this collection with the specified data, assigning * it a document ID automatically. @@ -2435,7 +2354,6 @@ declare namespace FirebaseFirestore { add( data: WithFieldValue, ): Promise>; - /** * Returns true if this `CollectionReference` is equal to the provided one. * @@ -2443,7 +2361,6 @@ declare namespace FirebaseFirestore { * @return true if this `CollectionReference` is equal to the provided one. */ isEqual(other: CollectionReference): boolean; - /** * Applies a custom data converter to this CollectionReference, allowing you * to use your own custom model objects with Firestore. When you call add() @@ -2459,11 +2376,9 @@ declare namespace FirebaseFirestore { NewAppModelType, NewDbModelType extends DocumentData = DocumentData, >( - converter: FirestoreDataConverter, + converter: FirestoreDataConverter | null, ): CollectionReference; - withConverter(converter: null): CollectionReference; } - /** * A `CollectionGroup` refers to all documents that are contained in a * collection or subcollection with a specific collection ID. @@ -2473,7 +2388,6 @@ declare namespace FirebaseFirestore { DbModelType extends DocumentData = DocumentData, > extends Query { private constructor(); - /** * Partitions a query by returning partition cursors that can be used to run * the query in parallel. The returned cursors are split points that can be @@ -2487,7 +2401,6 @@ declare namespace FirebaseFirestore { getPartitions( desiredPartitionCount: number, ): AsyncIterable>; - /** * Applies a custom data converter to this `CollectionGroup`, allowing you * to use your own custom model objects with Firestore. When you call get() @@ -2538,11 +2451,9 @@ declare namespace FirebaseFirestore { NewAppModelType, NewDbModelType extends DocumentData = DocumentData, >( - converter: FirestoreDataConverter, + converter: FirestoreDataConverter | null, ): CollectionGroup; - withConverter(converter: null): CollectionGroup; } - /** * A split point that can be used in a query as a starting and/or end point for * the query results. The cursors returned by {@link #startAt} and {@link @@ -2554,7 +2465,6 @@ declare namespace FirebaseFirestore { DbModelType extends DocumentData = DocumentData, > { private constructor(); - /** * The cursor that defines the first result for this partition or * `undefined` if this is the first partition. The cursor value must be @@ -2565,7 +2475,6 @@ declare namespace FirebaseFirestore { * `undefined` if this is the first partition. */ get startAt(): unknown[] | undefined; - /** * The cursor that defines the first result after this partition or * `undefined` if this is the last partition. The cursor value must be @@ -2576,7 +2485,6 @@ declare namespace FirebaseFirestore { * `undefined` if this is the last partition. */ get endBefore(): unknown[] | undefined; - /** * Returns a query that only returns the documents for this partition. * @@ -2585,12 +2493,10 @@ declare namespace FirebaseFirestore { */ toQuery(): Query; } - /** * Union type representing the aggregate type to be performed. */ export type AggregateType = 'count' | 'avg' | 'sum'; - /** * The union of all `AggregateField` types that are supported by Firestore. */ @@ -2598,20 +2504,16 @@ declare namespace FirebaseFirestore { | ReturnType | ReturnType | ReturnType; - /** * Represents an aggregation that can be performed by Firestore. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars export class AggregateField { private constructor(); - /** A type string to uniquely identify instances of this class. */ readonly type = 'AggregateField'; - /** The kind of aggregation performed by this AggregateField. */ public readonly aggregateType: AggregateType; - /** * Compares this object with the given object for equality. * @@ -2623,20 +2525,17 @@ declare namespace FirebaseFirestore { * defined above, or `false` otherwise. */ isEqual(other: AggregateField): boolean; - /** * Create an AggregateField object that can be used to compute the count of * documents in the result set of a query. */ static count(): AggregateField; - /** * Create an AggregateField object that can be used to compute the average of * a specified field over a range of documents in the result set of a query. * @param field Specifies the field to average across the result set. */ static average(field: string | FieldPath): AggregateField; - /** * Create an AggregateField object that can be used to compute the sum of * a specified field over a range of documents in the result set of a query. @@ -2644,14 +2543,12 @@ declare namespace FirebaseFirestore { */ static sum(field: string | FieldPath): AggregateField; } - /** * A type whose property values are all `AggregateField` objects. */ export interface AggregateSpec { [field: string]: AggregateFieldType; } - /** * A type whose keys are taken from an `AggregateSpec`, and whose values are * the result of the aggregation performed by the corresponding @@ -2660,7 +2557,6 @@ declare namespace FirebaseFirestore { export type AggregateSpecData = { [P in keyof T]: T[P] extends AggregateField ? U : never; }; - /** * A query that calculates aggregations over an underlying query. */ @@ -2670,10 +2566,8 @@ declare namespace FirebaseFirestore { DbModelType extends DocumentData = DocumentData, > { private constructor(); - /** The query whose aggregations will be calculated by this object. */ readonly query: Query; - /** * Executes this query. * @@ -2682,7 +2576,6 @@ declare namespace FirebaseFirestore { get(): Promise< AggregateQuerySnapshot >; - /** * Plans and optionally executes this query. Returns a Promise that will be * resolved with the planner information, statistics from the query execution (if any), @@ -2698,7 +2591,6 @@ declare namespace FirebaseFirestore { AggregateQuerySnapshot > >; - /** * Compares this object with the given object for equality. * @@ -2715,7 +2607,6 @@ declare namespace FirebaseFirestore { other: AggregateQuery, ): boolean; } - /** * The results of executing an aggregation query. */ @@ -2725,17 +2616,14 @@ declare namespace FirebaseFirestore { DbModelType extends DocumentData = DocumentData, > { private constructor(); - /** The query that was executed to produce this result. */ readonly query: AggregateQuery< AggregateSpecType, AppModelType, DbModelType >; - /** The time this snapshot was read. */ readonly readTime: Timestamp; - /** * Returns the results of the aggregations performed over the underlying * query. @@ -2748,7 +2636,6 @@ declare namespace FirebaseFirestore { * query. */ data(): AggregateSpecData; - /** * Compares this object with the given object for equality. * @@ -2768,7 +2655,6 @@ declare namespace FirebaseFirestore { >, ): boolean; } - /** * A query that finds the document whose vector fields are closest to a certain vector. */ @@ -2777,17 +2663,14 @@ declare namespace FirebaseFirestore { DbModelType extends DocumentData = DocumentData, > { private constructor(); - /** The query whose results participants in the distance search. */ readonly query: Query; - /** * Executes this query. * * @return A promise that will be resolved with the results of the query. */ get(): Promise>; - /** * Compares this object with the given object for equality. * @@ -2802,31 +2685,26 @@ declare namespace FirebaseFirestore { */ isEqual(other: VectorQuery): boolean; } - /** * Represent a vector type in Firestore documents. */ export class VectorValue { private constructor(values: number[] | undefined); - /** * Returns a copy of the raw number array form of the vector. */ toArray(): number[]; - /** * Returns true if the two `VectorValue` has the same raw number arrays, returns false otherwise. */ isEqual(other: VectorValue): boolean; } - /** * Sentinel values that can be used when writing document fields with set(), * create() or update(). */ export class FieldValue { private constructor(); - /** * Returns a sentinel used with set(), create() or update() to include a * server-generated timestamp in the written data. @@ -2835,7 +2713,6 @@ declare namespace FirebaseFirestore { * update(). */ static serverTimestamp(): FieldValue; - /** * Returns a sentinel for use with update() or set() with {merge:true} to * mark a field for deletion. @@ -2843,7 +2720,6 @@ declare namespace FirebaseFirestore { * @return The FieldValue sentinel for use in a call to set() or update(). */ static delete(): FieldValue; - /** * Returns a special value that can be used with set(), create() or update() * that tells the server to increment the field's current value by the given @@ -2862,7 +2738,6 @@ declare namespace FirebaseFirestore { * update(). */ static increment(n: number): FieldValue; - /** * Returns a special value that can be used with set(), create() or update() * that tells the server to union the given elements with any array value @@ -2876,7 +2751,6 @@ declare namespace FirebaseFirestore { * update(). */ static arrayUnion(...elements: any[]): FieldValue; - /** * Returns a special value that can be used with set(), create() or update() * that tells the server to remove the given elements from any array value @@ -2889,12 +2763,10 @@ declare namespace FirebaseFirestore { * update(). */ static arrayRemove(...elements: any[]): FieldValue; - /** * @return A new `VectorValue` constructed with a copy of the given array of number. */ static vector(values?: number[]): VectorValue; - /** * Returns true if this `FieldValue` is equal to the provided one. * @@ -2903,7 +2775,6 @@ declare namespace FirebaseFirestore { */ isEqual(other: FieldValue): boolean; } - /** * 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 @@ -2917,13 +2788,11 @@ declare namespace FirebaseFirestore { * @param fieldNames A list of field names. */ constructor(...fieldNames: string[]); - /** * Returns a special sentinel FieldPath to refer to the ID of a document. * It can be used in queries to sort or filter by the document ID. */ static documentId(): FieldPath; - /** * Returns true if this `FieldPath` is equal to the provided one. * @@ -2932,7 +2801,6 @@ declare namespace FirebaseFirestore { */ isEqual(other: FieldPath): boolean; } - /** * A Timestamp represents a point in time independent of any time zone or * calendar, represented as seconds and fractions of seconds at nanosecond @@ -2951,7 +2819,6 @@ declare namespace FirebaseFirestore { * @return A new `Timestamp` representing the current date. */ static now(): Timestamp; - /** * Creates a new timestamp from the given date. * @@ -2960,7 +2827,6 @@ declare namespace FirebaseFirestore { * given date. */ static fromDate(date: Date): Timestamp; - /** * Creates a new timestamp from the given number of milliseconds. * @@ -2970,7 +2836,6 @@ declare namespace FirebaseFirestore { * given number of milliseconds. */ static fromMillis(milliseconds: number): Timestamp; - /** * Creates a new timestamp. * @@ -2983,15 +2848,12 @@ declare namespace FirebaseFirestore { * 0 to 999,999,999 inclusive. */ constructor(seconds: number, nanoseconds: number); - /** * The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. */ readonly seconds: number; - /** The non-negative fractions of a second at nanosecond resolution. */ readonly nanoseconds: number; - /** * Returns a new `Date` corresponding to this timestamp. This may lose * precision. @@ -3000,7 +2862,6 @@ declare namespace FirebaseFirestore { * this `Timestamp`, with millisecond precision. */ toDate(): Date; - /** * Returns the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z. * @@ -3008,7 +2869,6 @@ declare namespace FirebaseFirestore { * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z. */ toMillis(): number; - /** * Returns true if this `Timestamp` is equal to the provided one. * @@ -3016,7 +2876,6 @@ declare namespace FirebaseFirestore { * @return 'true' if this `Timestamp` is equal to the provided one. */ isEqual(other: Timestamp): boolean; - /** * Converts this object to a primitive `string`, which allows `Timestamp` objects to be compared * using the `>`, `<=`, `>=` and `>` operators. @@ -3025,14 +2884,12 @@ declare namespace FirebaseFirestore { */ valueOf(): string; } - /** * Builds a Firestore data bundle with results from the given document and query snapshots. */ export class BundleBuilder { /** The ID of this bundle. */ readonly bundleId: string; - /** * Adds a Firestore `DocumentSnapshot` to the bundle. Both the documents data and the document * read time will be included in the bundle. @@ -3043,7 +2900,6 @@ declare namespace FirebaseFirestore { add( documentSnapshot: DocumentSnapshot, ): BundleBuilder; - /** * Adds a Firestore `QuerySnapshot` to the bundle. Both the documents in the query results and * the query read time will be included in the bundle. @@ -3056,13 +2912,11 @@ declare namespace FirebaseFirestore { queryName: string, querySnapshot: QuerySnapshot, ): BundleBuilder; - /** * Builds the bundle and returns the result as a `Buffer` instance. */ build(): Buffer; } - /** * The v1beta1 Veneer client. This client provides access to to the underlying * Firestore v1beta1 RPCs. @@ -3071,7 +2925,6 @@ declare namespace FirebaseFirestore { export const v1beta1: { FirestoreClient: typeof import('./v1beta1/firestore_client').FirestoreClient; }; - /** * The v1 Veneer clients. These clients provide access to the Firestore Admin * API and the underlying Firestore v1 RPCs. @@ -3080,7 +2933,6 @@ declare namespace FirebaseFirestore { FirestoreClient: typeof import('./v1/firestore_client').FirestoreClient; FirestoreAdminClient: typeof import('./v1/firestore_admin_client').FirestoreAdminClient; }; - /** * Status codes returned by Firestore's gRPC calls. */ @@ -3103,7 +2955,6 @@ declare namespace FirebaseFirestore { DATA_LOSS = 15, UNAUTHENTICATED = 16, } - /** * A `Filter` represents a restriction on one or more field values and can * be used to refine the results of a {@link Query}. @@ -3144,7 +2995,6 @@ declare namespace FirebaseFirestore { opStr: WhereFilterOp, value: unknown, ): Filter; - /** * Creates and returns a new [Filter]{@link Filter} that is a * disjunction of the given {@link Filter}s. A disjunction filter includes @@ -3175,7 +3025,6 @@ declare namespace FirebaseFirestore { * ``` */ static or(...filters: Filter[]): Filter; - /** * Creates and returns a new [Filter]{@link Filter} that is a * conjunction of the given {@link Filter}s. A conjunction filter includes @@ -3207,11 +3056,9 @@ declare namespace FirebaseFirestore { */ static and(...filters: Filter[]): Filter; } - type Duration = { /** Signed seconds of the span of time. */ seconds: number; - /** * Signed fractions of a second at nanosecond resolution of the span * of time. Durations less than one second are represented with a 0 @@ -3222,7 +3069,6 @@ declare namespace FirebaseFirestore { */ nanoseconds: number; }; - /** Options used to configure explain queries. */ export interface ExplainOptions { /** @@ -3232,7 +3078,6 @@ declare namespace FirebaseFirestore { */ readonly analyze?: boolean; } - /** * PlanSummary contains information about the planning stage of a query. */ @@ -3245,18 +3090,14 @@ declare namespace FirebaseFirestore { */ readonly indexesUsed: Record[]; } - /** ExecutionStats contains information about the execution of a query. */ export interface ExecutionStats { /** The number of query results. */ readonly resultsReturned: number; - /** The total execution time of the query. */ readonly executionDuration: Duration; - /** The number of read operations that occurred when executing the query. */ readonly readOperations: number; - /** * Contains additional statistics related to the query execution. * This should be inspected or logged, because the contents are intended to be @@ -3265,7 +3106,6 @@ declare namespace FirebaseFirestore { */ readonly debugStats: Record; } - /** * ExplainMetrics contains information about planning and execution of a query. */ @@ -3274,14 +3114,12 @@ declare namespace FirebaseFirestore { * Information about the query plan. */ readonly planSummary: PlanSummary; - /** * Information about the execution of the query, or null if the query was * not executed. */ readonly executionStats: ExecutionStats | null; } - /** * ExplainResults contains information about planning, execution, and results * of a query. @@ -3291,14 +3129,12 @@ declare namespace FirebaseFirestore { * Information about planning and execution of the query. */ readonly metrics: ExplainMetrics; - /** * The snapshot that contains the results of executing the query, or null * if the query was not executed. */ readonly snapshot: T | null; } - /** * Specifies the behavior of the {@link VectorQuery} generated by a call to {@link Query.findNearest}. */ @@ -3307,28 +3143,23 @@ declare namespace FirebaseFirestore { * A string or {@link FieldPath} specifying the vector field to search on. */ vectorField: string | FieldPath; - /** * The {@link VectorValue} used to measure the distance from `vectorField` values in the documents. */ queryVector: VectorValue | Array; - /** * Specifies the upper bound of documents to return, must be a positive integer with a maximum value of 1000. */ limit: number; - /** * Specifies what type of distance is calculated when performing the query. */ distanceMeasure: 'EUCLIDEAN' | 'COSINE' | 'DOT_PRODUCT'; - /** * Optionally specifies the name of a field that will be set on each returned DocumentSnapshot, * which will contain the computed distance for the document. */ distanceResultField?: string | FieldPath; - /** * Specifies a threshold for which no less similar documents will be returned. The behavior * of the specified `distanceMeasure` will affect the meaning of the distance threshold. @@ -3342,8 +3173,7839 @@ declare namespace FirebaseFirestore { */ distanceThreshold?: number; } -} + export namespace Pipelines { + /** + * @beta + * Represents an expression that has been assigned an alias using the `.as()` method. + * + * This class wraps an existing {@link Expression} and associates it with a user-defined alias, + * allowing the expression's result to be referred to by a different name in the output + * of a Firestore pipeline query, particularly within `select()` operations. + * + * @internal + */ + export type ExpressionType = + | 'Field' + | 'Constant' + | 'Function' + | 'AggregateFunction' + | 'ListOfExprs' + | 'AliasedExpression'; + /** + * @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 `Expression` class provides a fluent API for building expressions. You can chain together + * method calls to create complex expressions. + */ + export abstract class Expression { + abstract readonly expressionType: ExpressionType; + /** + * @beta + * 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 `Expression` representing the addition operation. + */ + add( + second: Expression | unknown, + ...others: Array + ): FunctionExpression; + /** + * @beta + * Wraps the expression in a [BooleanExpression]. + * + * @return A [BooleanExpression] representing the same expression. + */ + asBoolean(): BooleanExpression; + /** + * @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 `Expression` 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 `Expression` representing the subtraction operation. + */ + subtract(subtrahend: number): FunctionExpression; + /** + * @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 `Expression` representing the multiplication operation. + */ + multiply( + second: Expression | number, + ...others: Array + ): FunctionExpression; + /** + * @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 `Expression` 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 `Expression` representing the division operation. + */ + divide(divisor: number): FunctionExpression; + /** + * @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 `Expression` 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 `Expression` representing the modulo operation. + */ + mod(value: number): FunctionExpression; + + /** + * @beta + * Creates an expression that checks if this expression is equal to another expression. + * + * ```typescript + * // Check if the 'age' field is equal to the value of another field named 'otherAge'. + * field("age").equal(field("otherAge")); + * + * // Check if the 'status' field is equal to a string literal. + * field("status").equal("active"); + * ``` + * + * @param expression The expression to compare for equality. + * @return A new `BooleanExpression` 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 `Expression` representing the equality comparison. + */ + equal(value: unknown): BooleanExpression; + + /** + * @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 {@link BooleanExpression} 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 `Expression` representing the inequality comparison. + */ + notEqual(value: unknown): BooleanExpression; + + /** + * @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 expression The expression to compare against. + * @return A new `BooleanExpression` representing the less than comparison. + */ + lessThan(expression: 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 `Expression` representing the less than comparison. + */ + lessThan(value: unknown): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if the current expression's value is less than or equal to the value of another expression. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to 20 + * field("quantity").lessThanOrEqual(constant(20)); + * ``` + * + * @param expression The expression to compare against. + * @return A new `BooleanExpression` 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").lessThanOrEqual(70); + * ``` + * + * @param value The constant value to compare for less than or equal to. + * @return A new `Expression` representing the less than or equal to comparison. + */ + lessThanOrEqual(value: unknown): BooleanExpression; + + /** + * @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 {@link BooleanExpression} 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 `Expression` representing the greater than comparison. + */ + greaterThan(value: unknown): BooleanExpression; + /** + * @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 `Expression` 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 `Expression` representing the greater than or equal to comparison. + */ + greaterThanOrEqual(value: unknown): BooleanExpression; + + /** + * @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; + + /** + * @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 `Expression` 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 `Expression` representing the 'array_contains' comparison. + */ + arrayContains(value: unknown): BooleanExpression; + /** + * @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 `Expression` 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 `Expression` representing the 'array_contains_all' comparison. + */ + arrayContainsAll(arrayExpression: Expression): BooleanExpression; + /** + * @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 `Expression` 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 `Expression` representing the 'array_contains_any' comparison. + */ + arrayContainsAny(arrayExpression: Expression): BooleanExpression; + /** + * @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 `Expression` representing the length of the array. + */ + arrayLength(): FunctionExpression; + + /** + * @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 the value of field 'primaryType' + * field("category").equalAny(["Electronics", field("primaryType")]); + * ``` + * + * @param values An array of values or expressions to check against. + * @return A new `BooleanExpression` 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 the 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 `Expression` representing the 'IN' comparison. + */ + equalAny(arrayExpression: Expression): BooleanExpression; + /** + * @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 `Expression` representing the 'NotEqAny' comparison. + */ + notEqualAny(values: Array): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if the current expression's value is not equal to any of the values within the array produced by the `arrayExpression`. + * + * ```typescript + * // Check if the 'status' field is not equal to any value in the array returned by the 'rejectedStatuses' field. + * field("status").notEqualAny(field('rejectedStatuses')); + * ``` + * + * @param arrayExpression An `Expression` that evaluates to an array of values to compare against. + * @return A new `BooleanExpression` representing the result of the 'not equal to any' comparison. + */ + notEqualAny(arrayExpression: Expression): BooleanExpression; + + /** + * @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(); + * ``` + * + * @returns A new {@link BooleanExpression} representing the 'exists' check. + */ + exists(): BooleanExpression; + /** + * @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 `Expression` representing the length of the string. + */ + charLength(): FunctionExpression; + + /** + * @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 string pattern to search for. You can use "%" as a wildcard character within the pattern. + * @return A new {@link BooleanExpression} representing the 'like' comparison. + */ + like(pattern: string): BooleanExpression; + + /** + * @beta + * Creates an expression that performs a case-sensitive string comparison. + * + * ```typescript + * // Check if the 'description' field matches a pattern from another field + * field("description").like(field("searchPattern")); + * ``` + * + * @param pattern An {@link Expression} that evaluates to the string pattern to search for. You can use "%" as a wildcard character within the pattern. + * @return A new {@link BooleanExpression} representing the 'like' comparison. + */ + like(pattern: Expression): 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 "example" (case-insensitive) + * field("description").regexContains("(?i)example"); + * ``` + * + * @param pattern The regular expression to use for the search. + * @return A new `BooleanExpression` 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 {@link BooleanExpression} representing the 'contains' comparison. + */ + regexContains(pattern: Expression): BooleanExpression; + /** + * @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 `Expression` 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 another field. + * field("email").regexMatch(field("regexPattern")); + * ``` + * + * @param pattern An expression that evaluates to the regular expression string to use for the match. + * @return A new `BooleanExpression` representing the result of the regular expression match. + */ + regexMatch(pattern: Expression): BooleanExpression; + /** + * @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 `Expression` 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 {@link BooleanExpression} representing the 'contains' comparison. + */ + stringContains(expr: Expression): BooleanExpression; + /** + * @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 `Expression` representing the 'starts with' comparison. + */ + startsWith(prefix: string): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if the string value of the current expression starts with the string value of the given prefix expression. + * + * ```typescript + * // Check if the 'fullName' field starts with the value of the 'firstName' field + * field("fullName").startsWith(field("firstName")); + * ``` + * + * @param prefix An expression whose string value will be used as the prefix to check against. + * @returns A new `BooleanExpression` representing the 'starts with' comparison result. + */ + startsWith(prefix: Expression): BooleanExpression; + + /** + * @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 {@link BooleanExpression} 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 `Expression` representing the 'ends with' comparison. + */ + endsWith(suffix: Expression): BooleanExpression; + /** + * @beta + * Creates an expression that converts a string to lowercase. + * + * ```typescript + * // Convert the 'name' field to lowercase + * field("name").toLower(); + * ``` + * + * @return A new `Expression` representing the lowercase string. + */ + toLower(): FunctionExpression; + /** + * @beta + * Creates an expression that converts a string to uppercase. + * + * ```typescript + * // Convert the 'title' field to uppercase + * field("title").toUpper(); + * ``` + * + * @return A new `Expression` representing the uppercase string. + */ + toUpper(): FunctionExpression; + /** + * @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 | Uint8Array | Buffer, + ): FunctionExpression; + /** + * @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 `Expression` representing the concatenated string. + */ + stringConcat( + secondString: Expression | string, + ...otherStrings: Array + ): FunctionExpression; + /** + * @beta + * Creates an expression that reverses this string or bytes expression. + * + * ```typescript + * // Reverse the value of the 'myString' field. + * field("myString").reverse(); + * ``` + * + * @return A new {@code Expression} representing the reversed string or bytes. + */ + reverse(): FunctionExpression; + /** + * @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 Expression} representing the length of the string in bytes. + */ + byteLength(): FunctionExpression; + /** + * @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 Expression} representing the ceiling of the numeric value. + */ + ceil(): FunctionExpression; + /** + * @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 Expression} representing the floor of the numeric value. + */ + floor(): FunctionExpression; + + /** + * @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; + + /** + * @beta + * Creates an expression that computes `e` (Euler's number) raised to the power of this expression's numeric value. + * + * ```typescript + * // Compute `e` to the power of the 'value' field. + * field("value").exp(); + * ``` + * + * @return A new {@code FunctionExpression} representing `e` raised to the power of the numeric value. + */ + exp(): FunctionExpression; + /** + * @beta + * Creates an aggregation that counts the number of distinct values of the expression or field. + * + * @return A new `AggregateFunction` representing the 'count_distinct' aggregation. + */ + countDistinct(): AggregateFunction; + /** + * @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 `Expression` representing the value associated with the given key in the map. + */ + mapGet(subfield: string): FunctionExpression; + /** + * @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; + /** + * @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; + /** + * @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; + /** + * @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 'min' aggregation. + */ + minimum(): 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 + * field("score").maximum().as("highestScore"); + * ``` + * + * @return A new `AggregateFunction` representing the 'max' aggregation. + */ + maximum(): AggregateFunction; + /** + * @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 Expression} representing the logical max operation. + */ + logicalMaximum( + second: Expression | unknown, + ...others: Array + ): FunctionExpression; + /** + * @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 Expression} representing the logical min operation. + */ + logicalMinimum( + second: Expression | unknown, + ...others: Array + ): FunctionExpression; + /** + * @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 Expression} representing the length of the vector. + */ + vectorLength(): FunctionExpression; + /** + * @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 Expression) to compare against. + * @return A new `Expression` 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 'embedding' field and a target vector + * field("embedding").cosineDistance(FieldValue.vector([0.1, 0.2, 0.3])); + * + * // Calculate the Cosine distance using a plain number array + * field("embedding").cosineDistance([0.1, 0.2, 0.3]); + * ``` + * + * @param vector The other vector to compare against, provided as either a `VectorValue` object or a plain `number[]` array. + * @return A new `FunctionExpression` representing the Cosine distance between the two vectors. + */ + cosineDistance(vector: VectorValue | number[]): FunctionExpression; + /** + * @beta + * Calculates the dot product between the current expression (representing a vector) + * and another expression (representing a second vector). + * + * Both expressions are expected to resolve to an array of numbers (a vector) at runtime. + * + * ```typescript + * // Calculate the dot product between the 'features' field and the 'targetVector' field. + * field("features").dotProduct(field("targetVector")); + * ``` + * + * @param vectorExpression An {@link Expression} representing the second vector. + * @returns A new {@link FunctionExpression} representing the dot product of 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(FieldValue.vector([0.5, 0.8, 0.2])); + * ``` + * + * @param vector The other vector to calculate the dot product with. This can be a `VectorValue` object or a plain array of numbers. + * @return A new `Expression` representing the dot product between the two vectors. + */ + dotProduct(vector: VectorValue | number[]): FunctionExpression; + + /** + * @beta + * Calculates the Euclidean distance between the vector represented by this expression and another vector expression. + * + * ```typescript + * // Calculate the Euclidean distance between two different vector fields. + * field("vectorA").euclideanDistance(field("vectorB")); + * ``` + * + * @param vectorExpression An {@link Expression} that evaluates to the second vector (an array of numbers) for the distance calculation. + * @return A new {@link FunctionExpression} representing the Euclidean distance between the two vectors. + */ + euclideanDistance(vectorExpression: Expression): FunctionExpression; + /** + * @beta + * Calculates the Euclidean distance between two vectors. + * + * The `vector` parameter can be either a `VectorValue` object or a `number[]` (array of numbers). + * + * ```typescript + * // Calculate the Euclidean distance between the 'location' field and a target location using a VectorValue. + * field("location").euclideanDistance(FieldValue.vector([37.7749, -122.4194])); + * + * // Calculate the Euclidean distance between the 'location' field and a target location using a number array. + * field("location").euclideanDistance([37.7749, -122.4194]); + * ``` + * + * @param vector The other vector (as a `VectorValue` or `number[]`) to compare against. + * @return A new `Expression` representing the Euclidean distance between the two vectors. + */ + euclideanDistance(vector: VectorValue | number[]): FunctionExpression; + /** + * @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 Expression} representing the timestamp. + */ + unixMicrosToTimestamp(): FunctionExpression; + /** + * @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 Expression} representing the number of microseconds since epoch. + */ + timestampToUnixMicros(): FunctionExpression; + /** + * @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 Expression} representing the timestamp. + */ + unixMillisToTimestamp(): FunctionExpression; + /** + * @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 Expression} representing the number of milliseconds since epoch. + */ + timestampToUnixMillis(): FunctionExpression; + /** + * @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 Expression} representing the timestamp. + */ + unixSecondsToTimestamp(): FunctionExpression; + /** + * @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 Expression} representing the number of seconds since epoch. + */ + timestampToUnixSeconds(): FunctionExpression; + /** + * @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 Expression} 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 Expression} representing the resulting timestamp. + */ + timestampAdd( + unit: + | 'microsecond' + | 'millisecond' + | 'second' + | 'minute' + | 'hour' + | 'day', + amount: number, + ): FunctionExpression; + /** + * @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 Expression} 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. Supported units are 'microsecond', 'millisecond', 'second', 'minute', 'hour', and 'day'. + * @param amount The amount of time to subtract. + * @return A new {@code FunctionExpression} representing the resulting timestamp after subtraction. + */ + timestampSubtract( + unit: + | 'microsecond' + | 'millisecond' + | 'second' + | 'minute' + | 'hour' + | 'day', + amount: number, + ): FunctionExpression; + /** + * @beta + * Creates an expression that returns the document ID from a DocumentReference. + * + * ```typescript + * // Get the document ID for the current document. + * field("__name__").documentId(); + * ``` + * + * @return A new {@code Expression} representing the documentId operation. + */ + documentId(): FunctionExpression; + /** + * @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; + /** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and returns the element. If the index exceeds the array length, an error is + * returned. A negative index, starts from the end. + * + * ```typescript + * // Return the value in the 'tags' field array at index `1`. + * field('tags').arrayGet(1); + * ``` + * + * @param index The index of the element to return. + * @return A new Expression representing the 'arrayGet' operation. + */ + arrayGet(index: number): FunctionExpression; + /** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and returns the element. If the index exceeds the array length, an error is + * returned. A negative index, 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 indexExpr An Expression evaluating to the index of the element to return. + * @return A new Expression representing the 'arrayGet' operation. + */ + arrayGet(indexExpr: Expression): FunctionExpression; + /** + * @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 BooleanExpression} representing the 'isError' check. + */ + isError(): BooleanExpression; + /** + * @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 Expression} 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 Expression} representing the 'ifError' operation. + */ + ifError(catchValue: unknown): FunctionExpression; + /** + * @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 BooleanExpression} representing the 'isAbsent' check. + */ + isAbsent(): BooleanExpression; + + /** + * @beta + * Creates an expression that removes a key from the map produced by evaluating this expression. + * + * ```typescript + * // 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 FunctionExpression} representing the 'mapRemove' operation. + */ + mapRemove(key: string): FunctionExpression; + + /** + * @beta + * Creates an expression that removes a key from the map produced by evaluating this expression. + * + * ```typescript + * // 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 FunctionExpression} representing the 'mapRemove' operation. + */ + mapRemove(keyExpr: Expression): FunctionExpression; + + /** + * @beta + * Creates an expression that merges multiple map values. + * + * The first map in the merge operation is the expression on which `mapMerge` is called. + * + * ```typescript + * // Merges the map in the 'settings' field with a literal map and a map + * // conditionally returned by another expression. + * field('settings').mapMerge({ enabled: true }, conditional(field('isAdmin'), { admin: true }, {})) + * ``` + * + * @param secondMap A required second map to merge. This can be a literal object + * or an expression that evaluates to a map. + * @param otherMaps Optional additional maps to merge. Each can be a literal + * object or an expression that evaluates to a map. + * + * @returns A new {@code FunctionExpression} representing the result of the map merge operation. + */ + mapMerge( + secondMap: Record | Expression, + ...otherMaps: Array | Expression> + ): FunctionExpression; + /** + * @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 `Expression` 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 `Expression` representing the power operation. + */ + pow(exponent: number): FunctionExpression; + /** + * @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 `Expression` representing the rounded value. + */ + round(): FunctionExpression; + /** + * @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 `Expression` 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; + /** + * @beta + * Creates an expression that returns the collection ID from a path. + * + * ```typescript + * // Get the collection ID from a path. + * field("__name__").collectionId(); + * ``` + * + * @return A new {@code Expression} representing the collectionId operation. + */ + collectionId(): 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. + * field("name").length(); + * + * // Get the number of items in the 'cart' array. + * field("cart").length(); + * ``` + * + * @return A new `Expression` representing the length of the string, array, map, vector, or bytes. + */ + length(): FunctionExpression; + /** + * @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 Expression} representing the natural logarithm of the numeric value. + */ + ln(): FunctionExpression; + /** + * @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 Expression} representing the square root of the numeric value. + */ + sqrt(): FunctionExpression; + /** + * @beta + * Creates an expression that reverses a string. + * + * ```typescript + * // Reverse the value of the 'myString' field. + * field("myString").stringReverse(); + * ``` + * + * @return A new {@code Expression} representing the reversed string. + */ + stringReverse(): FunctionExpression; + + /** + * @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; + + /** + * @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; + + /** + * @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; + + /** + * @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; + /** + * @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; + /** + * 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; + + /** + * @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; + + // TODO(new-expression): Add new expression method declarations 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; + /** + * @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 + * pipeline().collection("users") + * .sort(field("createdAt").descending()); + * ``` + * + * @return A new `Ordering` for descending sorting. + */ + descending(): Ordering; + /** + * @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. + * 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; + } + + /** + * @beta + * Time granularity used for timestamp functions. + */ + 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; + /** + * @beta + * @internal + */ + readonly _alias: string; + /** + * @beta + * @internal + */ + readonly _expr: Expression; + } + + /** + * @beta + * Represents an aggregate function used within Firestore pipelines. + * Aggregate functions perform a calculation across a set of documents and return a single result, + * such as counting documents, summing numeric fields, or finding minimum/maximum values. + */ + export class AggregateFunction { + /** + * @beta + * @property expressionType The type of the aggregate expression, indicating the specific aggregate function (e.g., COUNT, SUM, AVG). + */ + expressionType: ExpressionType; + + /** + * @beta + * @private + * @internal + * @param name + * @param params + */ + constructor(name: string, params: Expression[]); + /** + * @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". + * 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; + } + + /** + * @beta + * Represents an {@link AggregateFunction} that has been assigned an alias. + * This class is used to associate an aggregate result with a name. + */ + export class AliasedAggregate { + /** + * @beta + * The underlying {@link AggregateFunction} that this aliased aggregate wraps. + * @internal + */ + readonly _aggregate: AggregateFunction; + + /** + * @beta + * Specifies the name of the property that will contain the aggregate result in the output document. + * @internal + */ + readonly _alias: string; + } + + /** + * @beta + * Represents an expression that has been assigned an alias using the `.as()` method. + * + * This class wraps an existing {@link Expression} and associates it with a user-defined alias, + * allowing the expression's result to be referred to by name in the output + * of a Firestore pipeline query. + */ + export class AliasedExpression implements Selectable { + /** + * @beta + * @internal + * Specifies that the instance is an AliasedExpression. + */ + expressionType: ExpressionType; + + /** + * @beta + * Specifies that this class is selectable, meaning it contains an {@link Expression} and an alias, + * and can be provided to the Select stage of a pipeline. + */ + selectable: true; + + /** + * @beta + * @internal + * The underlying expression that is being aliased. + */ + readonly _expr: Expression; + + /** + * @beta + * @internal + * Specifies the name of the property that will contain the aggregate result in the output document. + */ + readonly _alias: string; + } + + /** + * @beta + * Represents a reference to a field within a Firestore document or an output from a {@link Pipeline} stage. + * + * This class extends {@link Expression}. It is a type of expression that can be evaluated + * within Firestore Pipelines. It also implements {@link Selectable}, + * meaning instances of `Field` can be used to specify fields for selection in the {@link Pipeline.select} stage. + * + * `Field` instances are fundamental for constructing expressions that access document field values, + * and for defining criteria for sorting, filtering, and projecting data in Firestore Pipelines. + */ + export class Field extends Expression implements Selectable { + /** + * @beta + * @internal Specifies that the instance is a Field. + */ + readonly expressionType: ExpressionType; + /** + * @beta + * Specifies that this class is selectable, meaning it contains an {@link Expression} and an alias, + * and can be provided to the Select stage of a pipeline. + */ + selectable: true; + /** + * @beta + * Returns the name of the field. + * + * @example + * ```typescript + * const name = field("price").fieldName; + * console.log(name); // "price" + * ``` + * + * @returns The name of the field. + */ + get fieldName(): string; + /** + * @beta + * @internal + * Returns the alias of the field, which is the field-name itself. + * + * @returns The field name itself. + */ + get _alias(): string; + /** + * @beta + * @internal + * Self-referential getter that returns this. + * + * @returns This {@link Field} object. + */ + get _expr(): Expression; + } + /** + * @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 new Field instance from a given FieldPath. + * + * @param path The FieldPath to convert into a Field. + * @returns A new {@link Field} instance representing the specified path. + */ + export function field(path: FieldPath): Field; + + /** + * @beta + * @internal + * Represents a constant value that can be used as part of a Firestore pipeline expression. + * + * Instances of `Constant` are typically created via the top-level `constant()` function. + * + * ```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; + } + /** + * @beta + * Creates an `Expression` instance for a number value. + * + * @param value The number value. + * @return A new `Expression` instance. + */ + export function constant(value: number): Expression; + /** + * @beta + * Creates an `Expression` instance for a string value. + * + * @param value The string value. + * @return A new `Expression` instance. + */ + export function constant(value: string): Expression; + /** + * @beta + * Creates an `Expression` instance for a boolean value. + * + * @param value The boolean value. + * @return A new `Expression` instance. + */ + export function constant(value: boolean): BooleanExpression; + /** + * @beta + * Creates an `Expression` instance for a null value. + * + * @param value The null value. + * @return A new `Expression` instance. + */ + export function constant(value: null): Expression; + /** + * @beta + * Creates an `Expression` instance for a GeoPoint value. + * + * @param value The GeoPoint value. + * @return A new `Expression` instance. + */ + export function constant(value: GeoPoint): Expression; + /** + * @beta + * Creates an `Expression` instance for a Timestamp value. + * + * @param value The Timestamp value. + * @return A new `Expression` instance. + */ + export function constant(value: Timestamp): Expression; + /** + * @beta + * Creates an `Expression` instance for a Date value. + * + * @param value The Date value. + * @return A new `Expression` instance. + */ + export function constant(value: Date): Expression; + /** + * @beta + * Creates an `Expression` instance for a Buffer | Uint8Array value. + * + * @param value The Buffer | Uint8Array value. + * @return A new `Expression` instance. + */ + export function constant(value: Buffer | Uint8Array): Expression; + /** + * @beta + * Creates an `Expression` instance for a DocumentReference value. + * + * @param value The DocumentReference value. + * @return A new `Expression` instance. + */ + export function constant(value: DocumentReference): Expression; + /** + * @beta + * Creates an `Expression` instance for a VectorValue value. + * + * @param value The VectorValue value. + * @return A new `Expression` instance. + */ + export function constant(value: VectorValue): Expression; + + /** + * @beta + * Represents an expression that encapsulates a function call within the Firestore Pipelines. + * + * `FunctionExpression` extends {@link Expression} and is used to build complex queries and transformations + * by applying various functions (e.g., `and`, `equal`, `ceil`) to fields or other expressions. + * + * You typically do not instantiate `FunctionExpression` directly. Instead, use the provided + * top-level functions (like {@link and}, {@link equal}, {@link ceil}) or methods available + * on {@link Expression} instances (e.g., {@link Expression#equal}, {@link Expression#lessThan}) to construct + * `FunctionExpression` instances. + * + * ```typescript + * // Example of creating a FunctionExpression indirectly using helper functions: + * const priceGreaterThan10 = field("price").greaterThan(10); + * const combinedCondition = and(priceGreaterThan10, field("category").equal("books")); + * + * // 'priceGreaterThan10' and 'combinedCondition' are instances of FunctionExpression. + * ``` + */ + export class FunctionExpression extends Expression { + /** + * @beta + * @internal + * Indicates that this expression is a `FunctionExpression`. + */ + readonly expressionType: ExpressionType; + /** + * @beta + * @private + * @internal + * + * Initializes a new `FunctionExpression` instance with the given function name and parameters. + * + * @param name The name of the function. + * @param params An array of `Expression` instances representing the parameters of the function. + */ + constructor(name: string, params: Expression[]); + } + /** + * @beta + * An expression that evaluates to a boolean value. + * + * This expression type is useful for filter conditions. + * + */ + export abstract class BooleanExpression extends Expression { + /** + * @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; + + /** + * @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 Expression} representing the negated filter condition. + */ + not(): BooleanExpression; + + /** + * @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; + + /** + * @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; + } + /** + * @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; + /** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the index exceeds the array length, an error is + * returned. A negative index, 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 index The index of the element to return. + * @return A new Expression representing the 'arrayGet' operation. + */ + export function arrayGet( + arrayField: string, + index: number, + ): FunctionExpression; + /** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the index exceeds the array length, an error is + * returned. A negative index, 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 indexExpr An Expression evaluating to the index of the element to return. + * @return A new Expression representing the 'arrayGet' operation. + */ + export function arrayGet( + arrayField: string, + indexExpr: Expression, + ): FunctionExpression; + /** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the index exceeds the array length, an error is + * returned. A negative index, starts from the end. + * + * ```typescript + * // Return the value in the tags field array at index 1. + * arrayGet(field('tags'), 1); + * ``` + * + * @param arrayExpression An Expression evaluating to an array. + * @param index The index of the element to return. + * @return A new Expression representing the 'arrayGet' operation. + */ + export function arrayGet( + arrayExpression: Expression, + index: number, + ): FunctionExpression; + /** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the index exceeds the array length, an error is + * returned. A negative index, 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 Expression evaluating to an array. + * @param indexExpr An Expression evaluating to the index of the element to return. + * @return A new Expression representing the 'arrayGet' operation. + */ + export function arrayGet( + arrayExpression: Expression, + indexExpr: Expression, + ): FunctionExpression; + /** + * @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 BooleanExpression} representing the 'isError' check. + */ + export function isError(value: Expression): BooleanExpression; + /** + * @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 BooleanExpression} 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 Expression} 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 Expression} representing the 'ifError' operation. + */ + export function ifError( + tryExpr: Expression, + catchValue: unknown, + ): FunctionExpression; + + /** + * @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 Expression} 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 Expression} representing the 'isAbsent' check. + */ + export function isAbsent(field: string): BooleanExpression; + + /** + * @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 another expression. + * + * ```typescript + * // Removes the key 'baz' from the input map. + * mapRemove(map({foo: 'bar', baz: true}), 'baz'); + * ``` + * + * @param mapExpr An expression that evaluates to a map value. + * @param key The name of the key to remove from the input map. + * @returns A new {@link FunctionExpression} representing the map with the specified key removed. + */ + export function mapRemove( + mapExpr: Expression, + key: string, + ): FunctionExpression; + + /** + * @beta + * Creates an expression that removes a key from the map at the specified field name. + * + * ```typescript + * // 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. + * @returns A new {@code FunctionExpression} representing the map after the key has been removed. + */ + export function mapRemove( + mapField: string, + keyExpr: Expression, + ): FunctionExpression; + + /** + * @beta + * Creates an expression that removes a key from a map. + * + * The `mapRemove` function takes two expressions: + * 1. An expression that evaluates to a map. + * 2. An expression that evaluates to the string key to be removed from the map. + * + * ```typescript + * // Removes the key 'baz' from the input map. + * mapRemove(map({foo: 'bar', baz: true}), constant('baz')); + * ``` + * + * @param mapExpr An expression that evaluates to a map value. + * @param keyExpr An expression that evaluates to the string key to remove from the map. + * @returns A new {@link FunctionExpression} representing the map with the specified key removed. + */ + export function mapRemove( + mapExpr: Expression, + keyExpr: 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('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; + + /** + * @beta + * Creates an expression that extracts the document ID from a document reference. + * + * ```typescript + * // Get the document ID from a document reference. + * documentId(field("__name__")); + * ``` + * + * @param documentPathExpr An expression evaluating to a document path. + * @return A new {@code FunctionExpression} representing the document ID as a string. + */ + export function documentId( + documentPathExpr: Expression, + ): FunctionExpression; + + /** + * @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 Expression} representing the documentId operation. + */ + export function documentId( + documentPathExpr: Expression, + ): 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 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. + * + * ```typescript + * // Get a substring of the 'myString' field from index 0 with length 5. + * substring(field("myString"), 0, 5); + * ``` + * + * @param input An expression returning a string or byte array from which to extract the substring. + * @param position An expression that returns the 0-based starting index of the substring. + * @param length Optional. An expression that returns the length of the substring. If omitted, the substring extends to the end of the input. + * @return A new {@code FunctionExpression} representing the substring operation. + */ + export function substring( + input: Expression, + position: Expression, + length?: Expression, + ): FunctionExpression; + + /** + * @beta + * Creates an expression that performs an addition operation on two or more numeric expressions or literal values. + * + * This function supports adding multiple values. For example, `add(a, b, c)` is equivalent to `add(add(a, b), c)`. + * + * ```typescript + * // Add the value of the 'quantity' field and the 'reserve' field. + * add(field("quantity"), field("reserve")); + * + * // Add three values: a field, a literal number, and another field. + * add(field("price"), 10, field("tax")); + * ``` + * + * @param first The initial numeric expression or literal value. + * @param second The second numeric expression or literal value to add. + * @param others Optional: Additional numeric expressions or literal values to add. + * @return A new {@code FunctionExpression} representing the sum of all provided arguments. + */ + export function add( + first: Expression, + second: Expression | unknown, + ...others: Array + ): FunctionExpression; + + /** + * @beta + * Creates an expression that computes the sum of a field's value and one or more other expressions or literals. + * + * ```typescript + * // Add the value of the 'quantity' field to the value of the 'reserve' field. + * add("quantity", field("reserve")); + * + * // Add the value of the 'price' field to a literal number. + * add("price", 10); + * + * // Add the value of the 'total' field to the values of 'tax' and 'shipping' fields. + * add("total", field("tax"), field("shipping")); + * ``` + * + * @param fieldName The name of the field whose value will be the first operand in the addition. + * @param second The second operand, which can be an {@code Expression} or a literal value. + * @param others Optional additional operands, each of which can be an {@code Expression} or a literal value. + * @return A new {@code Expression} representing the addition operation. + */ + export function add( + fieldName: string, + second: Expression | unknown, + ...others: Array + ): FunctionExpression; + /** + * @beta + * Creates an expression that subtracts two expressions. + * + * ```typescript + * // Subtract the 'discount' field from the 'price' field + * subtract(field("price"), field("discount")); + * ``` + * + * @param minuend The expression to subtract from. + * @param subtrahend The expression to subtract. + * @return A new {@code Expression} representing the subtraction operation. + */ + export function subtract( + minuend: Expression, + subtrahend: Expression, + ): FunctionExpression; + + /** + * @beta + * Creates an expression that subtracts one value from another. + * + * ```typescript + * // Subtract the constant value 2 from the 'value' field + * subtract(field("value"), 2); + * ``` + * + * @param minuend The expression to subtract from. + * @param subtrahend The value to subtract. + * @return A new {@code Expression} representing the subtraction operation. + */ + export function subtract( + minuend: Expression, + subtrahend: 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 minuendFieldName The field name to subtract from. + * @param subtrahend The expression to subtract. + * @return A new {@code Expression} representing the subtraction operation. + */ + export function subtract( + minuendFieldName: string, + subtrahend: Expression, + ): FunctionExpression; + + /** + * @beta + * Creates an expression that subtracts a value from a field's value. + * + * + * ```typescript + * // Subtract 20 from the value of the 'total' field + * subtract("total", 20); + * ``` + * + * @param minuendFieldName The name of the field to subtract from. + * @param subtrahend The value to subtract. + * @return A new {@code Expression} representing the subtraction operation. + */ + export function subtract( + minuendFieldName: string, + subtrahend: unknown, + ): FunctionExpression; + + /** + * @beta + * Creates an expression that multiplies two or more values together. + * + * ```typescript + * // Multiply the 'quantity' field by the 'price' field + * multiply(field("quantity"), field("price")); + * + * // Multiply three values together + * multiply(field("a"), 2, field("b")); + * ``` + * + * @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 FunctionExpression} representing the multiplication operation. + */ + export function multiply( + first: Expression, + second: Expression | unknown, + ...others: Array + ): FunctionExpression; + + /** + * @beta + * Creates an expression that multiplies a field's value by one or more other expressions or literal values. + * + * ```typescript + * // Multiply the 'quantity' field by the 'price' field + * multiply("quantity", field("price")); + * ``` + * + * @param fieldName The name of the field whose value will be the initial operand of the multiplication. + * @param second The second operand, which can be an {@link Expression} or a literal value, to be multiplied. + * @param others Optional additional operands ({@link Expression} or literal values) to be included in the multiplication. + * @return A new {@link FunctionExpression} representing the multiplication operation. + */ + export function multiply( + fieldName: string, + second: Expression | unknown, + ...others: Array + ): FunctionExpression; + /** + * @beta + * Creates an expression that divides two expressions. + * + * ```typescript + * // Divide the 'total' field by the 'count' field + * divide(field("total"), field("count")); + * ``` + * + * @param dividend The expression to be divided. + * @param divisor The expression to divide by. + * @return A new {@code Expression} representing the division operation. + */ + export function divide( + dividend: Expression, + divisor: 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 dividend The expression to be divided. + * @param divisor The constant value to divide by. + * @return A new {@code Expression} representing the division operation. + */ + export function divide( + dividend: Expression, + divisor: 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 dividend The field name to be divided. + * @param divisor The expression to divide by. + * @return A new {@code Expression} representing the division operation. + */ + export function divide( + dividend: string, + divisor: 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 dividend The field name to be divided. + * @param divisor The constant value to divide by. + * @return A new {@code Expression} representing the division operation. + */ + export function divide( + dividend: string, + divisor: unknown, + ): FunctionExpression; + /** + * @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 Expression} 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 Expression} 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 Expression} 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 Expression} representing the modulo operation. + */ + export function mod(fieldName: string, value: unknown): FunctionExpression; + + /** + * @beta + * Creates an expression that constructs a Firestore map value from a given JavaScript object. + * The keys of the object become the field names in the Firestore map, and the values become the field values. + * Values can be literal values or other expressions (e.g., `Field.of()`). + * + * ```typescript + * // Create a map from the input object, where 'foo' is a literal string and 'baz' references the value + * // of the 'baz' field from the document currently being processed by the pipeline. + * map({foo: 'bar', baz: Field.of('baz')}).as('data'); + * ``` + * + * @param elements The JavaScript object literal whose properties will be used to create the Firestore map expression. + * @returns A new {@code FunctionExpression} representing the map function. + */ + export function map(elements: Record): FunctionExpression; + /** + * @beta + * Creates an expression that creates a Firestore array value from a given JavaScript array. + * Array values can be literals or other expressions (e.g., `Field.of()`). + * + * ```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 Expression} representing the array function. + */ + export function array(elements: unknown[]): FunctionExpression; + + /** + * @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 `BooleanExpression` 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 `Expression` 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 another expression. + * + * ```typescript + * // Check if the 'age' field is equal to the 'limit' field + * equal("age", field("limit")); + * ``` + * + * @param fieldName The name of the field to compare. + * @param expression The expression to compare the field's value against. + * @return A new `BooleanExpression` 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 `Expression` representing the equality comparison. + */ + export function equal(fieldName: string, value: unknown): BooleanExpression; + /** + * @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 `Expression` 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 `Expression` 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 {@link BooleanExpression} 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 `Expression` representing the inequality comparison. + */ + export function notEqual( + fieldName: string, + value: unknown, + ): BooleanExpression; + /** + * @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 `Expression` 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 `Expression` 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 `BooleanExpression` 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 `Expression` representing the less than comparison. + */ + export function lessThan( + fieldName: string, + value: unknown, + ): BooleanExpression; + /** + * @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 + * lessThanOrEqual(field("quantity"), field("limit")); + * ``` + * + * @param left The first expression to compare. + * @param right The second expression to compare. + * @return A new `Expression` representing the less than or equal to comparison. + */ + export function lessThanOrEqual( + left: Expression, + right: Expression, + ): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if a given expression's value is less than or equal to a constant value. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to 20 + * lessThanOrEqual(field("quantity"), 20); + * ``` + * + * @param expression The {@link Expression} to compare. + * @param value The constant value to compare against. + * @return A new {@link BooleanExpression} 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 another expression. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to the 'limit' field + * lessThanOrEqual("quantity", field("limit")); + * ``` + * + * @param fieldName The name of the field whose value will be compared. + * @param expression The expression to compare against the field's value. + * @return A new {@link BooleanExpression} 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 + * lessThanOrEqual("score", 70); + * ``` + * + * @param fieldName The field name to compare. + * @param value The constant value to compare to. + * @return A new `Expression` representing the less than or equal to comparison. + */ + export function lessThanOrEqual( + fieldName: string, + value: unknown, + ): BooleanExpression; + /** + * @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 `Expression` 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 `Expression` 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 another expression. + * + * ```typescript + * // Check if the value of field 'age' is greater than the value of field 'limit' + * greaterThan("age", field("limit")); + * ``` + * + * @param fieldName The name of the field to compare. + * @param expression The expression to compare against. + * @return A new {@link BooleanExpression} 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 name of the field to compare. + * @param value The constant value to compare to. + * @return A new {@link BooleanExpression} representing the greater than comparison. + */ + export function greaterThan( + fieldName: string, + value: unknown, + ): BooleanExpression; + /** + * @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 `Expression` 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 `Expression` 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 `Expression` 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 `Expression` representing the greater than or equal to comparison. + */ + export function greaterThanOrEqual( + fieldName: string, + value: unknown, + ): BooleanExpression; + + /** + * @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; + + /** + * @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 Expression} 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 Expression} 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 Expression} 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 Expression} representing the 'array_contains' comparison. + */ + export function arrayContains( + fieldName: string, + element: unknown, + ): 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"), [field("cate1"), "Science"]); + * ``` + * + * @param array The array expression to check. + * @param values The elements to check for in the array. + * @return A new {@code Expression} 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 Expression} 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 Expression} 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 Expression} representing the 'array_contains_any' comparison. + */ + export function arrayContainsAny( + fieldName: string, + values: Expression, + ): 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 values The elements to check for in the array. + * @return A new {@code Expression} 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 Expression} 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. + * + * The `array` parameter should be an expression that evaluates to an array. + * The `arrayExpression` parameter should be an expression that evaluates to an array of elements + * to check for within the `array`. + * + * ```typescript + * // Check if the "tags" array contains all elements from the "requiredTags" field. + * arrayContainsAll(field("tags"), field("requiredTags")); + * + * // Check if the "items" array contains all elements from a constant array. + * arrayContainsAll(field("items"), constant(["apple", "banana"])); + * ``` + * + * @param array An expression evaluating to the array to check. + * @param arrayExpression An expression evaluating to an array of elements to check for. + * @return A new {@code BooleanExpression} 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 elements specified by another expression. + * + * ```typescript + * // Check if the 'tags' array contains all values present in the 'requiredTags' field. + * arrayContainsAll("tags", field("requiredTags")); + * ``` + * + * @param fieldName The name of the array field to check. + * @param arrayExpression An expression that evaluates to an array of values to check for. + * @return A new {@code BooleanExpression} representing the 'array_contains_all' comparison. + */ + export function arrayContainsAll( + fieldName: string, + arrayExpression: Expression, + ): BooleanExpression; + /** + * @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 Expression} 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 Expression} representing the length of the array. + */ + export function arrayLength(array: Expression): FunctionExpression; + /** + * @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 Expression} 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 Expression} 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 Expression} 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 elements + * within the array that the provided expression evaluates to. + * + * This is equivalent to an 'IN' comparison in Firestore queries. + * + * ```typescript + * // Check if the 'category' field's value is present in the 'allowedCategories' array field. + * equalAny("category", field("allowedCategories")); + * ``` + * + * @param fieldName The name of the field to compare. + * @param arrayExpression An expression that evaluates to an array. The function checks if the value of `fieldName` is equal to any element within this array. + * @return A new {@code BooleanExpression} representing the 'IN' comparison. + */ + export function equalAny( + fieldName: string, + arrayExpression: Expression, + ): 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 'rejectedStatus' + * notEqualAny(field("status"), ["pending", field("rejectedStatus")]); + * ``` + * + * @param element The expression to compare. + * @param values The values to check against. + * @return A new {@code Expression} 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 Expression} representing the 'NOT IN' comparison. + */ + export function notEqualAny( + fieldName: string, + values: Array, + ): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if a given element expression is not equal to any of the values + * contained within an array expression. This is equivalent to a "NOT IN" operation. + * + * ```typescript + * // Check if the 'status' field is not present in the array stored in the 'invalidStatuses' field. + * notEqualAny(field("status"), field("invalidStatuses")); + * ``` + * + * @param element The expression representing the value to check. + * @param arrayExpression An expression that evaluates to an array of values to check against. + * @return A new {@code BooleanExpression} 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 Expression} representing the 'NOT IN' comparison. + */ + export function notEqualAny( + fieldName: string, + arrayExpression: Expression, + ): BooleanExpression; + + /** + * @beta + * Creates an expression that performs a logical 'XOR' (exclusive OR) operation on two or more Boolean expressions. + * The result is true if an odd number of the input expressions evaluate to true, and false otherwise. + * + * ```typescript + * // Check if exactly 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 Boolean expression. + * @param second The second Boolean expression. + * @param additionalConditions Optional: Additional Boolean expressions to include in the XOR operation. + * @returns A new {@link BooleanExpression} representing the logical 'XOR' operation. + */ + export function xor( + first: BooleanExpression, + second: BooleanExpression, + ...additionalConditions: BooleanExpression[] + ): BooleanExpression; + /** + * @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 Expression} representing the conditional expression. + */ + export function conditional( + condition: BooleanExpression, + thenExpr: Expression, + elseExpr: Expression, + ): FunctionExpression; + /** + * @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 Expression} representing the negated filter condition. + */ + export function not(booleanExpr: BooleanExpression): BooleanExpression; + /** + * @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 Expression} representing the logical max 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 Expression} representing the logical max operation. + */ + export function logicalMaximum( + fieldName: string, + second: Expression | unknown, + ...others: Array + ): FunctionExpression; + /** + * @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 Expression} representing the logical min 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 Expression} representing the logical min operation. + */ + export function logicalMinimum( + fieldName: string, + second: Expression | unknown, + ...others: Array + ): FunctionExpression; + + /** + * @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 representing the field to check for existence. + * @return A new {@code BooleanExpression} 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 Expression} representing the 'exists' check. + */ + export function exists(fieldName: string): BooleanExpression; + + /** + * @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 Expression} 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 Expression} representing the reversed string. + */ + export function reverse(field: 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 Expression} representing the reversed array. + */ + export function arrayReverse( + arrayExpression: Expression, + ): FunctionExpression; + /** + * @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 Expression} representing the reversed array. + */ + export function arrayReverse(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 Expression} representing the ceiling of the numeric value. + */ + export function ceil(expression: Expression): FunctionExpression; + /** + * @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 Expression} representing the ceiling of the numeric value. + */ + export function ceil(fieldName: string): FunctionExpression; + + /** + * @beta + * Creates an expression that computes `e` (Euler's number) raised to the power of the given expression's result. + * + * This function is equivalent to `Math.exp()` in JavaScript. + * + * ```typescript + * // Compute e to the power of 2. + * exp(constant(2)); + * ``` + * + * @param expression An expression evaluating to a numeric value, which will be used as the exponent for `e`. + * @returns A new {@link FunctionExpression} representing `e` raised to the power of the input expression's result. + */ + export function exp(expression: Expression): FunctionExpression; + + /** + * @beta + * Creates an expression that computes 'e' (Euler's number) raised to the power of the specified field's numeric value. + * + * ```typescript + * // Compute 'e' to the power of the 'value' field. + * exp('value'); + * ``` + * + * @param fieldName The name of the field whose numeric value will be used as the exponent. + * @return A new {@code FunctionExpression} representing 'e' raised to the power of the numeric value. + */ + export function exp(fieldName: string): FunctionExpression; + /** + * @beta + * Creates an aggregation that counts the number of distinct values of an evaluated expression. + * + * @param expression The expression to count distinct values of. + * @return A new `AggregateFunction` representing the 'count_distinct' aggregation. + */ + export function countDistinct(expression: Expression): AggregateFunction; + /** + * @beta + * Creates an aggregation that counts the number of distinct values of a field. + * + * @param fieldName The field to count distinct values of. + * @return A new `AggregateFunction` representing the 'count_distinct' aggregation. + */ + export function countDistinct(fieldName: string): AggregateFunction; + /** + * @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 Expression} 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 Expression} representing the length of the string in bytes. + */ + export function byteLength(fieldName: string): FunctionExpression; + + /** + * @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. + * charLength("name"); + * ``` + * + * @param fieldName The name of the field containing the string. + * @return A new {@code Expression} 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. + * charLength(field("name")); + * ``` + * + * @param stringExpression The expression representing the string to calculate the length of. + * @return A new {@code Expression} representing the length of the string. + */ + export function charLength( + stringExpression: Expression, + ): FunctionExpression; + /** + * @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 Expression} 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 Expression} 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 BooleanExpression} 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 Expression} representing the 'like' comparison. + */ + export function like( + stringExpression: Expression, + pattern: Expression, + ): 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", "(?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 Expression} 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 Expression} 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 regex comparison on. + * @param pattern The regular expression string to search for within the `stringExpression`. + * @return A new {@code BooleanExpression} representing the result of the regex 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 a pattern from another field. + * regexContains(field("description"), field("searchPattern")); + * + * // Check if the 'productName' field contains "apple" or "orange". + * regexContains(field("productName"), "apple|orange"); + * ``` + * + * @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 BooleanExpression} representing the 'contains' comparison. + */ + export function regexContains( + stringExpression: Expression, + pattern: Expression, + ): 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", "[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 Expression} 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. + * + * The `pattern` parameter is an {@link Expression} that evaluates to the regular expression string. + * This allows for dynamic regex patterns, such as those stored in other fields. + * + * ```typescript + * // Check if the 'email' field matches a regex pattern stored in the 'emailValidationRegex' field. + * regexMatch("email", field("emailValidationRegex")); + * ``` + * + * @param fieldName The name of the field containing the string value to be matched. + * @param pattern An {@link Expression} that evaluates to the regular expression string to use for the match. + * @return A new {@link BooleanExpression} representing the result of 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 An expression that evaluates to the string to match against. + * @param pattern The regular expression to use for the match. + * @return A new {@link BooleanExpression} representing the result of 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 Expression} representing the regular expression match. + */ + export function regexMatch( + stringExpression: Expression, + pattern: Expression, + ): BooleanExpression; + /** + * @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 Expression} 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 Expression} 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 Expression} 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 Expression} representing the 'contains' comparison. + */ + export function stringContains( + stringExpression: Expression, + substring: Expression, + ): BooleanExpression; + /** + * @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 Expression} 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 Expression} representing the 'starts with' comparison. + */ + export function startsWith( + fieldName: string, + prefix: Expression, + ): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if a string value, represented by an expression, starts with a given prefix. + * + * ```typescript + * // Check if the 'name' field starts with "Mr." + * startsWith(field("name"), "Mr."); + * ``` + * + * @param stringExpression An expression that evaluates to a string, which will be checked for the prefix. + * @param prefix The string prefix to check for. + * @return A new {@code BooleanExpression} 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 expression. + * + * ```typescript + * // Check if the 'fullName' field starts with the value of the 'prefix' field. + * startsWith(field("fullName"), field("prefix")); + * ``` + * + * @param stringExpression The string expression to check. + * @param prefix The prefix expression to check for. + * @return A new {@code BooleanExpression} representing the 'starts with' comparison. + */ + export function startsWith( + stringExpression: Expression, + prefix: Expression, + ): BooleanExpression; + + /** + * @beta + * Creates an expression that checks if a field's value ends with a given suffix. + * + * ```typescript + * // Check if the 'filename' field ends with ".txt" + * endsWith("filename", ".txt"); + * ``` + * + * @param fieldName The field name to check. + * @param suffix The suffix to check for. + * @return A new {@link BooleanExpression} 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 Expression} 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 Expression} 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 Expression} representing the 'ends with' comparison. + */ + export function endsWith( + stringExpression: Expression, + suffix: Expression, + ): BooleanExpression; + + /** + * @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 Expression} 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 Expression} representing the lowercase string. + */ + export function toLower(stringExpression: Expression): FunctionExpression; + /** + * @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 Expression} 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 + * toUpper(field("title")); + * ``` + * + * @param stringExpression The expression representing the string to convert to uppercase. + * @return A new {@code FunctionExpression} representing the uppercase string. + */ + export function toUpper(stringExpression: Expression): FunctionExpression; + /** + * @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; + + /** + * @beta + * Creates an expression that concatenates multiple string values, which can be field names or other expressions that evaluate to strings, along with string literals. + * + * ```typescript + * // Combine the 'firstName' field, a space, and the 'lastName' field into a single string. + * stringConcat("firstName", " ", field("lastName")); + * ``` + * + * @param fieldName The name of the field to use as the initial string value for concatenation. + * @param secondString An expression or string literal to concatenate. + * @param otherStrings Optional additional expressions or literals (typically strings) to concatenate. + * @return A new {@code FunctionExpression} 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 Expression} representing the concatenated string. + */ + export function stringConcat( + firstString: Expression, + secondString: Expression | string, + ...otherStrings: Array + ): FunctionExpression; + /** + * @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 Expression} 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 Expression} representing the value associated with the given key in the map. + */ + export function mapGet( + mapExpression: Expression, + subField: string, + ): FunctionExpression; + /** + * @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; + /** + * @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; + /** + * @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; + /** + * @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; + /** + * @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 'min' 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 'min' aggregation. + */ + export function minimum(fieldName: string): AggregateFunction; + /** + * @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 'max' 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 'max' aggregation. + */ + export function maximum(fieldName: string): AggregateFunction; + /** + * @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 Expression} 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 Expression) to compare against. + * @return A new {@code Expression} representing the cosine distance between the two vectors. + */ + export function cosineDistance( + fieldName: string, + vectorExpression: Expression, + ): FunctionExpression; + + /** + * @beta + * Creates an expression that computes the cosine distance between two vectors. + * + * The second argument can be either a vector literal (an array of numbers) or another vector expression. + * + * ```typescript + * // Calculate the cosine distance between the 'location' field and a target location literal. + * cosineDistance(field("location"), [37.7749, -122.4194]); + * ``` + * + * ```typescript + * // Calculate the cosine distance between two vector fields. + * cosineDistance(field("vector1"), field("vector2")); + * ``` + * + * @param vectorExpression The first vector, represented as an {@link Expression} (e.g., a field path). + * @param vector The second vector, which can be either a numeric array literal (`number[]`) or another {@link Expression}. + * @return A new {@link FunctionExpression} representing the cosine distance. + */ + export function cosineDistance( + vectorExpression: Expression, + vector: number[] | Expression, + ): 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 Expression) to compare against. + * @param otherVectorExpression The other vector (represented as an Expression) to compare against. + * @return A new {@code Expression} representing the cosine distance between the two vectors. + */ + export function cosineDistance( + vectorExpression: Expression, + otherVectorExpression: Expression, + ): FunctionExpression; + /** + * @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 Expression} 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 of 'docVector1' with 'docVector2' + * dotProduct("docVector1", field("docVector2")); + * ``` + * + * @param fieldName The name of the field containing the first vector. + * @param vectorExpression The other vector (represented as an Expression) to calculate with. + * @return A new {@code Expression} 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 another vector (either a number array or a VectorValue). + * + * ```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 Expression) to calculate with. + * @param vector The other vector (as an array of numbers or VectorValue) to calculate with. + * @return A new {@code Expression} 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 Expression) to calculate with. + * @param otherVectorExpression The other vector (represented as an Expression) to calculate with. + * @return A new {@code Expression} representing the dot product between the two vectors. + */ + export function dotProduct( + vectorExpression: Expression, + otherVectorExpression: Expression, + ): FunctionExpression; + + /** + * @beta + * Calculates the Euclidean distance between a field's vector value and another vector. + * The other vector can be provided as a double array or a {@link VectorValue}. + * + * ```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 Expression} 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 Expression) to compare against. + * @return A new {@code Expression} 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 Expression) to compare against. + * @param vector The other vector (as an array of doubles or VectorValue) to compare against. + * @return A new {@code Expression} 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 Expression) to compare against. + * @param otherVectorExpression The other vector (represented as an Expression) to compare against. + * @return A new {@code Expression} representing the Euclidean distance between the two vectors. + */ + export function euclideanDistance( + vectorExpression: Expression, + otherVectorExpression: Expression, + ): FunctionExpression; + /** + * @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 Expression} representing the length of the array. + */ + export function vectorLength( + vectorExpression: Expression, + ): FunctionExpression; + + /** + * @beta + * Creates an expression that calculates the length (dimension) 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 FunctionExpression} representing the length (dimension) of the vector. + */ + export function vectorLength(fieldName: string): FunctionExpression; + /** + * @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 Expression} 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 Expression} representing the timestamp. + */ + export function unixMicrosToTimestamp( + fieldName: string, + ): FunctionExpression; + /** + * @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 Expression} 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 Expression} representing the number of microseconds since epoch. + */ + export function timestampToUnixMicros( + fieldName: string, + ): FunctionExpression; + /** + * @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 Expression} 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 Expression} representing the timestamp. + */ + export function unixMillisToTimestamp( + fieldName: string, + ): FunctionExpression; + /** + * @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 Expression} 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 Expression} representing the number of milliseconds since epoch. + */ + export function timestampToUnixMillis( + fieldName: string, + ): FunctionExpression; + /** + * @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 Expression} 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 Expression} representing the timestamp. + */ + export function unixSecondsToTimestamp( + fieldName: string, + ): FunctionExpression; + /** + * @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 Expression} 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 Expression} representing the number of seconds since epoch. + */ + export function timestampToUnixSeconds( + fieldName: string, + ): FunctionExpression; + /** + * @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 Expression} 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: 'microsecond', 'millisecond', 'second', 'minute', 'hour', or 'day'. + * @param amount The amount of time to add. + * @return A new {@code Expression} 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 Expression} representing the resulting timestamp. + */ + export function timestampAdd( + fieldName: string, + unit: + | 'microsecond' + | 'millisecond' + | 'second' + | 'minute' + | 'hour' + | 'day', + amount: number, + ): FunctionExpression; + /** + * @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 Expression} 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 Expression} 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 Expression} representing the resulting timestamp. + */ + export function timestampSubtract( + fieldName: string, + unit: + | 'microsecond' + | 'millisecond' + | 'second' + | 'minute' + | 'hour' + | 'day', + amount: number, + ): FunctionExpression; + + /** + * @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; + + /** + * @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 Expression} representing the logical 'AND' operation. + */ + export function and( + first: BooleanExpression, + second: BooleanExpression, + ...more: BooleanExpression[] + ): BooleanExpression; + /** + * @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 Expression} representing the logical 'OR' operation. + */ + export function or( + first: BooleanExpression, + second: BooleanExpression, + ...more: BooleanExpression[] + ): BooleanExpression; + /** + * @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 `Expression` 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 `Expression` 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 `Expression` 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 `Expression` representing the power operation. + */ + export function pow(base: string, exponent: number): 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 Expression} representing the collectionId operation. + */ + export function collectionId(expression: Expression): FunctionExpression; + /** + * @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 Expression} representing the collectionId operation. + */ + export function collectionId(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 `Expression` representing the length of the string, array, map, vector, or bytes. + */ + export function length(expression: Expression): 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("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 `Expression` representing the length of the string, array, map, vector, or bytes. + */ + export function length(fieldName: string): FunctionExpression; + /** + * @beta + * Creates an expression that reverses a string. + * + * ```typescript + * // Reverse the value of the 'myString' field. + * stringReverse(field("myString")); + * ``` + * + * @param stringExpression An expression evaluating to a string value, which will be reversed. + * @return A new {@code Expression} 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. + * stringReverse("myString"); + * ``` + * + * @param field The name of the field representing the string to reverse. + * @return A new {@code Expression} representing the reversed string. + */ + export function stringReverse(field: string): FunctionExpression; + + /** + * @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 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 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 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 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; + + /** + * @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; + /** + * @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 {@code Expression} representing the natural logarithm of the numeric value. + */ + export function ln(expression: Expression): FunctionExpression; + /** + * @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 {@code Expression} representing the natural logarithm of the numeric value. + */ + export function ln(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 `Expression` representing the rounded value. + */ + export function round(expression: Expression): FunctionExpression; + /** + * @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 `Expression` representing the rounded value. + */ + export function round(fieldName: string): 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; + /** + * @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 Expression} 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 Expression} representing the square root of the numeric value. + */ + export function sqrt(fieldName: string): FunctionExpression; + /** + * @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; + + /** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * @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; + + // TODO(new-expression): Add new top-level expression function declarations 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 + * 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 + * 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; + /** + * @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 + * 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 + * 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; + /** + * @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 { + readonly expr: Expression; + readonly direction: 'ascending' | 'descending'; + } + + /** + * @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 { + /** + * @beta + * Specifies the source for a new pipeline as a Firestore collection. + * This method allows you to start a data pipeline by defining the collection + * from which documents will be read. + * + * @param collectionPath The path to the collection as a string (e.g., 'users/alovelace/chats') + * or a {@link CollectionReference} object. + * @returns A new {@link Pipeline} object configured to read from the specified collection. + * + * @example + * ```typescript + * // Using a string path + * const pipeline1 = firestore.pipeline().collection('myCollection'); + * + * // Using a CollectionReference + * const collectionRef = firestore.collection('anotherCollection'); + * const pipeline2 = firestore.pipeline().collection(collectionRef); + * ``` + */ + collection(collectionPath: string | CollectionReference): Pipeline; + /** + * @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): Pipeline; + /** + * @beta + * Specifies the source as a collection group. + * + * @param collectionId The ID of the collection group. + * @return A new Pipeline object with the collection group as the source. + */ + collectionGroup(collectionId: string): Pipeline; + + /** + * @beta + * Creates a new {@link Pipeline} stage that queries all documents belonging to a collection + * with the ID specified in the provided `options`, regardless of the document's parent. + * + * A collection group query can be used to query collections that have the same ID but + * are located at different paths. + * + * ```typescript + * // Query all 'cities' collections regardless of their parent document. + * const citiesPipeline = firestore.collectionGroup({ collectionId: 'cities' }); + * ``` + * + * @param options Options defining how this CollectionGroupStage is evaluated, including the `collectionId`. + * @returns A new {@link Pipeline} instance representing the collection group stage. + */ + collectionGroup(options: CollectionGroupStageOptions): Pipeline; + /** + * @beta + * Specifies the source as a database. + * + * @return A new Pipeline object with the database as the source. + */ + database(): Pipeline; + /** + * @beta + * Returns all documents from the entire database. + * @param options - Options defining how a DatabaseStage is evaluated. + */ + database(options: DatabaseStageOptions): Pipeline; + /** + * @beta + * Specifies the source as a set of documents. + * + * @param docs The document references. + * @return A new Pipeline object with the documents as the source. + */ + documents(docs: Array): Pipeline; + /** + * @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): Pipeline; + /** + * @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; + } + /** + * @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 db.pipeline() + * .collection("books") + * .select("title", "author", Field.of("rating").as("bookRating")) + * .execute(); + * + * // Example 2: Filter documents where 'genre' is "Science Fiction" and 'published' is after 1950 + * const results2 = await db.pipeline() + * .collection("books") + * .where(and(Field.of("genre").equal("Science Fiction"), Field.of("published").greaterThan(1950))) + * .execute(); + * + * // Example 3: Calculate the average rating of books published after 1980 + * const results3 = await db.pipeline() + * .collection("books") + * .where(Field.of("published").greaterThan(1980)) + * .aggregate(average(Field.of("rating")).as("averageRating")) + * .execute(); + * ``` + */ + export class 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 {@FunctionExpression}) 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 {@FunctionExpression}) 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; + /** + * @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 + * Creates a stage that removes specified fields from the outputs of previous stages in the pipeline. + * + * This is useful for reducing the data transferred, by excluding fields + * that are no longer needed after certain pipeline operations. + * + * Example: + * + * ```typescript + * firestore.pipeline().collection('books') + * // Removes the 'rating' and 'cost' fields from the documents + * // processed by the preceding stages. + * .removeFields({ + * fields: [ + * field('rating'), + * 'cost' + * ] + * }); + * ``` + * + * @param options - An object containing the configuration for this stage. + * @param options.fields - An array of field names (strings) or {@link Expression} objects + * representing the fields to be removed from the output. + * @returns A new {@link Pipeline} object with this stage appended to the stage list. + */ + removeFields(options: RemoveFieldsStageOptions): 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}: The name of an existing field.
  • + *
  • {@link Field}: A reference to an existing field.
  • + *
  • {@link Expression}: An expression (e.g., a {@link FunctionExpression}) that represents the result of an expression., + * The Expression must be aliased using {@link Expression#as}.
  • + *
+ * + *

Example: + * + * ```typescript + * firestore.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 a {@link + * Selectable} expression or a 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}: The name of an existing field.
  • + *
  • {@link Field}: A reference to an existing field.
  • + *
  • {@link Expression}: An expression (e.g., a {@link FunctionExpression}) that represents the result of an expression., + * The Expression must be aliased using {@link Expression#as}.
  • + *
+ * + * If no selections are provided within the `options` (i.e., the `fields` array is empty), + * the output of this stage will be an empty document. If the intention is to add new fields + * without replacing existing ones, consider using {@link Pipeline#addFields} instead. + * + * Example: + * + * ```typescript + * db.pipeline().collection("books") + * .select({ + * fields: [ + * "firstName", + * field("lastName"), + * field("address").toUppercase().as("upperAddress"), + * ] + * }); + * ``` + * + * @param options An object of type {@link SelectStageOptions} that defines the selection criteria for this stage. + * It is expected to contain a `fields` property, which is an array of {@link Selectable} expressions. + * @return A new {@link Pipeline} object with this select stage appended to its list of stages. + */ + select(options: SelectStageOptions): 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#equal}, {@link Function#lessThan} (less than), {@link + * Function#greaterThan} (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( + * greaterThan(field("rating"), 4.0), // Filter for ratings greater than 4.0 + * field("genre").equal("Science Fiction") // Equivalent to greaterThan("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#equal}, {@link Function#lessThan} (less than), {@link + * Function#greaterThan} (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({ + * filter: and( + * greaterThan(field("rating"), 4.0), // Filter for ratings greater than 4.0 + * field("genre").equal("Science Fiction") // Equivalent to greaterThan("genre", "Science Fiction") + * ) + * }); + * ``` + * + * @param options An object that specifies the filtering criteria. It is expected to contain a `filter` property of type {@link BooleanExpression}. + * @return A new Pipeline object with this stage appended to the stage list. + */ + where(options: WhereStageOptions): 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.of("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 a specified number of documents from the results of previous stages. + * + * This stage is useful for implementing pagination in your pipelines, enabling you to + * retrieve results in manageable chunks. It is commonly used alongside {@link Pipeline.limit} + * to define 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 containing the configuration for the offset stage, + * including the number of documents to skip. + * @returns A new `Pipeline` object with this stage appended to the stage list. + */ + offset(options: OffsetStageOptions): 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.of("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. + * + *

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({ limit: 10 }); + * ``` + * + * @param options - An object that specifies the limit. It must contain a `limit` property, which is a number representing the maximum number of documents to return. + * @return A new {@link Pipeline} object with this stage appended to the stage list. + */ + limit(options: LimitStageOptions): 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 AliasedExpression}: 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 AliasedExpression}: 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; + /** + * @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").average().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: [average(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; + /** + * @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; + + /** + * @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 name of the 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 replaces all fields in a document with the fields from a map expression. + * + *

This stage allows you to transform the current document into a new one + * by providing an {@link Expression} that evaluates to a map. Each key-value pair + * in the resulting map will become a field-value pair in the document. + * + *

Example: + * + * ```typescript + * // Input document: + * // { + * // 'firstName': 'John', + * // 'lastName': 'Doe', + * // 'age': 30 + * // } + * + * // Replace the document's fields with a new map. + * // The 'fullName' field will be created by concatenating 'firstName' and 'lastName'. + * // The 'status' field will be a new static value. + * firestore.pipeline().collection('users').replaceWith(map({ + * fullName: concat(field('firstName'), ' ', field('lastName')), + * status: 'active' + * })); + * + * // Output document: + * // { + * // 'fullName': 'John Doe', + * // 'status': 'active' + * // } + * ``` + * + * @param expr An {@link Expression} that evaluates to a map. The key-value pairs of this map will replace the document's fields. + * @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 transform the current document into a new one + * by defining its new structure using a map expression. Each key in the + * provided map expression becomes a field in the new document, with its + * corresponding value. Values within the map can be literal values or + * expressions that refer to fields from the input document. + * + *

Example: + * + * ```typescript + * // Input document: + * // { + * // 'productId': 'P123', + * // 'itemName': 'Laptop', + * // 'price': 1200, + * // 'details': { + * // 'weight': '2kg', + * // 'color': 'silver' + * // } + * // } + * + * // Replace the document with a new structure, selecting and renaming fields, + * // and adding new literal values. + * firestore.pipeline().collection('products').replaceWith(map({ + * id: field('productId'), + * productName: field('itemName'), + * displayPrice: concat('$', field('price')), + * category: 'Electronics', + * weightInKg: field('details.weight') + * })); + * + * // Output document: + * // { + * // 'id': 'P123', + * // 'productName': 'Laptop', + * // 'displayPrice': '$1200', + * // 'category': 'Electronics', + * // 'weightInKg': '2kg' + * // } + * ``` + * + * @param options - An object that specifies the map expression to use for replacement. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + replaceWith(options: ReplaceWithStageOptions): Pipeline; + + /** + * @beta + * Performs a pseudo-random sampling of documents from the previous stage in the pipeline. + * + * This stage filters documents pseudo-randomly, returning a specified number of documents. + * + * Examples: + * + * ```typescript + * // Sample 25 books, if available. + * firestore.pipeline().collection('books') + * .sample(25); + * ``` + * + * @param documents The maximum 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; + /** + * @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; + /** + * @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; + /** + * @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; + /** + * @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.of("published").lessThan(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. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + rawStage(name: string, params: any[]): Pipeline; + /** + * @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 in a {@link PipelineSnapshot} object, which 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 futureResults = await firestore.pipeline().collection("books") + * .where(greaterThan(Field.of("rating"), 4.5)) + * .select("title", "author", "rating") + * .execute(); + * ``` + * + * @param pipelineExecuteOptions - Optionally specify pipeline execution behavior. + * @return A Promise representing the asynchronous pipeline execution. + */ + execute( + pipelineExecuteOptions?: PipelineExecuteOptions, + ): Promise; + + /** + * @beta + * Executes this pipeline and streams the results as {@link PipelineResult}s. + * + * @returns {NodeJS.ReadableStream} A Node.js ReadableStream that emits {@link PipelineResult} objects. + * + * @example + * ```typescript + * firestore.pipeline().collection("books") + * .where(greaterThan(Field.of("rating"), 4.5)) + * .select("title", "author", "rating") + * .stream() + * .on('data', (pipelineResult) => { + * console.log(pipelineResult); + * }) + * .on('end', () => { + * console.log('Stream ended.'); + * }); + * ``` + */ + stream(): NodeJS.ReadableStream; + } + /** + * @beta + * Options defining how a Pipeline is evaluated. + */ + export type PipelineExecuteOptions = { + /** + * @beta + * Specifies the index mode for the query. + */ + indexMode?: 'recommended'; + /** + * @beta Options used to configure explain queries. */ + explainOptions?: { + /** + * @beta + * The explain mode configures what explain data + * and query results are returned from the Pipeline query. + * + * `"execute"` - [DEFAULT] Execute the Pipeline and return results + * `"analyze"` - Plan the query and execute, returning both the planner + * information and the Pipeline query results. + */ + mode?: 'execute' | 'analyze'; + /** + * @beta + * Specifies the output format of the query planner information. + */ + outputFormat?: 'text'; + }; + /** + * @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; + }; + }; + /** + * @beta + * Options defining how a Stage is evaluated. + */ + export type 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 + * 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[]; + }; + + /** + * @beta + * Represents a field value within the explain statistics, which can be a primitive type (null, string, number, boolean) + * or a recursively defined object where keys are strings and values are also `ExplainStatsFieldValue`. + */ + export type ExplainStatsFieldValue = + | null + | string + | number + | boolean + | { + [key: string]: ExplainStatsFieldValue; + } + | ExplainStatsFieldValue[]; + + /** + * @beta + * Represents the explanation statistics for a {@link Pipeline} query execution. + * These statistics are available when the query is executed with explain or analyze + * options enabled, providing insights into the query's performance and execution plan. + */ + export class ExplainStats { + /** + * @beta + * When explain stats were requested with `outputFormat = 'text'`, this returns + * the explain stats string verbatium as returned from the Firestore backend. + * + * If explain stats were requested with `outputFormat = 'json'`, this returns + * the explain stats as stringified JSON, which was returned from the Firestore backend. + */ + get text(): string; + /** + * @beta + * Returns the explain stats as an encoded protocol buffer message, typically wrapped in a `google.protobuf.Any` format. + * This object includes a `type_url` field that identifies the specific type of the serialized message. + * The caller is responsible for deserializing and unpacking this proto message to access the explain stats. + */ + get rawData(): { + type_url?: string | null; + value?: Uint8Array | null; + }; + } + + /** + * @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 = await firestore + * .pipeline() + * .collection('myCollection') + * .where(field('value').greaterThan(10)) + * .execute(); + * + * snapshot.results.forEach(doc => { + * console.log(doc.id, '=>', doc.data()); + * }); + * ``` + */ + export class PipelineSnapshot { + /** + * @beta + * The Pipeline on which you called `execute()` in order to get this + * `PipelineSnapshot`. + */ + get pipeline(): Pipeline; + + /** + * @beta An array of all the results in the `PipelineSnapshot`. */ + get results(): PipelineResult[]; + + /** + * @beta + * The time at which the pipeline producing this result was executed. + */ + get executionTime(): Timestamp; + + /** + * @beta + * Return stats from query explain. + * + * If `explainOptions.mode` was set to `execute` or left unset, then this returns `undefined`. + */ + get explainStats(): ExplainStats | undefined; + } + + /** + * @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 { + readonly createTime: Timestamp | undefined; + readonly updateTime: Timestamp | undefined; + /** + * @beta + * The reference of the document, if it is a document; otherwise `undefined`. + */ + get ref(): DocumentReference | undefined; + + /** + * @beta + * The ID of the document for which this PipelineResult contains data. + * Returns `undefined` if the PipelineResult does not represent a document. + */ + get id(): string | undefined; + + /** + * @beta + * Retrieves all fields of the document from the query result as a plain JavaScript object. + * + * @returns {DocumentData} An object containing all fields in the document. + * + * @example + * ``` + * let p = firestore.pipeline().collection('col'); + * + * p.execute().then(results => { + * let data = results[0].data(); + * console.log(`Retrieved data: ${JSON.stringify(data)}`); + * }); + * ``` + */ + data(): DocumentData; + /** + * @beta + * Retrieves the field specified by `field`. + * + * @param {string|FieldPath} fieldPath 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): any; + + /** + * @beta + * Checks if this `PipelineResult` is equal to the provided `other` object. + * Equality is determined by comparing the document's data and path. + * + * @param other The `PipelineResult` to compare against. + * @returns `true` if this `PipelineResult` is equal to the provided value; `false` otherwise. + */ + isEqual(other: PipelineResult): boolean; + } + } +} declare module '@google-cloud/firestore' { export = FirebaseFirestore; } diff --git a/types/pipelines.d.ts b/types/pipelines.d.ts new file mode 100644 index 000000000..afca919a5 --- /dev/null +++ b/types/pipelines.d.ts @@ -0,0 +1,28 @@ +/*! + * 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. + */ + +// We deliberately use `any` in the external API to not impose type-checking +// on end users. +/* eslint-disable @typescript-eslint/no-explicit-any */ + +// Declare a global (ambient) namespace +// (used when not using import statement, but just script include). + +import './firestore'; + +declare module '@google-cloud/firestore/pipelines' { + export = FirebaseFirestore.Pipelines; +}