@firebase/firestore
Version:
The Cloud Firestore component of the Firebase JS SDK.
1 lines • 121 kB
Source Map (JSON)
{"version":3,"file":"pipelines.node.mjs","sources":["../src/lite-api/pipeline-result.ts","../src/util/pipeline_util.ts","../src/lite-api/pipeline.ts","../src/lite-api/pipeline-source.ts","../src/api/pipeline.ts","../src/api/pipeline_impl.ts"],"sourcesContent":["/**\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 { RealtimePipeline } from '../api/realtime_pipeline';\nimport { SnapshotMetadata } from '../api/snapshot';\nimport { ListenOptions } from '../core/event_manager';\nimport { Document } from '../model/document';\nimport { ObjectValue } from '../model/object_value';\nimport { firestoreV1ApiClientInterfaces } from '../protos/firestore_proto_api';\nimport { isOptionalEqual } from '../util/misc';\n\nimport { Field, isField } from './expressions';\nimport { FieldPath } from './field_path';\nimport { Pipeline } from './pipeline';\nimport { DocumentData, DocumentReference, refEqual } from './reference';\nimport { Timestamp } from './timestamp';\nimport { fieldPathFromArgument } from './user_data_reader';\nimport { AbstractUserDataWriter } from './user_data_writer';\n\n/**\n * Represents the results of a Firestore pipeline execution.\n *\n * A `PipelineSnapshot` contains zero or more {@link @firebase/firestore/pipelines#PipelineResult} objects\n * representing the documents returned by a pipeline query. It provides methods\n * to iterate over the documents and access metadata about the query results.\n *\n * @example\n * ```typescript\n * const snapshot: PipelineSnapshot = await firestore\n * .pipeline()\n * .collection('myCollection')\n * .where(field('value').greaterThan(10))\n * .execute();\n *\n * snapshot.results.forEach(doc => {\n * console.log(doc.id, '=>', doc.data());\n * });\n * ```\n */\nexport class PipelineSnapshot {\n private readonly _pipeline: Pipeline;\n private readonly _executionTime: Timestamp | undefined;\n private readonly _results: PipelineResult[];\n constructor(\n pipeline: Pipeline,\n results: PipelineResult[],\n executionTime?: Timestamp\n ) {\n this._pipeline = pipeline;\n this._executionTime = executionTime;\n this._results = results;\n }\n\n /**\n * An array of all the results in the `PipelineSnapshot`.\n */\n get results(): PipelineResult[] {\n return this._results;\n }\n\n /**\n * The time at which the pipeline producing this result is executed.\n *\n * @readonly\n *\n */\n get executionTime(): Timestamp {\n if (this._executionTime === undefined) {\n throw new Error(\n \"'executionTime' is expected to exist, but it is undefined\"\n );\n }\n return this._executionTime;\n }\n}\n\n/**\n *\n * A PipelineResult contains data read from a Firestore Pipeline. The data can be extracted with the\n * {@link @firebase/firestore/pipelines#PipelineResult.data} or {@link @firebase/firestore/pipelines#PipelineResult.(get:1)} methods.\n *\n * <p>If the PipelineResult represents a non-document result, `ref` will return a undefined\n * value.\n */\nexport class PipelineResult<AppModelType = DocumentData> {\n private readonly _userDataWriter: AbstractUserDataWriter;\n\n private readonly _createTime: Timestamp | undefined;\n private readonly _updateTime: Timestamp | undefined;\n\n readonly _metadata: SnapshotMetadata | undefined;\n readonly _listenOptions: ListenOptions | undefined;\n\n /**\n * @internal\n * @private\n */\n readonly _ref: DocumentReference | undefined;\n\n /**\n * @internal\n * @private\n */\n readonly _fields: ObjectValue;\n\n /**\n * @private\n * @internal\n *\n * @param userDataWriter - The serializer used to encode/decode protobuf.\n * @param fields - The fields of the Firestore `Document` Protobuf backing\n * this document.\n * @param ref - The reference to the document.\n * @param createTime - The time when the document was created if the result is a document, undefined otherwise.\n * @param updateTime - The time when the document was last updated if the result is a document, undefined otherwise.\n * @param metadata\n * @param listenOptions\n */\n constructor(\n userDataWriter: AbstractUserDataWriter,\n fields: ObjectValue,\n ref?: DocumentReference,\n createTime?: Timestamp,\n updateTime?: Timestamp,\n metadata?: SnapshotMetadata,\n listenOptions?: ListenOptions\n ) {\n this._ref = ref;\n this._userDataWriter = userDataWriter;\n this._createTime = createTime;\n this._updateTime = updateTime;\n this._fields = fields;\n this._metadata = metadata;\n this._listenOptions = listenOptions;\n }\n\n /**\n * @private\n * @internal\n * @param userDataWriter\n * @param doc\n * @param ref\n * @param metadata\n * @param listenOptions\n */\n static fromDocument(\n userDataWriter: AbstractUserDataWriter,\n doc: Document,\n ref?: DocumentReference,\n metadata?: SnapshotMetadata,\n listenOptions?: ListenOptions\n ): PipelineResult {\n return new PipelineResult(\n userDataWriter,\n doc.data,\n ref,\n doc.createTime.toTimestamp(),\n doc.version.toTimestamp(),\n metadata,\n listenOptions\n );\n }\n\n /**\n * The reference of the document, if it is a document; otherwise `undefined`.\n */\n get ref(): DocumentReference | undefined {\n return this._ref;\n }\n\n /**\n * The ID of the document for which this PipelineResult contains data, if it is a document; otherwise `undefined`.\n *\n * @readonly\n *\n */\n get id(): string | undefined {\n return this._ref?.id;\n }\n\n /**\n * The time the document was created. Undefined if this result is not a document.\n *\n * @readonly\n */\n get createTime(): Timestamp | undefined {\n return this._createTime;\n }\n\n /**\n * The time the document was last updated (at the time the snapshot was\n * generated). Undefined if this result is not a document.\n *\n * @readonly\n */\n get updateTime(): Timestamp | undefined {\n return this._updateTime;\n }\n\n /**\n * Retrieves all fields in the result as an object.\n *\n * @returns An object containing all fields in the document or\n * 'undefined' if the document doesn't exist.\n *\n * @example\n * ```\n * let p = firestore.pipeline().collection('col');\n *\n * p.execute().then(results => {\n * let data = results[0].data();\n * console.log(`Retrieved data: ${JSON.stringify(data)}`);\n * });\n * ```\n */\n data(): AppModelType {\n return this._userDataWriter.convertValue(\n this._fields.value,\n this._listenOptions?.serverTimestampBehavior\n ) as AppModelType;\n }\n\n /**\n * @internal\n * @private\n *\n * Retrieves all fields in the result as a proto value.\n *\n * @returns An `Object` containing all fields in the result.\n */\n _fieldsProto(): { [key: string]: firestoreV1ApiClientInterfaces.Value } {\n // Return a cloned value to prevent manipulation of the Snapshot's data\n return this._fields.clone().value.mapValue.fields!;\n }\n\n /**\n * Retrieves the field specified by `field`.\n *\n * @param field - The field path\n * (e.g. 'foo' or 'foo.bar') to a specific field.\n * @returns The data at the specified field location or `undefined` if no\n * such field exists.\n *\n * @example\n * ```\n * let p = firestore.pipeline().collection('col');\n *\n * p.execute().then(results => {\n * let field = results[0].get('a.b');\n * console.log(`Retrieved field value: ${field}`);\n * });\n * ```\n */\n // We deliberately use `any` in the external API to not impose type-checking\n // on end users.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n get(fieldPath: string | FieldPath | Field): any {\n if (this._fields === undefined) {\n return undefined;\n }\n if (isField(fieldPath)) {\n fieldPath = fieldPath.fieldName;\n }\n\n const value = this._fields.field(\n fieldPathFromArgument('DocumentSnapshot.get', fieldPath)\n );\n if (value !== null) {\n return this._userDataWriter.convertValue(\n value,\n this._listenOptions?.serverTimestampBehavior\n );\n }\n }\n}\n\n/**\n * Test equality of two PipelineResults.\n * @param left - First PipelineResult to compare.\n * @param right - Second PipelineResult to compare.\n */\nexport function pipelineResultEqual(\n left: PipelineResult,\n right: PipelineResult\n): boolean {\n if (left === right) {\n return true;\n }\n\n return (\n isOptionalEqual(left._ref, right._ref, refEqual) &&\n isOptionalEqual(left._fields, right._fields, (l, r) => l.isEqual(r))\n );\n}\n\nexport function toPipelineResult(\n doc: Document,\n pipeline: RealtimePipeline,\n listenOptions?: ListenOptions\n): PipelineResult {\n return PipelineResult.fromDocument(\n pipeline._userDataWriter,\n doc,\n doc.key.path\n ? new DocumentReference(pipeline._db, null, doc.key)\n : undefined,\n undefined,\n listenOptions\n );\n}\n","/**\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