Skip to content
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
85 changes: 84 additions & 1 deletion dev/src/field-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import * as proto from '../protos/firestore_proto_api';

import {FieldPath} from './path';
import {Serializer, validateUserInput} from './serializer';
import {validateMinNumberOfArguments} from './validate';
import {validateMinNumberOfArguments, validateNumber} from './validate';

import api = proto.google.firestore.v1;

Expand Down Expand Up @@ -79,6 +79,39 @@ export class FieldValue {
return ServerTimestampTransform.SERVER_TIMESTAMP_SENTINEL;
}

/**
* Returns a special value that can be used with set(), create() or update()
* that tells the server to increment the the field's current value by the
* given value.
*
* If either current field value or the operand uses floating point
* precision, both values will be interpreted as floating point numbers and
* all arithmetic will follow IEEE 754 semantics. Otherwise, integer
* precision is kept and the result is capped between -2^63 and 2^63-1.
*
* If the current field value is not of type 'number', or if the field does
* not yet exist, the transformation will set the field to the given value.
*
* @param {number} n The value to increment by.
* @return {FieldValue} The FieldValue sentinel for use in a call to set(),
* create() or update().
*
* @example
* let documentRef = firestore.doc('col/doc');
*
* documentRef.update(
* 'counter', Firestore.FieldValue.increment(1)
* ).then(() => {
* return documentRef.get();
* }).then(doc => {
* // doc.get('counter') was incremented
* });
*/
static increment(n: number): FieldValue {
validateMinNumberOfArguments('FieldValue.increment', arguments, 1);
return new NumericIncrementTransform(n);
}

/**
* Returns a special value that can be used with set(), create() or update()
* that tells the server to union the given elements with any array value that
Expand Down Expand Up @@ -276,6 +309,56 @@ class ServerTimestampTransform extends FieldTransform {
}
}

/**
* Increments a field value on the backend.
*
* @private
*/
class NumericIncrementTransform extends FieldTransform {
constructor(private readonly operand: number) {
super();
}

/**
* Numeric transforms are omitted from document masks.
*
* @private
*/
get includeInDocumentMask(): false {
return false;
}

/**
* Numeric transforms are included in document transforms.
*
* @private
*/
get includeInDocumentTransform(): true {
return true;
}

get methodName(): string {
return 'FieldValue.increment';
}

validate(): void {
validateNumber('FieldValue.increment()', this.operand);
}

toProto(serializer: Serializer, fieldPath: FieldPath):
api.DocumentTransform.IFieldTransform {
const encodedOperand = serializer.encodeValue(this.operand)!;
return {fieldPath: fieldPath.formattedName, increment: encodedOperand};
}

isEqual(other: FieldValue): boolean {
return (
this === other ||
(other instanceof NumericIncrementTransform &&
this.operand === other.operand));
}
}

/**
* Transforms an array value via a union operation.
*
Expand Down
4 changes: 2 additions & 2 deletions dev/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ export class Firestore {

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

Expand Down Expand Up @@ -426,7 +426,7 @@ export class Firestore {

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

Expand Down
4 changes: 2 additions & 2 deletions dev/src/reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ export class DocumentReference {

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

Expand Down Expand Up @@ -1879,7 +1879,7 @@ export class CollectionReference extends Query {

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

Expand Down
3 changes: 2 additions & 1 deletion dev/src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ export class Transaction {
return refOrQuery._get(this._transactionId);
}

throw new Error('Argument "refOrQuery" must be a DocumentRef or a Query.');
throw new Error(
'Value for argument "refOrQuery" must be a DocumentReference or a Query.');
}

/**
Expand Down
21 changes: 10 additions & 11 deletions dev/src/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,8 @@ export function validateNumber(
if (typeof value !== 'number' || isNaN(value)) {
throw new Error(invalidArgumentMessage(arg, 'number'));
} else if (value < min || value > max) {
throw new Error(
`Value for argument ${formatArgumentName(arg)} must be within [${
min}, ${max}] inclusive, but was: ${value}`);
throw new Error(`${formatArgumentName(arg)} must be within [${min}, ${
max}] inclusive, but was: ${value}`);
}
}
}
Expand All @@ -210,9 +209,8 @@ export function validateInteger(
if (typeof value !== 'number' || isNaN(value) || value % 1 !== 0) {
throw new Error(invalidArgumentMessage(arg, 'integer'));
} else if (value < min || value > max) {
throw new Error(
`Value for argument ${formatArgumentName(arg)} must be within [${
min}, ${max}] inclusive, but was: ${value}`);
throw new Error(`${formatArgumentName(arg)} must be within [${min}, ${
max}] inclusive, but was: ${value}`);
}
}
}
Expand All @@ -226,7 +224,7 @@ export function validateInteger(
*/
export function invalidArgumentMessage(
arg: string|number, expectedType: string) {
return `Argument ${formatArgumentName(arg)} is not a valid ${expectedType}.`;
return `${formatArgumentName(arg)} is not a valid ${expectedType}.`;
}

/**
Expand Down Expand Up @@ -262,7 +260,8 @@ function formatPlural(num: number, str: string): string {
* @return Either the argument name or its index description.
*/
function formatArgumentName(arg: string|number): string {
return typeof arg === 'string' ? `"${arg}"` : `at index ${arg}`;
return typeof arg === 'string' ? `Value for argument "${arg}"` :
`Element at index ${arg}`;
}

/**
Expand Down Expand Up @@ -323,8 +322,8 @@ export function validateEnumValue(
expectedDescription.push(allowed);
}

throw new Error(`Invalid value for argument ${
formatArgumentName(
arg)}. Acceptable values are: ${expectedDescription.join(', ')}`);
throw new Error(
`${formatArgumentName(arg)} is invalid. Acceptable values are: ${
expectedDescription.join(', ')}`);
}
}
14 changes: 14 additions & 0 deletions dev/system-test/firestore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,20 @@ describe('DocumentReference class', () => {
});
});

it('supports increment()', () => {
const baseData = {sum: 1};
const updateData = {sum: FieldValue.increment(1)};
const expectedData = {sum: 2};

const ref = randomCol.doc('doc');
return ref.set(baseData)
.then(() => ref.update(updateData))
.then(() => ref.get())
.then(doc => {
expect(doc.data()).to.deep.equal(expectedData);
});
});

it('supports arrayUnion()', () => {
const baseObject = {
a: [],
Expand Down
10 changes: 5 additions & 5 deletions dev/test/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,19 @@ describe('Collection interface', () => {

expect(() => collectionRef.doc(false as InvalidApiUsage))
.to.throw(
'Argument "documentPath" is not a valid resource path. Path must be a non-empty string.');
'Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string.');
expect(() => collectionRef.doc(null as InvalidApiUsage))
.to.throw(
'Argument "documentPath" is not a valid resource path. Path must be a non-empty string.');
'Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string.');
expect(() => collectionRef.doc(''))
.to.throw(
'Argument "documentPath" is not a valid resource path. Path must be a non-empty string.');
'Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string.');
expect(() => collectionRef.doc(undefined))
.to.throw(
'Argument "documentPath" is not a valid resource path. Path must be a non-empty string.');
'Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string.');
expect(() => collectionRef.doc('doc/coll'))
.to.throw(
'Argument "documentPath" must point to a document, but was "doc\/coll". Your path does not contain an even number of components.');
'Value for argument "documentPath" must point to a document, but was "doc\/coll". Your path does not contain an even number of components.');

documentRef = collectionRef.doc('docId/colId/docId');
expect(documentRef).to.be.an.instanceOf(DocumentReference);
Expand Down
Loading