Skip to content
This repository was archived by the owner on Mar 4, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions api-report/firestore.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,7 @@ abstract class Expression implements firestore.Pipelines.Expression, HasUserData
ifError(catchValue: unknown): FunctionExpression;
isAbsent(): BooleanExpression;
isError(): BooleanExpression;
isType(type: Type): BooleanExpression;
join(delimiterExpression: Expression): Expression;
join(delimiter: string): Expression;
length(): FunctionExpression;
Expand Down Expand Up @@ -1508,6 +1509,12 @@ function isAbsent(field: string): BooleanExpression;
// @beta
function isError(value: Expression): BooleanExpression;

// @beta
function isType(fieldName: string, type: Type): BooleanExpression;

// @beta
function isType(expression: Expression, type: Type): BooleanExpression;

// @beta
function join(arrayFieldName: string, delimiter: string): Expression;

Expand Down Expand Up @@ -1940,6 +1947,8 @@ declare namespace Pipelines {
currentTimestamp,
arrayConcat,
type,
isType,
Type,
timestampTruncate,
split
}
Expand Down Expand Up @@ -2660,6 +2669,9 @@ function trunc(fieldName: string, decimalPlaces: number | Expression): FunctionE
// @beta
function trunc(expression: Expression, decimalPlaces: number | Expression): FunctionExpression;

// @beta
type Type = 'null' | 'array' | 'boolean' | 'bytes' | 'timestamp' | 'geo_point' | 'number' | 'int32' | 'int64' | 'float64' | 'decimal128' | 'map' | 'reference' | 'string' | 'vector' | 'max_key' | 'min_key' | 'object_id' | 'regex' | 'request_timestamp';

// Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag
// Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@"
//
Expand Down
97 changes: 97 additions & 0 deletions dev/src/pipelines/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,39 @@ import {
import {HasUserData, Serializer, validateUserInput} from '../serializer';
import {cast} from '../util';

/**
* @beta
*
* An enumeration of the different types generated by the Firestore backend.
*
* <ul>
* <li>Numerics evaluate directly to backend representation (`int64` or `float64`), not JS `number`.</li>
* <li>JavaScript `Date` and firestore `Timestamp` objects strictly evaluate to `'timestamp'`.</li>
* <li>Advanced configurations parsing backend types (such as `decimal128`, `max_key` or `min_key` from BSON) are also incorporated in this union string type. Note that `decimal128` is a backend-only numeric type that the JavaScript SDK cannot create natively, but can be evaluated in pipelines.</li>
* </ul>
*/
export type Type =
| 'null'
| 'array'
| 'boolean'
| 'bytes'
| 'timestamp'
| 'geo_point'
| 'number'
| 'int32'
| 'int64'
| 'float64'
| 'decimal128'
| 'map'
| 'reference'
| 'string'
| 'vector'
| 'max_key'
| 'min_key'
| 'object_id'
| 'regex'
| 'request_timestamp';

/**
* @beta
* Represents an expression that can be evaluated to a value within the execution of a `Pipeline`.
Expand Down Expand Up @@ -2497,6 +2530,28 @@ export abstract class Expression
return new FunctionExpression('type', [this]);
}

/**
* @beta
* Creates an expression that checks if the result of this expression is of the given type.
*
* @remarks Null or undefined fields evaluate to skip/error. Use `ifAbsent()` / `isAbsent()` to evaluate missing data.
*
* @example
* ```typescript
* // Check if the 'price' field is specifically an integer (not just 'number')
* field('price').isType('int64');
* ```
*
* @param type The type to check for.
* @returns A new `BooleanExpression` that evaluates to true if the expression's result is of the given type, false otherwise.
*/
isType(type: Type): BooleanExpression {
return new FunctionExpression('is_type', [
this,
constant(type),
]).asBoolean();
}

// TODO(new-expression): Add new expression method definitions above this line

/**
Expand Down Expand Up @@ -8456,6 +8511,48 @@ export function type(
return fieldOrExpression(fieldNameOrExpression).type();
}

/**
* @beta
* Creates an expression that checks if the value in the specified field is of the given type.
*
* @remarks Null or undefined fields evaluate to skip/error. Use `ifAbsent()` / `isAbsent()` to evaluate missing data.
*
* @example
* ```typescript
* // Check if the 'price' field is a floating point number (evaluating to true inside pipeline conditionals)
* isType('price', 'float64');
* ```
*
* @param fieldName The name of the field to check.
* @param type The type to check for.
* @returns A new `BooleanExpression` that evaluates to true if the field's value is of the given type, false otherwise.
*/
export function isType(fieldName: string, type: Type): BooleanExpression;

/**
* @beta
* Creates an expression that checks if the result of an expression is of the given type.
*
* @remarks Null or undefined fields evaluate to skip/error. Use `ifAbsent()` / `isAbsent()` to evaluate missing data.
*
* @example
* ```typescript
* // Check if the result of a calculation is a number
* isType(add('count', 1), 'number')
* ```
*
* @param expression The expression to check.
* @param type The type to check for.
* @returns A new `BooleanExpression` that evaluates to true if the expression's result is of the given type, false otherwise.
*/
export function isType(expression: Expression, type: Type): BooleanExpression;
export function isType(
fieldNameOrExpression: string | Expression,
type: Type,
): BooleanExpression {
return fieldOrExpression(fieldNameOrExpression).isType(type);
}

// TODO(new-expression): Add new top-level expression function definitions above this line

/**
Expand Down
2 changes: 2 additions & 0 deletions dev/src/pipelines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ export {
currentTimestamp,
arrayConcat,
type,
isType,
Type,
timestampTruncate,
split,
// TODO(new-expression): Add new expression exports above this line
Expand Down
69 changes: 69 additions & 0 deletions dev/system-test/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ import {
currentTimestamp,
arrayConcat,
type,
isType,
timestampTruncate,
split,
// TODO(new-expression): add new expression imports above this line
Expand Down Expand Up @@ -4386,6 +4387,74 @@ describe.skipClassic('Pipeline class', () => {
});
});

it('supports isType', async () => {
const result = await firestore
.pipeline()
.collection(randomCol.path)
.replaceWith(
map({
int: constant(1),
float: constant(1.1),
str: constant('a string'),
bool: constant(true),
null: constant(null),
geoPoint: constant(new GeoPoint(0.1, 0.2)),
timestamp: constant(new Timestamp(123456, 0)),
bytes: constant(new Uint8Array([1, 2, 3])),
docRef: constant(firestore.doc(`${randomCol.path}/bar`)),
vector: constant(FieldValue.vector([1, 2, 3])),
map: map({
numberK: 1,
stringK: 'a string',
}),
array: array([1, '2', true]),
}),
)
.select(
isType(field('int'), 'int64').as('isInt64'),
isType(field('int'), 'number').as('isInt64IsNumber'),
isType(field('int'), 'decimal128').as('isInt64IsDecimal128'),
field('float').isType('float64').as('isFloat64'),
field('float').isType('number').as('isFloat64IsNumber'),
field('float').isType('decimal128').as('isFloat64IsDecimal128'),
isType('str', 'string').as('isStr'),
isType('int', 'string').as('isNumStr'),
field('bool').isType('boolean').as('isBool'),
isType('null', 'null').as('isNull'),
field('geoPoint').isType('geo_point').as('isGeoPoint'),
isType('timestamp', 'timestamp').as('isTimestamp'),
field('bytes').isType('bytes').as('isBytes'),
isType('docRef', 'reference').as('isDocRef'),
field('vector').isType('vector').as('isVector'),
isType('map', 'map').as('isMap'),
field('array').isType('array').as('isArray'),
field('str').isType('int64').as('isStrNum'),
)
.limit(1)
Comment thread
dlarocque marked this conversation as resolved.
.execute();

expectResults(result, {
isInt64: true,
isInt64IsNumber: true,
isInt64IsDecimal128: false,
isFloat64: true,
isFloat64IsNumber: true,
isFloat64IsDecimal128: false,
isStr: true,
isNumStr: false,
isBool: true,
isNull: true,
isGeoPoint: true,
isTimestamp: true,
isBytes: true,
isDocRef: true,
isVector: true,
isMap: true,
isArray: true,
isStrNum: false,
});
});

// TODO(new-expression): Add new expression tests above this line
});

Expand Down
88 changes: 88 additions & 0 deletions types/firestore.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5136,6 +5136,23 @@ declare namespace FirebaseFirestore {
*/
type(): FunctionExpression;

/**
* @beta
* Creates an expression that checks if the result of this expression is of the given type.
*
* @remarks Null or undefined fields evaluate to skip/error. Use `ifAbsent()` / `isAbsent()` to evaluate missing data.
*
* @example
* ```typescript
* // Check if the 'price' field is specifically an integer (not just 'number')
* field('price').isType('int64');
* ```
*
* @param type The type to check for.
* @returns A new `BooleanExpression` that evaluates to true if the expression's result is of the given type, false otherwise.
*/
isType(type: Type): BooleanExpression;

// TODO(new-expression): Add new expression method declarations above this line
/**
* @beta
Expand Down Expand Up @@ -10128,6 +10145,39 @@ declare namespace FirebaseFirestore {
timezone?: string | Expression,
): FunctionExpression;

/**
* @beta
*
* An enumeration of the different types generated by the Firestore backend.
*
* <ul>
* <li>Numerics evaluate directly to backend representation (`int64` or `float64`), not JS `number`.</li>
* <li>JavaScript `Date` and firestore `Timestamp` objects strictly evaluate to `'timestamp'`.</li>
* <li>Advanced configurations parsing backend types (such as `decimal128`, `max_key` or `min_key` from BSON) are also incorporated in this union string type. Note that `decimal128` is a backend-only numeric type that the JavaScript SDK cannot create natively, but can be evaluated in pipelines.</li>
* </ul>
*/
export type Type =
| 'null'
| 'array'
| 'boolean'
| 'bytes'
| 'timestamp'
| 'geo_point'
| 'number'
| 'int32'
| 'int64'
| 'float64'
| 'decimal128'
| 'map'
| 'reference'
| 'string'
| 'vector'
| 'max_key'
| 'min_key'
| 'object_id'
| 'regex'
| 'request_timestamp';

/**
* @beta
* Creates an expression that returns the data type of the data in the specified field.
Expand Down Expand Up @@ -10155,6 +10205,44 @@ declare namespace FirebaseFirestore {
*/
export function type(expression: Expression): FunctionExpression;

/**
* @beta
* Creates an expression that checks if the value in the specified field is of the given type.
*
* @remarks Null or undefined fields evaluate to skip/error. Use `ifAbsent()` / `isAbsent()` to evaluate missing data.
*
* @example
* ```typescript
* // Check if the 'price' field is a floating point number (evaluating to true inside pipeline conditionals)
* isType('price', 'float64');
* ```
*
* @param fieldName The name of the field to check.
* @param type The type to check for.
* @returns A new `BooleanExpression` that evaluates to true if the field's value is of the given type, false otherwise.
*/
export function isType(fieldName: string, type: Type): BooleanExpression;
/**
* @beta
* Creates an expression that checks if the result of an expression is of the given type.
*
* @remarks Null or undefined fields evaluate to skip/error. Use `ifAbsent()` / `isAbsent()` to evaluate missing data.
*
* @example
* ```typescript
* // Check if the result of a calculation is a number
* isType(add('count', 1), 'number')
* ```
*
* @param expression The expression to check.
* @param type The type to check for.
* @returns A new `BooleanExpression` that evaluates to true if the expression's result is of the given type, false otherwise.
*/
export function isType(
expression: Expression,
type: Type,
): BooleanExpression;

// TODO(new-expression): Add new top-level expression function declarations above this line
/**
* @beta
Expand Down
Loading