@firebase/firestore
Version:
The Cloud Firestore component of the Firebase JS SDK.
1 lines • 119 kB
Source Map (JSON)
{"version":3,"file":"pipelines.node.mjs","sources":["../../src/util/pipeline_util.ts","../../src/lite-api/pipeline.ts","../../src/lite-api/pipeline-source.ts","../../src/lite-api/pipeline-result.ts","../../src/api/pipeline.ts","../../src/api/pipeline_impl.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n _constant,\n AggregateFunction,\n AliasedAggregate,\n array,\n constant,\n Expression,\n AliasedExpression,\n field,\n Field,\n map,\n Selectable,\n pipelineValue\n} from '../lite-api/expressions';\nimport { vector } from '../lite-api/field_value_impl';\nimport type { Pipeline } from '../lite-api/pipeline';\nimport { VectorValue } from '../lite-api/vector_value';\n\nimport { fail } from './assert';\nimport { FirestoreError } from './error';\nimport { isPlainObject } from './input_validation';\nimport { isFirestoreValue } from './proto';\nimport { isString } from './types';\n\n/**\n * @deprecated use selectablesToObject instead\n * @param selectables\n */\nexport function selectablesToMap(\n selectables: Array<Selectable | string>\n): Map<string, Expression> {\n return new Map(Object.entries(selectablesToObject(selectables)));\n}\n\nexport function selectablesToObject(\n selectables: Array<Selectable | string>\n): Record<string, Expression> {\n const result: Record<string, Expression> = {};\n for (const selectable of selectables) {\n let alias: string;\n let expression: Expression;\n if (typeof selectable === 'string') {\n alias = selectable as string;\n expression = field(selectable);\n } else if (selectable instanceof Field) {\n alias = selectable.alias;\n expression = selectable.expr;\n } else if (selectable instanceof AliasedExpression) {\n alias = selectable.alias;\n expression = selectable.expr;\n } else {\n fail(0x5319, '`selectable` has an unsupported type', { selectable });\n }\n\n if (result[alias] !== undefined) {\n throw new FirestoreError(\n 'invalid-argument',\n `Duplicate alias or field '${alias}'`\n );\n }\n\n result[alias] = expression;\n }\n return result;\n}\n\nexport function aliasedAggregateToMap(\n aliasedAggregatees: AliasedAggregate[]\n): Map<string, AggregateFunction> {\n return aliasedAggregatees.reduce(\n (map: Map<string, AggregateFunction>, selectable: AliasedAggregate) => {\n if (map.get(selectable.alias) !== undefined) {\n throw new FirestoreError(\n 'invalid-argument',\n `Duplicate alias or field '${selectable.alias}'`\n );\n }\n\n map.set(selectable.alias, selectable.aggregate as AggregateFunction);\n return map;\n },\n new Map() as Map<string, AggregateFunction>\n );\n}\n\n/**\n * Converts a value to an Expression, Returning either a Constant, MapFunction,\n * ArrayFunction, or the input itself (if it's already an expression).\n *\n * @private\n * @internal\n * @param value\n */\nexport function vectorToExpr(\n value: VectorValue | number[] | Expression\n): Expression {\n if (value instanceof Expression) {\n return value;\n } else if (value instanceof VectorValue) {\n const result = constant(value);\n return result;\n } else if (Array.isArray(value)) {\n const result = constant(vector(value));\n return result;\n } else {\n throw new Error('Unsupported value: ' + typeof value);\n }\n}\n\n/**\n * Converts a value to an Expression, Returning either a Constant, MapFunction,\n * ArrayFunction, or the input itself (if it's already an expression).\n * If the input is a string, it is assumed to be a field name, and a\n * field(value) is returned.\n *\n * @private\n * @internal\n * @param value\n */\nexport function fieldOrExpression(value: unknown): Expression {\n if (isString(value)) {\n const result = field(value);\n return result;\n } else {\n return valueToDefaultExpr(value);\n }\n}\n/**\n * Converts a value to an Expression, Returning either a Constant, MapFunction,\n * ArrayFunction, or the input itself (if it's already an expression).\n *\n * @private\n * @internal\n * @param value\n */\nexport function valueToDefaultExpr(value: unknown): Expression {\n let result: Expression | undefined;\n if (isFirestoreValue(value)) {\n return constant(value);\n }\n if (value instanceof Expression) {\n return value;\n } else if (isPlainObject(value)) {\n result = map(value as Record<string, unknown>);\n } else if (value instanceof Array) {\n result = array(value);\n } else if (isPipeline(value)) {\n result = pipelineValue(value);\n } else {\n result = _constant(value, undefined);\n }\n\n return result;\n}\n\n/**\n * Checks if a value is a Pipeline object.\n *\n * We use duck typing here to avoid a circular dependency between pipeline.ts and pipeline_util.ts.\n */\nfunction isPipeline(value: unknown): value is Pipeline {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as Pipeline).toArrayExpression === 'function'\n );\n}\n","/**\n * @license\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ParseContext } from '../api/parse_context';\nimport {\n Pipeline as ProtoPipeline,\n Stage as ProtoStage\n} from '../protos/firestore_proto_api';\nimport { JsonProtoSerializer, ProtoSerializable } from '../remote/serializer';\nimport { isPlainObject } from '../util/input_validation';\nimport {\n aliasedAggregateToMap,\n fieldOrExpression,\n selectablesToMap,\n selectablesToObject,\n vectorToExpr\n} from '../util/pipeline_util';\nimport { isNumber, isString } from '../util/types';\n\nimport { Firestore } from './database';\nimport {\n _mapValue,\n AggregateFunction,\n AliasedAggregate,\n BooleanExpression,\n _constant,\n Expression,\n Field,\n field,\n Ordering,\n Selectable,\n _field,\n isSelectable,\n isField,\n isBooleanExpr,\n isAliasedAggregate,\n toField,\n isOrdering,\n isExpr,\n AliasedExpression,\n FunctionExpression,\n isAliasedExpr,\n documentMatches\n} from './expressions';\nimport {\n AddFields,\n Aggregate,\n Distinct,\n FindNearest,\n RawStage,\n Limit,\n Offset,\n RemoveFields,\n Replace,\n Sample,\n Select,\n Sort,\n Stage,\n Union,\n Unnest,\n Where,\n Define,\n Search\n} from './stage';\nimport {\n AddFieldsStageOptions,\n AggregateStageOptions,\n DefineStageOptions,\n DistinctStageOptions,\n FindNearestStageOptions,\n LimitStageOptions,\n OffsetStageOptions,\n RemoveFieldsStageOptions,\n ReplaceWithStageOptions,\n SampleStageOptions,\n SearchStageOptions,\n SelectStageOptions,\n SortStageOptions,\n StageOptions,\n UnionStageOptions,\n UnnestStageOptions,\n WhereStageOptions\n} from './stage_options';\nimport { UserDataReader, UserData } from './user_data_reader';\nimport { AbstractUserDataWriter } from './user_data_writer';\n\n/**\n *\n * The Pipeline class provides a flexible and expressive framework for building complex data\n * transformation and query pipelines for Firestore.\n *\n * A pipeline takes data sources, such as Firestore collections or collection groups, and applies\n * a series of stages that are chained together. Each stage takes the output from the previous stage\n * (or the data source) and produces an output for the next stage (or as the final output of the\n * pipeline).\n *\n * Expressions can be used within each stage to filter and transform data through the stage.\n *\n * NOTE: The chained stages do not prescribe exactly how Firestore will execute the pipeline.\n * Instead, Firestore only guarantees that the result is the same as if the chained stages were\n * executed in order.\n *\n * @example\n * ```typescript\n * const db: Firestore; // Assumes a valid firestore instance.\n *\n * // Example 1: Select specific fields and rename 'rating' to 'bookRating'\n * const results1 = await execute(db.pipeline()\n * .collection(\"books\")\n * .select(\"title\", \"author\", field(\"rating\").as(\"bookRating\")));\n *\n * // Example 2: Filter documents where 'genre' is \"Science Fiction\" and 'published' is after 1950\n * const results2 = await execute(db.pipeline()\n * .collection(\"books\")\n * .where(and(field(\"genre\").equal(\"Science Fiction\"), field(\"published\").greaterThan(1950))));\n *\n * // Example 3: Calculate the average rating of books published after 1980\n * const results3 = await execute(db.pipeline()\n * .collection(\"books\")\n * .where(field(\"published\").greaterThan(1980))\n * .aggregate(average(field(\"rating\")).as(\"averageRating\")));\n * ```\n */\nexport class Pipeline implements ProtoSerializable<ProtoPipeline>, UserData {\n /**\n * @internal\n * @private\n * @param _db\n * @param userDataReader\n * @param _userDataWriter\n * @param stages\n */\n constructor(\n /**\n * @internal\n * @private\n */\n public _db: Firestore | undefined,\n /**\n * @internal\n * @private\n */\n readonly userDataReader: UserDataReader | undefined,\n /**\n * @internal\n * @private\n */\n public _userDataWriter: AbstractUserDataWriter | undefined,\n /**\n * @internal\n * @private\n */\n readonly stages: Stage[]\n ) {}\n\n _readUserData(context: ParseContext): void {\n this.stages.forEach(stage => {\n const subContext = context.contextWith({\n methodName: stage._name\n });\n stage._readUserData(subContext);\n });\n }\n\n /**\n * Adds new fields to outputs from previous stages.\n *\n * This stage allows you to compute values on-the-fly based on existing data from previous\n * stages or constants. You can use this to create new fields or overwrite existing ones (if there\n * is name overlaps).\n *\n * The added fields are defined using {@link @firebase/firestore/pipelines#Selectable}s, which can be:\n *\n * <ul>\n * <li>{@link @firebase/firestore/pipelines#Field}: References an existing document field.</li>\n * <li>{@link @firebase/firestore/pipelines#Expression}: Either a literal value (see {@link @firebase/firestore/pipelines#(constant:1)}) or a computed value\n * with an assigned alias using {@link @firebase/firestore/pipelines#Expression.(as:1)}.</li>\n * </ul>\n *\n * @example\n * ```typescript\n * firestore.pipeline().collection(\"books\")\n * .addFields(\n * field(\"rating\").as(\"bookRating\"), // Rename 'rating' to 'bookRating'\n * add(field(\"quantity\"), 5).as(\"totalCost\") // Calculate 'totalCost'\n * );\n * ```\n *\n * @param field - The first field to add to the documents, specified as a {@link @firebase/firestore/pipelines#Selectable}.\n * @param additionalFields - Optional additional fields to add to the documents, specified as {@link @firebase/firestore/pipelines#Selectable}s.\n * @returns A new Pipeline object with this stage appended to the stage list.\n */\n addFields(field: Selectable, ...additionalFields: Selectable[]): Pipeline;\n /**\n * Adds new fields to outputs from previous stages.\n *\n * This stage allows you to compute values on-the-fly based on existing data from previous\n * stages or constants. You can use this to create new fields or overwrite existing ones (if there\n * is name overlaps).\n *\n * The added fields are defined using {@link @firebase/firestore/pipelines#Selectable}s, which can be:\n *\n * <ul>\n * <li>{@link @firebase/firestore/pipelines#Field}: References an existing document field.</li>\n * <li>{@link @firebase/firestore/pipelines#Expression}: Either a literal value (see {@link @firebase/firestore/pipelines#(constant:1)}) or a computed value\n * with an assigned alias using {@link @firebase/firestore/pipelines#Expression.(as:1)}.</li>\n * </ul>\n *\n * @example\n * ```typescript\n * firestore.pipeline().collection(\"books\")\n * .addFields(\n * field(\"rating\").as(\"bookRating\"), // Rename 'rating' to 'bookRating'\n * add(field(\"quantity\"), 5).as(\"totalCost\") // Calculate 'totalCost'\n * );\n * ```\n *\n * @param options - An object that specifies required and optional parameters for the stage.\n * @returns A new Pipeline object with this stage appended to the stage list.\n */\n addFields(options: AddFieldsStageOptions): Pipeline;\n addFields(\n fieldOrOptions: Selectable | AddFieldsStageOptions,\n ...additionalFields: Selectable[]\n ): Pipeline {\n // Process argument union(s) from method overloads\n let fields: Selectable[];\n let options: {};\n if (isSelectable(fieldOrOptions)) {\n fields = [fieldOrOptions, ...additionalFields];\n options = {};\n } else {\n ({ fields, ...options } = fieldOrOptions);\n }\n\n // Convert user land convenience types to internal types\n const normalizedFields: Map<string, Expression> = selectablesToMap(fields);\n\n // Create stage object\n const stage = new AddFields(normalizedFields, options);\n\n // Add stage to the pipeline\n return this._addStage(stage);\n }\n\n /**\n * Remove fields from outputs of previous stages.\n *\n * @example\n * ```typescript\n * firestore.pipeline().collection('books')\n * // removes field 'rating' and 'cost' from the previous stage outputs.\n * .removeFields(\n * field('rating'),\n * 'cost'\n * );\n * ```\n *\n * @param fieldValue - The first field to remove.\n * @param additionalFields - Optional additional fields to remove.\n * @returns A new Pipeline object with this stage appended to the stage list.\n */\n removeFields(\n fieldValue: Field | string,\n ...additionalFields: Array<Field | string>\n ): Pipeline;\n /**\n * Remove fields from outputs of previous stages.\n *\n * @example\n * ```typescript\n * firestore.pipeline().collection('books')\n * // removes field 'rating' and 'cost' from the previous stage outputs.\n * .removeFields(\n * field('rating'),\n * 'cost'\n * );\n * ```\n *\n * @param options - An object that specifies required and optional parameters for the stage.\n * @returns A new Pipeline object with this stage appended to the stage list.\n */\n removeFields(options: RemoveFieldsStageOptions): Pipeline;\n removeFields(\n fieldValueOrOptions: Field | string | RemoveFieldsStageOptions,\n ...additionalFields: Array<Field | string>\n ): Pipeline {\n // Process argument union(s) from method overloads\n const options =\n isField(fieldValueOrOptions) || isString(fieldValueOrOptions)\n ? {}\n : fieldValueOrOptions;\n const fields: Array<Field | string> =\n isField(fieldValueOrOptions) || isString(fieldValueOrOptions)\n ? [fieldValueOrOptions, ...additionalFields]\n : fieldValueOrOptions.fields;\n\n // Convert user land convenience types to internal types\n const convertedFields: Field[] = fields.map(f =>\n isString(f) ? field(f) : (f as Field)\n );\n\n // Create stage object\n const stage = new RemoveFields(convertedFields, options);\n\n // Add stage to the pipeline\n return this._addStage(stage);\n }\n\n /**\n * Defines one or more variables in the pipeline's scope. `define` is used to bind a value to a\n * variable for internal reuse within the pipeline body (accessed via the `variable()` function).\n *\n * This stage is useful for declaring reusable values or intermediate calculations that can be\n * referenced multiple times in later parts of the pipeline, improving readability and\n * maintainability.\n *\n * Each variable is defined using an {@link @firebase/firestore/pipelines#AliasedExpression}, which pairs an expression with a name\n * (alias). The expression can be a simple constant, a field reference, or a complex computation.\n *\n * @example\n * ```typescript\n * db.pipeline().collection(\"products\")\n * .define(\n * field(\"price\").multiply(0.9).as(\"discountedPrice\"),\n * field(\"stock\").add(10).as(\"newStock\")\n * )\n * .where(variable(\"discountedPrice\").lessThan(100))\n * .select(field(\"name\"), variable(\"newStock\"));\n * ```\n *\n * @param aliasedExpression - The first expression to bind to a variable.\n * @param additionalExpressions - Optional additional expression to bind to a variable.\n * @returns A new Pipeline object with this stage appended to the stage list.\n */\n define(\n aliasedExpression: AliasedExpression,\n ...additionalExpressions: AliasedExpression[]\n ): Pipeline;\n /**\n * Defines one or more variables in the pipeline's scope. `define` is used to bind a value to a\n * variable for internal reuse within the pipeline body (accessed via the `variable()` function).\n *\n * This stage is useful for declaring reusable values or intermediate calculations that can be\n * referenced multiple times in later parts of the pipeline, improving readability and\n * maintainability.\n *\n * Each variable is defined using an {@link @firebase/firestore/pipelines#AliasedExpression}, which pairs an expression with a name\n * (alias). The expression can be a simple constant, a field reference, or a complex computation.\n *\n * @example\n * ```typescript\n * db.pipeline().collection(\"products\")\n * .define(\n * field(\"price\").multiply(0.9).as(\"discountedPrice\"),\n * field(\"stock\").add(10).as(\"newStock\")\n * )\n * .where(variable(\"discountedPrice\").lessThan(100))\n * .select(field(\"name\"), variable(\"newStock\"));\n * ```\n *\n * @param options - An object that specifies required and optional parameters for the stage.\n * @returns A new Pipeline object with this stage appended to the stage list.\n */\n define(options: DefineStageOptions): Pipeline;\n define(\n aliasedExpressionOrOptions: AliasedExpression | DefineStageOptions,\n ...additionalExpressions: AliasedExpression[]\n ): Pipeline {\n // Process argument union(s) from method overloads\n const options = isAliasedExpr(aliasedExpressionOrOptions)\n ? {}\n : aliasedExpressionOrOptions;\n const aliasedExpressions: AliasedExpression[] = isAliasedExpr(\n aliasedExpressionOrOptions\n )\n ? [aliasedExpressionOrOptions, ...additionalExpressions]\n : aliasedExpressionOrOptions.variables;\n\n const convertedExpressions: Map<string, Expression> =\n selectablesToMap(aliasedExpressions);\n\n // Create stage object\n const stage = new Define(convertedExpressions, options);\n\n return this._addStage(stage);\n }\n\n /**\n * Converts this Pipeline into an expression that evaluates to an array of results.\n *\n * <p>Result Unwrapping:</p>\n * <ul>\n * <li>If the items have a single field, their values are unwrapped and returned directly in the array.</li>\n * <li>If the items have multiple fields, they are returned as objects in the array</li>\n * </ul>\n *\n * @example\n * ```typescript\n * // Get a list of reviewers for each book\n * db.pipeline().collection(\"books\")\n * .define(field(\"id\").as(\"book_id\"))\n * .addFields(\n * db.pipeline().collection(\"reviews\")\n * .where(field(\"book_id\").equal(variable(\"book_id\")))\n * .select(field(\"reviewer\"))\n * .toArrayExpression()\n * .as(\"reviewers\")\n * )\n * ```\n *\n * Output:\n * ```json\n * [\n * {\n * \"id\": \"1\",\n * \"title\": \"1984\",\n * \"reviewers\": [\"Alice\", \"Bob\"]\n * }\n * ]\n * ```\n *\n * Multiple Fields:\n * ```typescript\n * // Get a list of reviews (reviewer and rating) for each book\n * db.pipeline().collection(\"books\")\n * .define(field(\"id\").as(\"book_id\"))\n * .addFields(\n * db.pipeline().collection(\"reviews\")\n * .where(field(\"book_id\").equal(variable(\"book_id\")))\n * .select(field(\"reviewer\"), field(\"rating\"))\n * .toArrayExpression()\n * .as(\"reviews\"))\n * ```\n *\n * Output:\n * ```json\n * [\n * {\n * \"id\": \"1\",\n * \"title\": \"1984\",\n * \"reviews\": [\n * { \"reviewer\": \"Alice\", \"rating\": 5 },\n * { \"reviewer\": \"Bob\", \"rating\": 4 }\n * ]\n * }\n * ]\n * ```\n *\n * @returns An `Expression` representing the execution of this pipeline.\n */\n toArrayExpression(): Expression {\n return new FunctionExpression('array', [fieldOrExpression(this)]);\n }\n\n /**\n * Converts this Pipeline into an expression that evaluates to a single scalar result.\n *\n * <p><b>Runtime Validation:</b> The runtime validates that the result set contains zero or one item. If\n * zero items, it evaluates to `null`.</p>\n *\n * <p>Result Unwrapping:</p>\n * <ul>\n * <li>If the item has a single field, its value is unwrapped and returned directly.</li>\n * <li>If the item has multiple fields, they are returned as an object.</li>\n * </ul>\n *\n * @example\n * ```typescript\n * // Calculate average rating for a restaurant\n * db.pipeline().collection(\"restaurants\").addFields(\n * db.pipeline().collection(\"reviews\")\n * .where(field(\"restaurant_id\").equal(variable(\"rid\")))\n * .aggregate(average(\"rating\").as(\"avg\"))\n * // Unwraps the single \"avg\" field to a scalar double\n * .toScalarExpression().as(\"average_rating\")\n * )\n * ```\n *\n * Output:\n * ```json\n * {\n * \"name\": \"The Burger Joint\",\n * \"average_rating\": 4.5\n * }\n * ```\n *\n * Multiple Fields:\n * ```typescript\n * // Calculate average rating AND count for a restaurant\n * db.pipeline().collection(\"restaurants\").addFields(\n * db.pipeline().collection(\"reviews\")\n * .where(field(\"restaurant_id\").equal(variable(\"rid\")))\n * .aggregate(\n * average(\"rating\").as(\"avg\"),\n * count().as(\"count\")\n * )\n * // Returns an object with \"avg\" and \"count\" fields\n * .toScalarExpression().as(\"stats\")\n * )\n * ```\n *\n * Output:\n * ```json\n * {\n * \"name\": \"The Burger Joint\",\n * \"stats\": {\n * \"avg\": 4.5,\n * \"count\": 100\n * }\n * }\n * ```\n *\n * @returns An `Expression` representing the execution of this pipeline.\n */\n toScalarExpression(): Expression {\n return new FunctionExpression('scalar', [fieldOrExpression(this)]);\n }\n\n /**\n * Selects or creates a set of fields from the outputs of previous stages.\n *\n * <p>The selected fields are defined using {@link @firebase/firestore/pipelines#Selectable} expressions, which can be:\n *\n * <ul>\n * <li>`string` : Name of an existing field</li>\n * <li>{@link @firebase/firestore/pipelines#Field}: References an existing field.</li>\n * <li>{@link @firebase/firestore/pipelines#AliasedExpression}: Represents the result of a function with an assigned alias name using\n * {@link @firebase/firestore/pipelines#Expression.(as:1)}</li>\n * </ul>\n *\n * <p>If no selections are provided, the output of this stage is empty. Use {@link\n * @firebase/firestore/pipelines#Pipeline.(addFields:1)} instead if only additions are\n * desired.\n *\n * @example\n * ```typescript\n * db.pipeline().collection(\"books\")\n * .select(\n * \"firstName\",\n * field(\"lastName\"),\n * field(\"address\").toUpper().as(\"upperAddress\"),\n * );\n * ```\n *\n * @param selection - The first field to include in the output documents, specified as {@link\n * @firebase/firestore/pipelines#Selectable} expression or string value representing the field name.\n * @param additionalSelections - Optional additional fields to include in the output documents, specified as {@link\n * @firebase/firestore/pipelines#Selectable} expressions or `string` values representing field names.\n * @returns A new Pipeline object with this stage appended to the stage list.\n */\n select(\n selection: Selectable | string,\n ...additionalSelections: Array<Selectable | string>\n ): Pipeline;\n /**\n * Selects or creates a set of fields from the outputs of previous stages.\n *\n * <p>The selected fields are defined using {@link @firebase/firestore/pipelines#Selectable} expressions, which can be:\n *\n * <ul>\n * <li>`string`: Name of an existing field</li>\n * <li>{@link @firebase/firestore/pipelines#Field}: References an existing field.</li>\n * <li>{@link @firebase/firestore/pipelines#AliasedExpression}: Represents the result of a function with an assigned alias name using\n * {@link @firebase/firestore/pipelines#Expression.(as:1)}</li>\n * </ul>\n *\n * <p>If no selections are provided, the output of this stage is empty. Use {@link\n * @firebase/firestore/pipelines#Pipeline.(addFields:1)} instead if only additions are\n * desired.\n *\n * @example\n * ```typescript\n * db.pipeline().collection(\"books\")\n * .select(\n * \"firstName\",\n * field(\"lastName\"),\n * field(\"address\").toUpper().as(\"upperAddress\"),\n * );\n * ```\n *\n * @param options - An object that specifies required and optional parameters for the stage.\n * @returns A new Pipeline object with this stage appended to the stage list.\n */\n select(options: SelectStageOptions): Pipeline;\n select(\n selectionOrOptions: Selectable | string | SelectStageOptions,\n ...additionalSelections: Array<Selectable | string>\n ): Pipeline {\n // Process argument union(s) from method overloads\n const options =\n isSelectable(selectionOrOptions) || isString(selectionOrOptions)\n ? {}\n : selectionOrOptions;\n\n const selections: Array<Selectable | string> =\n isSelectable(selectionOrOptions) || isString(selectionOrOptions)\n ? [selectionOrOptions, ...additionalSelections]\n : selectionOrOptions.selections;\n\n // Convert user land convenience types to internal types\n const normalizedSelections: Map<string, Expression> =\n selectablesToMap(selections);\n\n // Create stage object\n const stage = new Select(normalizedSelections, options);\n\n // Add stage to the pipeline\n return this._addStage(stage);\n }\n\n /**\n * Filters the documents from previous stages to only include those matching the specified {@link\n * @firebase/firestore/pipelines#BooleanExpression}.\n *\n * <p>This stage allows you to apply conditions to the data, similar to a \"WHERE\" clause in SQL.\n * You can filter documents based on their field values, using implementations of {@link\n * @firebase/firestore/pipelines#BooleanExpression}, typically including but not limited to:\n *\n * <ul>\n * <li>field comparators: {@link @firebase/firestore/pipelines#Expression.(equal:1)}, {@link @firebase/firestore/pipelines#Expression.(lessThan:1)}, {@link\n * @firebase/firestore/pipelines#Expression.(greaterThan:1)}, etc.</li>\n * <li>logical operators: {@link @firebase/firestore/pipelines#Expression.(and:1)}, {@link @firebase/firestore/pipelines#Expression.(or:1)}, {@link @firebase/firestore/pipelines#Expression.(not:1)}, etc.</li>\n * <li>advanced functions: {@link @firebase/firestore/pipelines#Expression.(regexMatch:1)}, {@link\n * @firebase/firestore/pipelines#Expression.(arrayContains:1)}, etc.</li>\n * </ul>\n *\n * @example\n * ```typescript\n * firestore.pipeline().collection(\"books\")\n * .where(\n * and(\n * greaterThan(field(\"rating\"), 4.0), // Filter for ratings greater than 4.0\n * field(\"genre\").equal(\"Science Fiction\") // Equivalent to equal(\"genre\", \"Science Fiction\")\n * )\n * );\n * ```\n *\n * @param condition - The {@link @firebase/firestore/pipelines#BooleanExpression} to apply.\n * @returns A new Pipeline object with this stage appended to the stage list.\n */\n where(condition: BooleanExpression): Pipeline;\n /**\n * Filters the documents from previous stages to only include those matching the specified {@link\n * @firebase/firestore/pipelines#BooleanExpression}.\n *\n * <p>This stage allows you to apply conditions to the data, similar to a \"WHERE\" clause in SQL.\n * You can filter documents based on their field values, using implementations of {@link\n * @firebase/firestore/pipelines#BooleanExpression}, typically including but not limited to:\n *\n * <ul>\n * <li>field comparators: {@link @firebase/firestore/pipelines#Expression.(eq:1)}, {@link @firebase/firestore/pipelines#Expression.(lt:1)} (less than), {@link\n * @firebase/firestore/pipelines#Expression.(greaterThan:1)}, etc.</li>\n * <li>logical operators: {@link @firebase/firestore/pipelines#Expression.(and:1)}, {@link @firebase/firestore/pipelines#Expression.(or:1)}, {@link @firebase/firestore/pipelines#Expression.(not:1)}, etc.</li>\n * <li>advanced functions: {@link @firebase/firestore/pipelines#Expression.(regexMatch:1)}, {@link\n * @firebase/firestore/pipelines#Expression.(arrayContains:1)}, etc.</li>\n * </ul>\n *\n * @example\n * ```typescript\n * firestore.pipeline().collection(\"books\")\n * .where(\n * and(\n * greaterThan(field(\"rating\"), 4.0), // Filter for ratings greater than 4.0\n * field(\"genre\").equal(\"Science Fiction\") // Equivalent to equal(\"genre\", \"Science Fiction\")\n * )\n * );\n * ```\n *\n * @param options - An object that specifies required and optional parameters for the stage.\n * @returns A new Pipeline object with this stage appended to the stage list.\n */\n where(options: WhereStageOptions): Pipeline;\n where(conditionOrOptions: BooleanExpression | WhereStageOptions): Pipeline {\n // Process argument union(s) from method overloads\n const options = isBooleanExpr(conditionOrOptions) ? {} : conditionOrOptions;\n const condition: BooleanExpression = isBooleanExpr(conditionOrOptions)\n ? conditionOrOptions\n : conditionOrOptions.condition;\n\n // Create stage object\n const stage = new Where(condition, options);\n\n // Add stage to the pipeline\n return this._addStage(stage);\n }\n\n /**\n * Skips the first `offset` number of documents from the results of previous stages.\n *\n * <p>This stage is useful for implementing pagination in your pipelines, allowing you to retrieve\n * results in chunks. It is typically used in conjunction with {@link @firebase/firestore/pipelines#Pipeline.limit} to control the\n * size of each page.\n *\n * @example\n * ```typescript\n * // Retrieve the second page of 20 results\n * firestore.pipeline().collection('books')\n * .sort(field('published').descending())\n * .offset(20) // Skip the first 20 results\n * .limit(20); // Take the next 20 results\n * ```\n *\n * @param offset - The number of documents to skip.\n * @returns A new Pipeline object with this stage appended to the stage list.\n */\n offset(offset: number): Pipeline;\n /**\n * Skips the first `offset` number of documents from the results of previous stages.\n *\n * <p>This stage is useful for implementing pagination in your pipelines, allowing you to retrieve\n * results in chunks. It is typically used in conjunction with {@link @firebase/firestore/pipelines#Pipeline.limit} to control the\n * size of each page.\n *\n * @example\n * ```typescript\n * // Retrieve the second page of 20 results\n * firestore.pipeline().collection('books')\n * .sort(field('published').descending())\n * .offset(20) // Skip the first 20 results\n * .limit(20); // Take the next 20 results\n * ```\n *\n * @param options - An object that specifies required and optional parameters for the stage.\n * @returns A new Pipeline object with this stage appended to the stage list.\n */\n offset(options: OffsetStageOptions): Pipeline;\n offset(offsetOrOptions: number | OffsetStageOptions): Pipeline {\n // Process argument union(s) from method overloads\n let options: {};\n let offset: number;\n if (isNumber(offsetOrOptions)) {\n options = {};\n offset = offsetOrOptions;\n } else {\n options = offsetOrOptions;\n offset = offsetOrOptions.offset;\n }\n\n // Create stage object\n const stage = new Offset(offset, options);\n\n // Add stage to the pipeline\n return this._addStage(stage);\n }\n\n /**\n * Limits the maximum number of documents returned by previous stages to `limit`.\n *\n * <p>This stage is particularly useful when you want to retrieve a controlled subset of data from\n * a potentially large result set. It's often used for:\n *\n * <ul>\n * <li>Pagination: In combination with {@link @firebase/firestore/pipelines#Pipeline.offset} to retrieve specific pages of\n * results.</li>\n * <li>Limiting Data Retrieval: To prevent excessive data transfer and improve performance,\n * especially when dealing with large collections.</li>\n * </ul>\n *\n * @example\n * ```typescript\n * // Limit the results to the top 10 highest-rated books\n * firestore.pipeline().collection('books')\n * .sort(field('rating').descending())\n * .limit(10);\n * ```\n *\n * @param limit - The maximum number of documents to return.\n * @returns A new Pipeline object with this stage appended to the stage list.\n */\n limit(limit: number): Pipeline;\n /**\n * Limits the maximum number of documents returned by previous stages to `limit`.\n *\n * <p>This stage is particularly useful when you want to retrieve a controlled subset of data from\n * a potentially large result set. It's often used for:\n *\n * <ul>\n * <li>Pagination: In combination with {@link @firebase/firestore/pipelines#Pipeline.offset} to retrieve specific pages of\n * results.</li>\n * <li>Limiting Data Retrieval: To prevent excessive data transfer and improve performance,\n * especially when dealing with large collections.</li>\n * </ul>\n *\n * @example\n * ```typescript\n * // Limit the results to the top 10 highest-rated books\n * firestore.pipeline().collection('books')\n * .sort(field('rating').descending())\n * .limit(10);\n * ```\n *\n * @param options - An object that specifies required and optional parameters for the stage.\n * @returns A new Pipeline object with this stage appended to the stage list.\n */\n limit(options: LimitStageOptions): Pipeline;\n limit(limitOrOptions: number | LimitStageOptions): Pipeline {\n // Process argument union(s) from method overloads\n const options = isNumber(limitOrOptions) ? {} : limitOrOptions;\n const limit: number = isNumber(limitOrOptions)\n ? limitOrOptions\n : limitOrOptions.limit;\n\n // Create stage object\n const stage = new Limit(limit, options);\n\n // Add stage to the pipeline\n return this._addStage(stage);\n }\n\n /**\n * Returns a set of distinct values from the inputs to this stage.\n *\n * This stage runs through the results from previous stages to include only results with\n * unique combinations of {@link @firebase/firestore/pipelines#Expression} values ({@link @firebase/firestore/pipelines#Field}, {@link @firebase/firestore/pipelines#AliasedExpression}, etc).\n *\n * The parameters to this stage are defined using {@link @firebase/firestore/pipelines#Selectable} expressions or strings:\n *\n * <ul>\n * <li> `string`: Name of an existing field</li>\n * <li> {@link @firebase/firestore/pipelines#Field}: References an existing document field.</li>\n * <li> {@link @firebase/firestore/pipelines#AliasedExpression}: Represents the result of a function with an assigned alias name\n * using {@link @firebase/firestore/pipelines#Expression.(as:1)}.</li>\n * </ul>\n *\n * @example\n * ```typescript\n * // Get a list of unique author names in uppercase and genre combinations.\n * firestore.pipeline().collection(\"books\")\n * .distinct(toUpper(field(\"author\")).as(\"authorName\"), field(\"genre\"), \"publishedAt\")\n * .select(\"authorName\");\n * ```\n *\n * @param group - The {@link @firebase/firestore/pipelines#Selectable} expression or field name to consider when determining\n * distinct value combinations.\n * @param additionalGroups - Optional additional {@link @firebase/firestore/pipelines#Selectable} expressions to consider when determining distinct\n * value combinations or strings representing field names.\n * @returns A new {@link @firebase/firestore/pipelines#Pipeline} object with this stage appended to the stage list.\n */\n distinct(\n group: string | Selectable,\n ...additionalGroups: Array<string | Selectable>\n ): Pipeline;\n /**\n * Returns a set of distinct values from the inputs to this stage.\n *\n * This stage runs through the results from previous stages to include only results with\n * unique combinations of {@link @firebase/firestore/pipelines#Expression} values ({@link @firebase/firestore/pipelines#Field}, {@link @firebase/firestore/pipelines#AliasedExpression}, etc).\n *\n * The parameters to this stage are defined using {@link @firebase/firestore/pipelines#Selectable} expressions or strings:\n *\n * <ul>\n * <li>`string`: Name of an existing field</li>\n * <li>{@link @firebase/firestore/pipelines#Field}: References an existing document field.</li>\n * <li>{@link @firebase/firestore/pipelines#AliasedExpression}: Represents the result of a function with an assigned alias name\n * using {@link @firebase/firestore/pipelines#Expression.(as:1)}.</li>\n * </ul>\n *\n * @example\n * ```typescript\n * // Get a list of unique author names in uppercase and genre combinations.\n * firestore.pipeline().collection(\"books\")\n * .distinct(toUpper(field(\"author\")).as(\"authorName\"), field(\"genre\"), \"publishedAt\")\n * .select(\"authorName\");\n * ```\n *\n * @param options - An object that specifies required and optional parameters for the stage.\n * @returns A new {@link @firebase/firestore/pipelines#Pipeline} object with this stage appended to the stage list.\n */\n distinct(options: DistinctStageOptions): Pipeline;\n distinct(\n groupOrOptions: string | Selectable | DistinctStageOptions,\n ...additionalGroups: Array<string | Selectable>\n ): Pipeline {\n // Process argument union(s) from method overloads\n const options =\n isString(groupOrOptions) || isSelectable(groupOrOptions)\n ? {}\n : groupOrOptions;\n const groups: Array<string | Selectable> =\n isString(groupOrOptions) || isSelectable(groupOrOptions)\n ? [groupOrOptions, ...additionalGroups]\n : groupOrOptions.groups;\n\n // Convert user land convenience types to internal types\n const convertedGroups: Map<string, Expression> = selectablesToMap(groups);\n\n // Create stage object\n const stage = new Distinct(convertedGroups, options);\n\n // Add stage to the pipeline\n return this._addStage(stage);\n }\n\n /**\n * Performs aggregation operations on the documents from previous stages.\n *\n * This stage allows you to calculate aggregate values over a set of documents. You define the\n * aggregations to perform using {@link @firebase/firestore/pipelines#AliasedAggregate} expressions which are typically results of\n * calling {@link @firebase/firestore/pipelines#Expression.(as:1)} on {@link @firebase/firestore/pipelines#AggregateFunction} instances.\n *\n * @example\n * ```typescript\n * // Calculate the average rating and the total number of books\n * firestore.pipeline().collection(\"books\")\n * .aggregate(\n * field(\"rating\").average().as(\"averageRating\"),\n * countAll().as(\"totalBooks\")\n * );\n * ```\n *\n * @param accumulator - The first {@link @firebase/firestore/pipelines#AliasedAggregate}, wrapping an {@link @firebase/firestore/pipelines#AggregateFunction}\n * and providing a name for the accumulated results.\n * @param additionalAccumulators - Optional additional {@link @firebase/firestore/pipelines#AliasedAggregate}, each wrapping an {@link @firebase/firestore/pipelines#AggregateFunction}\n * and providing a name for the accumulated results.\n * @returns A new Pipeline object with this stage appended to the stage list.\n */\n aggregate(\n accumulator: AliasedAggregate,\n ...additionalAccumulators: AliasedAggregate[]\n ): Pipeline;\n /**\n * Performs optionally grouped aggregation operations on the documents from previous stages.\n *\n * This stage allows you to calculate aggregate values over a set of documents, optionally\n * grouped by one or more fields or functions. You can specify:\n *\n * <ul>\n * <li>Grouping Fields or Functions: One or more fields or functions to group the documents\n * by. For each distinct combination of values in these fields, a separate group is created.\n * If no grouping fields are provided, a single group containing all documents is used. Not\n * specifying groups is the same as putting the entire inputs into one group.</li>\n * <li>Accumulators: One or more accumulation operations to perform within each group. These\n * are defined using {@link @firebase/firestore/pipelines#AliasedAggregate} expressions, which are typically created by\n * calling {@link @firebase/firestore/pipelines#Expression.(as:1)} on {@link @firebase/firestore/pipelines#AggregateFunction} instances. Each aggregation\n * calculates a value (e.g., sum, average, count) based on the documents within its group.</li>\n * </ul>\n *\n * @example\n * ```typescript\n * // Calculate the average rating for each genre.\n * firestore.pipeline().collection(\"books\")\n * .aggregate({\n * accumulators: [average(field(\"rating\")).as(\"avg_rating\")],\n * groups: [\"genre\"]\n * });\n * ```\n *\n * @param options - An object that specifies required and optional parameters for the stage.\n * @returns A new {@link @firebase/firestore/pipelines#Pipeline} object with this stage appended to the stage\n * list.\n */\n aggregate(options: AggregateStageOptions): Pipeline;\n aggregate(\n targetOrOptions: AliasedAggregate | AggregateStageOptions,\n ...rest: AliasedAggregate[]\n ): Pipeline {\n // Process argument union(s) from method overloads\n const options = isAliasedAggregate(targetOrOptions) ? {} : targetOrOptions;\n const accumulators: AliasedAggregate[] = isAliasedAggregate(targetOrOptions)\n ? [targetOrOptions, ...rest]\n : targetOrOptions.accumulators;\n const groups: Array<Selectable | string> = isAliasedAggregate(\n targetOrOptions\n )\n ? []\n : targetOrOptions.groups ?? [];\n\n // Convert user land convenience types to internal types\n const convertedAccumulators: Map<string, AggregateFunction> =\n aliasedAggregateToMap(accumulators);\n const convertedGroups: Map<string, Expression> = selectablesToMap(groups);\n\n // Create stage object\n const stage = new Aggregate(\n convertedGroups,\n convertedAccumulators,\n options\n );\n\n // Add stage to the pipeline\n return this._addStage(stage);\n }\n\n /**\n * Performs a vector proximity search on the documents from the previous stage, returning the\n * K-nearest documents based on the specified query `vectorValue` and `distanceMeasure`. The\n * returned documents will be sorted in order from nearest to furthest from the query `vectorValue`.\n *\n * @example\n * ```typescript\n * // Find the 10 most similar books based on the book description.\n * const bookDescription = \"Lorem ipsum...\";\n * const queryVector: number[] = ...; // compute embedding of `bookDescription`\n *\n * firestore.pipeline().collection(\"books\")\n * .findNearest({\n * field: 'embedding',\n * vectorValue: queryVector,\n * distanceMeasure: 'euclidean',\n * limit: 10, // optional\n * distanceField: 'computedDistance' // optional\n * });\n * ```\n *\n * @param options - An object that specifies required and optional parameters for the stage.\n * @returns A new {@link @firebase/firestore/pipelines#Pipeline} object with this stage appended to the stage list.\n */\n findNearest(options: FindNearestStageOptions): Pipeline {\n // Convert user land convenience types to internal types\n const field = toField(options.field);\n const vectorValue = vectorToExpr(options.vectorValue);\n const distanceField = options.distanceField\n ? toField(options.distanceField)\n : undefined;\n const internalOptions = {\n distanceField,\n limit: options.limit,\n rawOptions: options.rawOptions\n };\n\n // Create stage object\n const stage = new FindNearest(\n vectorValue,\n field,\n options.distanceMeasure,\n internalOptions\n );\n\n // Add stage to the pipeline\n return this._addStage(stage);\n }\n\n // TODO(search) link to external documentation citing list of supported\n // expressions, when that documentation is created. List is not maintained\n // in the SDK because the list will change as the backend enables support.\n\n /**\n * Add a search stage to the Pipeline. The search stage supports\n * full-text search and geo search expressions.\n *\n * @remarks\n * This must be the first stage of the pipeline. A limited set of expressions are supported in the search stage.\n *\n * @example\n * ```typescript\n * // Full-text search example\n * firestore.pipeline().collection(\"restaurants\")\n * .search({\n * query: documentMatches(\"waffles OR pancakes\"),\n * sort: [\n * score().descending(),\n * ],\n * addFields: [\n * score().as(\"searchScore\"),\n * ]\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Geo distance search example\n * const queryLocation = new GeoPoint(0, 0);\n * db.pipeline().collection('restaurants').search({\n * query: field('location').geoDistance(queryLocation).lessThanOrEqual(1000),\n * sort: [\n * score().descending(),\n * ],\n * })\n * ```\n *\n * @param options - An object that specifies parameters for the stage.\n * @return A new `Pipeline` object with this stage appended to the stage list.\n * @beta\n */\n search(options: SearchStageOptions): Pipeline {\n // Convert user land convenience types to internal types\n const addFields: Record<string, Expression> | undefined = options.addFields\n ? selectablesToObject(options.addFields)\n : undefined;\n const query: BooleanExpression = isExpr(options.query)\n ? options.query\n : documentMatches(options.query);\n const sort: Ordering[] | undefined = isOrdering(options.sort)\n ? [options.sort]\n : options.sort;\n\n const select: Record<string, Expression> | undefined = undefined;\n // TODO(search) enable with backend support\n // select = options.select\n // ? selectablesToObject(options.select)\n // : undefined;\n\n const internalOptions = {\n ...options,\n addFields,\n select,\n query,\n sort\n };\n\n // Create stage object\n const stage = new Search(internalOptions);\n\n // Add stage to the pipeline\n return this._addStage(stage);\n }\n\n /**\n * Sorts the documents from previous stages based on one or more {@link @firebase/firestore/pipelines#Ordering} criteria.\n *\n * <p>This stage allows you to order the results of your pipeline. You can specify multiple {@link\n * @firebase/firestore/pipelines#Ordering} instances to sort by multiple fields in ascending or descending order. If documents\n * have the same value for a field used for sorting, the next specified ordering will be used. If\n * all orderings result in equal comparison, the documents are considered equal and the order is\n * unspecified.\n *\n * @example\n * ```typescript\n * // Sort books by rating in descending order, and then by title in ascending order for books\n * // with the same rating\n * firestore.pipeline().collection(\"books\")\n * .sort(\n * field(\"rating\").descending(),\n * field(\"title\").ascending()\n * );\n * ```\n *\n * @param ordering - The fir