Skip to content
This repository was archived by the owner on Mar 4, 2026. It is now read-only.

Commit 14f3e69

Browse files
Adding Numeric Add
1 parent e6a3a68 commit 14f3e69

20 files changed

Lines changed: 281 additions & 83 deletions

dev/protos/firestore_proto_api.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4424,6 +4424,9 @@ export namespace google {
44244424
/** FieldTransform setToServerValue */
44254425
setToServerValue?: (google.firestore.v1beta1.DocumentTransform.FieldTransform.ServerValue|null);
44264426

4427+
/** FieldTransform numericAdd */
4428+
numericAdd?: (google.firestore.v1beta1.IValue|null);
4429+
44274430
/** FieldTransform appendMissingElements */
44284431
appendMissingElements?: (google.firestore.v1beta1.IArrayValue|null);
44294432

@@ -4446,14 +4449,17 @@ export namespace google {
44464449
/** FieldTransform setToServerValue. */
44474450
public setToServerValue: google.firestore.v1beta1.DocumentTransform.FieldTransform.ServerValue;
44484451

4452+
/** FieldTransform numericAdd. */
4453+
public numericAdd?: (google.firestore.v1beta1.IValue|null);
4454+
44494455
/** FieldTransform appendMissingElements. */
44504456
public appendMissingElements?: (google.firestore.v1beta1.IArrayValue|null);
44514457

44524458
/** FieldTransform removeAllFromArray. */
44534459
public removeAllFromArray?: (google.firestore.v1beta1.IArrayValue|null);
44544460

44554461
/** FieldTransform transformType. */
4456-
public transformType?: ("setToServerValue"|"appendMissingElements"|"removeAllFromArray");
4462+
public transformType?: ("setToServerValue"|"numericAdd"|"appendMissingElements"|"removeAllFromArray");
44574463
}
44584464

44594465
namespace FieldTransform {

dev/protos/firestore_proto_api.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7705,6 +7705,7 @@ $root.google = (function() {
77057705
* @interface IFieldTransform
77067706
* @property {string|null} [fieldPath] FieldTransform fieldPath
77077707
* @property {google.firestore.v1beta1.DocumentTransform.FieldTransform.ServerValue|null} [setToServerValue] FieldTransform setToServerValue
7708+
* @property {google.firestore.v1beta1.IValue|null} [numericAdd] FieldTransform numericAdd
77087709
* @property {google.firestore.v1beta1.IArrayValue|null} [appendMissingElements] FieldTransform appendMissingElements
77097710
* @property {google.firestore.v1beta1.IArrayValue|null} [removeAllFromArray] FieldTransform removeAllFromArray
77107711
*/
@@ -7740,6 +7741,14 @@ $root.google = (function() {
77407741
*/
77417742
FieldTransform.prototype.setToServerValue = 0;
77427743

7744+
/**
7745+
* FieldTransform numericAdd.
7746+
* @member {google.firestore.v1beta1.IValue|null|undefined} numericAdd
7747+
* @memberof google.firestore.v1beta1.DocumentTransform.FieldTransform
7748+
* @instance
7749+
*/
7750+
FieldTransform.prototype.numericAdd = null;
7751+
77437752
/**
77447753
* FieldTransform appendMissingElements.
77457754
* @member {google.firestore.v1beta1.IArrayValue|null|undefined} appendMissingElements
@@ -7761,12 +7770,12 @@ $root.google = (function() {
77617770

77627771
/**
77637772
* FieldTransform transformType.
7764-
* @member {"setToServerValue"|"appendMissingElements"|"removeAllFromArray"|undefined} transformType
7773+
* @member {"setToServerValue"|"numericAdd"|"appendMissingElements"|"removeAllFromArray"|undefined} transformType
77657774
* @memberof google.firestore.v1beta1.DocumentTransform.FieldTransform
77667775
* @instance
77677776
*/
77687777
Object.defineProperty(FieldTransform.prototype, "transformType", {
7769-
get: $util.oneOfGetter($oneOfFields = ["setToServerValue", "appendMissingElements", "removeAllFromArray"]),
7778+
get: $util.oneOfGetter($oneOfFields = ["setToServerValue", "numericAdd", "appendMissingElements", "removeAllFromArray"]),
77707779
set: $util.oneOfSetter($oneOfFields)
77717780
});
77727781

dev/protos/google/firestore/v1beta1/write.proto

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ message DocumentTransform {
8989
// Sets the field to the given server value.
9090
ServerValue set_to_server_value = 2;
9191

92+
// Adds the given value to the field's current value.
93+
//
94+
// This must be an integer or a double value.
95+
// If the field is not an integer or double, or if the field does not yet
96+
// exist, the transformation will set the field to the given value.
97+
// If either of the given value or the current field value are doubles,
98+
// both values will be interpreted as doubles. Double arithmetic and
99+
// representation of double values follow IEEE 754 semantics.
100+
// If there is positive/negative integer overflow, the field is resolved
101+
// to the largest magnitude positive/negative integer.
102+
Value numeric_add = 3;
103+
92104
// Append the given elements in order if they are not already present in
93105
// the current field value.
94106
// If the field is not an array, or if the field does not yet exist, it is

dev/src/field-value.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,38 @@ export class FieldValue {
8282
return ServerTimestampTransform.SERVER_TIMESTAMP_SENTINEL;
8383
}
8484

85+
/**
86+
* Returns a special value that can be used with set(), create() or update()
87+
* that tells the server to add the given value to the field's current
88+
* value.
89+
*
90+
* If the either current field value or the operand uses floating point
91+
* precision, both values will be interpreted as floating point numbers and
92+
* all arithmetic will follow IEEE 754 semantics. Otherwise, integer
93+
* precision is kept and the result is capped between -2^63 and 2^63-1.
94+
*
95+
* If the current field value is not of type 'number', or if the field does
96+
* not yet exist, the transformation will set the field to the given value.
97+
*
98+
* @param n The value to add.
99+
* @return The FieldValue sentinel for use in a call to set() or update().
100+
*
101+
* @example
102+
* let documentRef = firestore.doc('col/doc');
103+
*
104+
* documentRef.update(
105+
* 'counter', Firestore.FieldValue.numericAdd(1)
106+
* ).then(() => {
107+
* return documentRef.get();
108+
* }).then(doc => {
109+
* // doc.get('counter') was incremented
110+
* });
111+
*/
112+
static numericAdd(n: number): FieldValue {
113+
validate.minNumberOfArguments('FieldValue.numericAdd', arguments, 1);
114+
return new NumericAddTransform(n);
115+
}
116+
85117
/**
86118
* Returns a special value that can be used with set(), create() or update()
87119
* that tells the server to union the given elements with any array value that
@@ -280,6 +312,52 @@ class ServerTimestampTransform extends FieldTransform {
280312
}
281313
}
282314

315+
/**
316+
* Increments a field value on the backend.
317+
*
318+
* @private
319+
*/
320+
class NumericAddTransform extends FieldTransform {
321+
constructor(private readonly operand: number) {
322+
super();
323+
}
324+
325+
/**
326+
* Numeric transforms are omitted from document masks.
327+
*/
328+
get includeInDocumentMask(): false {
329+
return false;
330+
}
331+
332+
/**
333+
* Numeric transforms are included in document transforms.
334+
*/
335+
get includeInDocumentTransform(): true {
336+
return true;
337+
}
338+
339+
get methodName(): string {
340+
return 'FieldValue.numericAdd';
341+
}
342+
343+
validate(validator: AnyDuringMigration): boolean {
344+
return validator.isNumber('FieldValue.numericAdd()', this.operand);
345+
}
346+
347+
toProto(serializer: Serializer, fieldPath: FieldPath):
348+
api.DocumentTransform.IFieldTransform {
349+
const encodedOperand = serializer.encodeValue(this.operand)!;
350+
return {fieldPath: fieldPath.formattedName, numericAdd: encodedOperand};
351+
}
352+
353+
isEqual(other: FieldValue): boolean {
354+
return (
355+
this === other ||
356+
(other instanceof NumericAddTransform &&
357+
this.operand === other.operand));
358+
}
359+
}
360+
283361
/**
284362
* Transforms an array value via a union operation.
285363
*

dev/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ export class Firestore {
409409

410410
const path = this._referencePath!.append(documentPath);
411411
if (!path.isDocument) {
412-
throw new Error(`Argument "documentPath" must point to a document, but was "${
412+
throw new Error(`Value for "documentPath" must point to a document, but was "${
413413
documentPath}". Your path does not contain an even number of components.`);
414414
}
415415

@@ -437,7 +437,7 @@ export class Firestore {
437437

438438
const path = this._referencePath!.append(collectionPath);
439439
if (!path.isCollection) {
440-
throw new Error(`Argument "collectionPath" must point to a collection, but was "${
440+
throw new Error(`Value for "collectionPath" must point to a collection, but was "${
441441
collectionPath}". Your path does not contain an odd number of components.`);
442442
}
443443

dev/src/reference.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ export class DocumentReference {
238238

239239
const path = this._path.append(collectionPath);
240240
if (!path.isCollection) {
241-
throw new Error(`Argument "collectionPath" must point to a collection, but was "${
241+
throw new Error(`Value for "collectionPath" must point to a collection, but was "${
242242
collectionPath}". Your path does not contain an odd number of components.`);
243243
}
244244

@@ -1888,7 +1888,7 @@ export class CollectionReference extends Query {
18881888

18891889
const path = this._path.append(documentPath!);
18901890
if (!path.isDocument) {
1891-
throw new Error(`Argument "documentPath" must point to a document, but was "${
1891+
throw new Error(`Value for "documentPath" must point to a document, but was "${
18921892
documentPath}". Your path does not contain an even number of components.`);
18931893
}
18941894

dev/src/validate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export class Validator {
9292
let valid = false;
9393
let message = is.number(argumentName) ?
9494
`Argument at index ${argumentName} is not a valid ${type}.` :
95-
`Argument "${argumentName}" is not a valid ${type}.`;
95+
`Value for "${argumentName}" is not a valid ${type}.`;
9696

9797
try {
9898
valid = validators[type].call(null, ...values);

dev/system-test/firestore.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,20 @@ describe('DocumentReference class', () => {
287287
});
288288
});
289289

290+
it('supports numericAdd()', () => {
291+
const baseData = {sum: 1};
292+
const updateData = {sum: FieldValue.numericAdd(1)};
293+
const expectedData = {sum: 2};
294+
295+
const ref = randomCol.doc('doc');
296+
return ref.set(baseData)
297+
.then(() => ref.update(updateData))
298+
.then(() => ref.get())
299+
.then(doc => {
300+
expect(doc.data()).to.deep.equal(expectedData);
301+
});
302+
});
303+
290304
it('supports arrayUnion()', () => {
291305
const baseObject = {
292306
a: [],

dev/test/collection.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,19 @@ describe('Collection interface', () => {
4444

4545
expect(() => collectionRef.doc(false))
4646
.to.throw(
47-
/Argument "documentPath" is not a valid ResourcePath. Path must be a non-empty string./);
47+
/Value for "documentPath" is not a valid ResourcePath. Path must be a non-empty string./);
4848
expect(() => collectionRef.doc(null))
4949
.to.throw(
50-
/Argument "documentPath" is not a valid ResourcePath. Path must be a non-empty string./);
50+
/Value for "documentPath" is not a valid ResourcePath. Path must be a non-empty string./);
5151
expect(() => collectionRef.doc(''))
5252
.to.throw(
53-
/Argument "documentPath" is not a valid ResourcePath. Path must be a non-empty string./);
53+
/Value for "documentPath" is not a valid ResourcePath. Path must be a non-empty string./);
5454
expect(() => collectionRef.doc(undefined))
5555
.to.throw(
56-
/Argument "documentPath" is not a valid ResourcePath. Path must be a non-empty string./);
56+
/Value for "documentPath" is not a valid ResourcePath. Path must be a non-empty string./);
5757
expect(() => collectionRef.doc('doc/coll'))
5858
.to.throw(
59-
/Argument "documentPath" must point to a document, but was "doc\/coll". Your path does not contain an even number of components\./);
59+
/Value for "documentPath" must point to a document, but was "doc\/coll". Your path does not contain an even number of components\./);
6060

6161
documentRef = collectionRef.doc('docId/colId/docId');
6262
expect(documentRef).to.be.an.instanceOf(DocumentReference);

0 commit comments

Comments
 (0)