Skip to content

Commit 1ae13d1

Browse files
Untangle ObjectValue and ObjectValueBuilder (#2970)
1 parent 4ad5733 commit 1ae13d1

File tree

7 files changed

+101
-106
lines changed

7 files changed

+101
-106
lines changed

packages/firestore/src/api/user_data_reader.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import { debugAssert, fail } from '../util/assert';
3636
import { Code, FirestoreError } from '../util/error';
3737
import { isPlainObject, valueDescription } from '../util/input_validation';
3838
import { Dict, forEach, isEmpty } from '../util/obj';
39-
import { ObjectValue } from '../model/field_value';
39+
import { ObjectValue, ObjectValueBuilder } from '../model/field_value';
4040
import {
4141
ArrayRemoveTransformOperation,
4242
ArrayUnionTransformOperation,
@@ -390,7 +390,7 @@ export class UserDataReader {
390390
validatePlainObject('Data must be an object, but it was:', context, input);
391391

392392
let fieldMaskPaths = new SortedSet<FieldPath>(FieldPath.comparator);
393-
const updateData = ObjectValue.newBuilder();
393+
const updateData = new ObjectValueBuilder();
394394
forEach(input as Dict<unknown>, (key, value) => {
395395
const path = fieldPathFromDotSeparatedString(methodName, key);
396396

@@ -450,7 +450,7 @@ export class UserDataReader {
450450
}
451451

452452
let fieldMaskPaths = new SortedSet<FieldPath>(FieldPath.comparator);
453-
const updateData = ObjectValue.newBuilder();
453+
const updateData = new ObjectValueBuilder();
454454

455455
for (let i = 0; i < keys.length; ++i) {
456456
const path = keys[i];

packages/firestore/src/local/local_store.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import { IndexedDbPersistence } from './indexeddb_persistence';
6666
import { IndexedDbMutationQueue } from './indexeddb_mutation_queue';
6767
import { IndexedDbRemoteDocumentCache } from './indexeddb_remote_document_cache';
6868
import { IndexedDbTargetCache } from './indexeddb_target_cache';
69+
import { extractFieldMask } from '../model/field_value';
6970

7071
const LOG_TAG = 'LocalStore';
7172

@@ -339,7 +340,7 @@ export class LocalStore {
339340
new PatchMutation(
340341
mutation.key,
341342
baseValue,
342-
baseValue.fieldMask(),
343+
extractFieldMask(baseValue.proto.mapValue!),
343344
Precondition.exists(true)
344345
)
345346
);

packages/firestore/src/model/field_value.ts

Lines changed: 32 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,8 @@ export class ObjectValue {
5959
);
6060
}
6161

62-
/** Returns a new Builder instance that is based on an empty object. */
63-
static newBuilder(): ObjectValueBuilder {
64-
return ObjectValue.EMPTY.toBuilder();
62+
static empty(): ObjectValue {
63+
return new ObjectValue({ mapValue: {} });
6564
}
6665

6766
/**
@@ -90,48 +89,9 @@ export class ObjectValue {
9089
}
9190
}
9291

93-
/**
94-
* Returns a FieldMask built from all FieldPaths starting from this
95-
* ObjectValue, including paths from nested objects.
96-
*/
97-
fieldMask(): FieldMask {
98-
return this.extractFieldMask(this.proto.mapValue!);
99-
}
100-
101-
private extractFieldMask(value: api.MapValue): FieldMask {
102-
let fields = new SortedSet<FieldPath>(FieldPath.comparator);
103-
forEach(value.fields || {}, (key, value) => {
104-
const currentPath = new FieldPath([key]);
105-
if (typeOrder(value) === TypeOrder.ObjectValue) {
106-
const nestedMask = this.extractFieldMask(value.mapValue!);
107-
const nestedFields = nestedMask.fields;
108-
if (nestedFields.isEmpty()) {
109-
// Preserve the empty map by adding it to the FieldMask.
110-
fields = fields.add(currentPath);
111-
} else {
112-
// For nested and non-empty ObjectValues, add the FieldPath of the
113-
// leaf nodes.
114-
nestedFields.forEach(nestedPath => {
115-
fields = fields.add(currentPath.child(nestedPath));
116-
});
117-
}
118-
} else {
119-
// For nested and non-empty ObjectValues, add the FieldPath of the leaf
120-
// nodes.
121-
fields = fields.add(currentPath);
122-
}
123-
});
124-
return FieldMask.fromSet(fields);
125-
}
126-
12792
isEqual(other: ObjectValue): boolean {
12893
return valueEquals(this.proto, other.proto);
12994
}
130-
131-
/** Creates a ObjectValueBuilder instance that is based on the current value. */
132-
toBuilder(): ObjectValueBuilder {
133-
return new ObjectValueBuilder(this);
134-
}
13595
}
13696

13797
/**
@@ -152,7 +112,7 @@ export class ObjectValueBuilder {
152112
/**
153113
* @param baseObject The object to mutate.
154114
*/
155-
constructor(private readonly baseObject: ObjectValue) {}
115+
constructor(private readonly baseObject: ObjectValue = ObjectValue.empty()) {}
156116

157117
/**
158118
* Sets the field to the provided value.
@@ -278,3 +238,32 @@ export class ObjectValueBuilder {
278238
return modified ? { mapValue: { fields: resultAtPath } } : null;
279239
}
280240
}
241+
242+
/**
243+
* Returns a FieldMask built from all fields in a MapValue.
244+
*/
245+
export function extractFieldMask(value: api.MapValue): FieldMask {
246+
let fields = new SortedSet<FieldPath>(FieldPath.comparator);
247+
forEach(value!.fields || {}, (key, value) => {
248+
const currentPath = new FieldPath([key]);
249+
if (isMapValue(value)) {
250+
const nestedMask = extractFieldMask(value.mapValue!);
251+
const nestedFields = nestedMask.fields;
252+
if (nestedFields.isEmpty()) {
253+
// Preserve the empty map by adding it to the FieldMask.
254+
fields = fields.add(currentPath);
255+
} else {
256+
// For nested and non-empty ObjectValues, add the FieldPath of the
257+
// leaf nodes.
258+
nestedFields.forEach(nestedPath => {
259+
fields = fields.add(currentPath.child(nestedPath));
260+
});
261+
}
262+
} else {
263+
// For nested and non-empty ObjectValues, add the FieldPath of the leaf
264+
// nodes.
265+
fields = fields.add(currentPath);
266+
}
267+
});
268+
return FieldMask.fromSet(fields);
269+
}

packages/firestore/src/model/mutation.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -488,13 +488,13 @@ export class PatchMutation extends Mutation {
488488
if (maybeDoc instanceof Document) {
489489
data = maybeDoc.data();
490490
} else {
491-
data = ObjectValue.EMPTY;
491+
data = ObjectValue.empty();
492492
}
493493
return this.patchObject(data);
494494
}
495495

496496
private patchObject(data: ObjectValue): ObjectValue {
497-
const builder = data.toBuilder();
497+
const builder = new ObjectValueBuilder(data);
498498
this.fieldMask.fields.forEach(fieldPath => {
499499
if (!fieldPath.isEmpty()) {
500500
const newValue = this.data.field(fieldPath);
@@ -601,7 +601,7 @@ export class TransformMutation extends Mutation {
601601

602602
if (coercedValue != null) {
603603
if (baseObject == null) {
604-
baseObject = ObjectValue.newBuilder().set(
604+
baseObject = new ObjectValueBuilder().set(
605605
fieldTransform.field,
606606
coercedValue
607607
);
@@ -729,7 +729,7 @@ export class TransformMutation extends Mutation {
729729
'TransformResults length mismatch.'
730730
);
731731

732-
const builder = data.toBuilder();
732+
const builder = new ObjectValueBuilder(data);
733733
for (let i = 0; i < this.fieldTransforms.length; i++) {
734734
const fieldTransform = this.fieldTransforms[i];
735735
const fieldPath = fieldTransform.field;

packages/firestore/test/unit/model/field_value.test.ts

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818
import * as api from '../../../src/protos/firestore_proto_api';
1919

2020
import { expect } from 'chai';
21-
import { ObjectValue, TypeOrder } from '../../../src/model/field_value';
21+
import {
22+
extractFieldMask,
23+
ObjectValue,
24+
ObjectValueBuilder,
25+
TypeOrder
26+
} from '../../../src/model/field_value';
2227
import { typeOrder } from '../../../src/model/values';
2328
import { wrap, wrapObject, field, mask } from '../../util/helpers';
2429

@@ -79,13 +84,11 @@ describe('FieldValue', () => {
7984
});
8085

8186
it('can add multiple new fields', () => {
82-
let objValue = ObjectValue.EMPTY;
83-
objValue = objValue
84-
.toBuilder()
87+
let objValue = ObjectValue.empty();
88+
objValue = new ObjectValueBuilder(objValue)
8589
.set(field('a'), wrap('a'))
8690
.build();
87-
objValue = objValue
88-
.toBuilder()
91+
objValue = new ObjectValueBuilder(objValue)
8992
.set(field('b'), wrap('b'))
9093
.set(field('c'), wrap('c'))
9194
.build();
@@ -154,8 +157,7 @@ describe('FieldValue', () => {
154157
it('can delete added keys', () => {
155158
let objValue = wrapObject({});
156159

157-
objValue = objValue
158-
.toBuilder()
160+
objValue = new ObjectValueBuilder(objValue)
159161
.set(field('a'), wrap('a'))
160162
.delete(field('a'))
161163
.build();
@@ -189,12 +191,8 @@ describe('FieldValue', () => {
189191
it('can delete multiple fields', () => {
190192
let objValue = wrapObject({ a: 'a', b: 'a', c: 'c' });
191193

192-
objValue = objValue
193-
.toBuilder()
194-
.delete(field('a'))
195-
.build();
196-
objValue = objValue
197-
.toBuilder()
194+
objValue = new ObjectValueBuilder(objValue).delete(field('a')).build();
195+
objValue = new ObjectValueBuilder(objValue)
198196
.delete(field('b'))
199197
.delete(field('c'))
200198
.build();
@@ -216,7 +214,7 @@ describe('FieldValue', () => {
216214
'map.nested.d',
217215
'emptymap'
218216
);
219-
const actualMask = objValue.fieldMask();
217+
const actualMask = extractFieldMask(objValue.proto.mapValue!);
220218
expect(actualMask.isEqual(expectedMask)).to.be.true;
221219
});
222220

@@ -225,8 +223,7 @@ describe('FieldValue', () => {
225223
fieldPath: string,
226224
value: api.Value
227225
): ObjectValue {
228-
return objectValue
229-
.toBuilder()
226+
return new ObjectValueBuilder(objectValue)
230227
.set(field(fieldPath), value)
231228
.build();
232229
}
@@ -235,10 +232,7 @@ describe('FieldValue', () => {
235232
objectValue: ObjectValue,
236233
fieldPath: string
237234
): ObjectValue {
238-
return objectValue
239-
.toBuilder()
240-
.delete(field(fieldPath))
241-
.build();
235+
return new ObjectValueBuilder(objectValue).delete(field(fieldPath)).build();
242236
}
243237

244238
function assertObjectEquals(

packages/firestore/test/unit/model/mutation.test.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
wrap,
4848
wrapObject
4949
} from '../../util/helpers';
50+
import { ObjectValueBuilder } from '../../../src/model/field_value';
5051

5152
describe('Mutation', () => {
5253
addEqualityMatcher();
@@ -191,11 +192,12 @@ describe('Mutation', () => {
191192
);
192193

193194
// Server timestamps aren't parsed, so we manually insert it.
194-
const data = wrapObject({
195-
foo: { bar: '<server-timestamp>' },
196-
baz: 'baz-value'
197-
})
198-
.toBuilder()
195+
const data = new ObjectValueBuilder(
196+
wrapObject({
197+
foo: { bar: '<server-timestamp>' },
198+
baz: 'baz-value'
199+
})
200+
)
199201
.set(field('foo.bar'), serverTimestamp(timestamp, null))
200202
.build();
201203
const expectedDoc = new Document(key('collection/key'), version(0), data, {

0 commit comments

Comments
 (0)