Skip to content

Commit a957d31

Browse files
Add FieldValue methods to the Lite SDK
1 parent 6e6dbe2 commit a957d31

File tree

5 files changed

+213
-47
lines changed

5 files changed

+213
-47
lines changed

packages/firestore/lite/index.node.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ export {
2626
getFirestore
2727
} from './src/api/database';
2828

29+
export {
30+
FieldValue,
31+
deleteField,
32+
increment,
33+
arrayRemove,
34+
arrayUnion,
35+
serverTimestamp
36+
} from './src/api/field_value';
37+
2938
export function registerFirestore(): void {
3039
_registerComponent(
3140
new Component(
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/**
2+
* @license
3+
* Copyright 2020 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import * as firestore from '../../';
19+
20+
import { validateAtLeastNumberOfArgs } from '../../../src/util/input_validation';
21+
import {
22+
ArrayRemoveFieldValueImpl,
23+
ArrayUnionFieldValueImpl,
24+
DeleteFieldValueImpl,
25+
NumericIncrementFieldValueImpl,
26+
SerializableFieldValue,
27+
ServerTimestampFieldValueImpl
28+
} from '../../../src/api/field_value';
29+
import { ParseContext } from '../../../src/api/user_data_reader';
30+
import { FieldTransform } from '../../../src/model/mutation';
31+
32+
/** The public FieldValue class of the lite API. */
33+
export abstract class FieldValue extends SerializableFieldValue
34+
implements firestore.FieldValue {}
35+
36+
/**
37+
* A delegate class that allows the FieldValue implementations returned by
38+
* deleteField(), serverTimestamp(), arrayUnion(), arrayRemove() and
39+
* increment() to be an instance of the lite FieldValue class declared above.
40+
*
41+
* We don't directly subclass `FieldValue` in the various field value
42+
* implementations as the base FieldValue class differs between the lite, full
43+
* and legacy SDK.
44+
*/
45+
class FieldValueDelegate extends FieldValue implements firestore.FieldValue {
46+
readonly _methodName: string;
47+
48+
constructor(readonly _delegate: SerializableFieldValue) {
49+
super();
50+
this._methodName = _delegate._methodName;
51+
}
52+
53+
_toFieldTransform(context: ParseContext): FieldTransform | null {
54+
return this._delegate._toFieldTransform(context);
55+
}
56+
57+
isEqual(other: firestore.FieldValue): boolean {
58+
if (!(other instanceof FieldValueDelegate)) {
59+
return false;
60+
}
61+
return this._delegate.isEqual(other._delegate);
62+
}
63+
}
64+
65+
export function deleteField(): firestore.FieldValue {
66+
return new FieldValueDelegate(new DeleteFieldValueImpl('delete'));
67+
}
68+
69+
export function serverTimestamp(): firestore.FieldValue {
70+
return new FieldValueDelegate(
71+
new ServerTimestampFieldValueImpl('serverTimestamp')
72+
);
73+
}
74+
75+
export function arrayUnion(...elements: unknown[]): firestore.FieldValue {
76+
validateAtLeastNumberOfArgs('arrayUnion()', arguments, 1);
77+
// NOTE: We don't actually parse the data until it's used in set() or
78+
// update() since we need access to the Firestore instance.
79+
return new FieldValueDelegate(
80+
new ArrayUnionFieldValueImpl('arrayUnion', elements)
81+
);
82+
}
83+
84+
export function arrayRemove(...elements: unknown[]): firestore.FieldValue {
85+
validateAtLeastNumberOfArgs('arrayRemove()', arguments, 1);
86+
// NOTE: We don't actually parse the data until it's used in set() or
87+
// update() since we need access to the Firestore instance.
88+
return new FieldValueDelegate(
89+
new ArrayRemoveFieldValueImpl('arrayRemove', elements)
90+
);
91+
}
92+
93+
export function increment(n: number): firestore.FieldValue {
94+
return new FieldValueDelegate(
95+
new NumericIncrementFieldValueImpl('increment', n)
96+
);
97+
}

packages/firestore/src/api/field_value.ts

Lines changed: 93 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,29 @@ import { ParseContext, parseData, UserDataSource } from './user_data_reader';
3333
import { debugAssert } from '../util/assert';
3434

3535
/**
36-
* An opaque base class for FieldValue sentinel objects in our public API,
37-
* with public static methods for creating said sentinel objects.
36+
* An opaque base class for FieldValue sentinel objects in our public API that
37+
* is shared between the full, lite and legacy SDK.
3838
*/
39-
export abstract class FieldValueImpl {
40-
protected constructor(readonly _methodName: string) {}
39+
export abstract class SerializableFieldValue {
40+
/** A pointer to the implementing class. */
41+
abstract readonly _delegate: SerializableFieldValue;
4142

42-
abstract toFieldTransform(context: ParseContext): FieldTransform | null;
43+
/** The public API endpoint that returns this class. */
44+
abstract readonly _methodName: string;
4345

44-
abstract isEqual(other: FieldValue): boolean;
46+
abstract _toFieldTransform(context: ParseContext): FieldTransform | null;
47+
48+
abstract isEqual(other: SerializableFieldValue): boolean;
4549
}
4650

47-
export class DeleteFieldValueImpl extends FieldValueImpl {
48-
constructor() {
49-
super('FieldValue.delete');
51+
export class DeleteFieldValueImpl extends SerializableFieldValue {
52+
_delegate = this;
53+
54+
constructor(readonly _methodName: string) {
55+
super();
5056
}
5157

52-
toFieldTransform(context: ParseContext): null {
58+
_toFieldTransform(context: ParseContext): null {
5359
if (context.dataSource === UserDataSource.MergeSet) {
5460
// No transform to add for a delete, but we need to add it to our
5561
// fieldMask so it gets deleted.
@@ -79,12 +85,14 @@ export class DeleteFieldValueImpl extends FieldValueImpl {
7985
}
8086
}
8187

82-
export class ServerTimestampFieldValueImpl extends FieldValueImpl {
83-
constructor() {
84-
super('FieldValue.serverTimestamp');
88+
export class ServerTimestampFieldValueImpl extends SerializableFieldValue {
89+
_delegate = this;
90+
91+
constructor(readonly _methodName: string) {
92+
super();
8593
}
8694

87-
toFieldTransform(context: ParseContext): FieldTransform {
95+
_toFieldTransform(context: ParseContext): FieldTransform {
8896
return new FieldTransform(context.path!, ServerTimestampTransform.instance);
8997
}
9098

@@ -93,12 +101,17 @@ export class ServerTimestampFieldValueImpl extends FieldValueImpl {
93101
}
94102
}
95103

96-
export class ArrayUnionFieldValueImpl extends FieldValueImpl {
97-
constructor(private readonly _elements: unknown[]) {
98-
super('FieldValue.arrayUnion');
104+
export class ArrayUnionFieldValueImpl extends SerializableFieldValue {
105+
_delegate = this;
106+
107+
constructor(
108+
readonly _methodName: string,
109+
private readonly _elements: unknown[]
110+
) {
111+
super();
99112
}
100113

101-
toFieldTransform(context: ParseContext): FieldTransform {
114+
_toFieldTransform(context: ParseContext): FieldTransform {
102115
// Although array transforms are used with writes, the actual elements
103116
// being uniomed or removed are not considered writes since they cannot
104117
// contain any FieldValue sentinels, etc.
@@ -125,12 +138,14 @@ export class ArrayUnionFieldValueImpl extends FieldValueImpl {
125138
}
126139
}
127140

128-
export class ArrayRemoveFieldValueImpl extends FieldValueImpl {
129-
constructor(readonly _elements: unknown[]) {
130-
super('FieldValue.arrayRemove');
141+
export class ArrayRemoveFieldValueImpl extends SerializableFieldValue {
142+
_delegate = this;
143+
144+
constructor(readonly _methodName: string, readonly _elements: unknown[]) {
145+
super();
131146
}
132147

133-
toFieldTransform(context: ParseContext): FieldTransform {
148+
_toFieldTransform(context: ParseContext): FieldTransform {
134149
// Although array transforms are used with writes, the actual elements
135150
// being unioned or removed are not considered writes since they cannot
136151
// contain any FieldValue sentinels, etc.
@@ -157,12 +172,14 @@ export class ArrayRemoveFieldValueImpl extends FieldValueImpl {
157172
}
158173
}
159174

160-
export class NumericIncrementFieldValueImpl extends FieldValueImpl {
161-
constructor(private readonly _operand: number) {
162-
super('FieldValue.increment');
175+
export class NumericIncrementFieldValueImpl extends SerializableFieldValue {
176+
_delegate = this;
177+
178+
constructor(readonly _methodName: string, private readonly _operand: number) {
179+
super();
163180
}
164181

165-
toFieldTransform(context: ParseContext): FieldTransform {
182+
_toFieldTransform(context: ParseContext): FieldTransform {
166183
const parseContext = new ParseContext(
167184
{
168185
dataSource: UserDataSource.Argument,
@@ -186,38 +203,75 @@ export class NumericIncrementFieldValueImpl extends FieldValueImpl {
186203
}
187204
}
188205

189-
export class FieldValue implements firestore.FieldValue {
190-
static delete(): FieldValueImpl {
206+
/** The public FieldValue class of the lite API. */
207+
export abstract class FieldValue extends SerializableFieldValue
208+
implements firestore.FieldValue {
209+
static delete(): firestore.FieldValue {
191210
validateNoArgs('FieldValue.delete', arguments);
192-
return new DeleteFieldValueImpl();
211+
return new FieldValueDelegate(
212+
new DeleteFieldValueImpl('FieldValue.delete')
213+
);
193214
}
194215

195-
static serverTimestamp(): FieldValueImpl {
216+
static serverTimestamp(): firestore.FieldValue {
196217
validateNoArgs('FieldValue.serverTimestamp', arguments);
197-
return new ServerTimestampFieldValueImpl();
218+
return new FieldValueDelegate(
219+
new ServerTimestampFieldValueImpl('FieldValue.serverTimestamp')
220+
);
198221
}
199222

200-
static arrayUnion(...elements: unknown[]): FieldValueImpl {
223+
static arrayUnion(...elements: unknown[]): firestore.FieldValue {
201224
validateAtLeastNumberOfArgs('FieldValue.arrayUnion', arguments, 1);
202225
// NOTE: We don't actually parse the data until it's used in set() or
203226
// update() since we need access to the Firestore instance.
204-
return new ArrayUnionFieldValueImpl(elements);
227+
return new FieldValueDelegate(
228+
new ArrayUnionFieldValueImpl('FieldValue.arrayUnion', elements)
229+
);
205230
}
206231

207-
static arrayRemove(...elements: unknown[]): FieldValueImpl {
232+
static arrayRemove(...elements: unknown[]): firestore.FieldValue {
208233
validateAtLeastNumberOfArgs('FieldValue.arrayRemove', arguments, 1);
209234
// NOTE: We don't actually parse the data until it's used in set() or
210235
// update() since we need access to the Firestore instance.
211-
return new ArrayRemoveFieldValueImpl(elements);
236+
return new FieldValueDelegate(
237+
new ArrayRemoveFieldValueImpl('FieldValue.arrayRemove', elements)
238+
);
212239
}
213240

214-
static increment(n: number): FieldValueImpl {
241+
static increment(n: number): firestore.FieldValue {
215242
validateArgType('FieldValue.increment', 'number', 1, n);
216243
validateExactNumberOfArgs('FieldValue.increment', arguments, 1);
217-
return new NumericIncrementFieldValueImpl(n);
244+
return new FieldValueDelegate(
245+
new NumericIncrementFieldValueImpl('FieldValue.increment', n)
246+
);
218247
}
248+
}
219249

220-
isEqual(other: FieldValue): boolean {
221-
return this === other;
250+
/**
251+
* A delegate class that allows the FieldValue implementations returned by
252+
* deleteField(), serverTimestamp(), arrayUnion(), arrayRemove() and
253+
* increment() to be an instance of the legacy FieldValue class declared above.
254+
*
255+
* We don't directly subclass `FieldValue` in the various field value
256+
* implementations as the base FieldValue class differs between the lite, full
257+
* and legacy SDK.
258+
*/
259+
class FieldValueDelegate extends FieldValue implements firestore.FieldValue {
260+
readonly _methodName: string;
261+
262+
constructor(readonly _delegate: SerializableFieldValue) {
263+
super();
264+
this._methodName = _delegate._methodName;
265+
}
266+
267+
_toFieldTransform(context: ParseContext): FieldTransform | null {
268+
return this._delegate._toFieldTransform(context);
269+
}
270+
271+
isEqual(other: firestore.FieldValue): boolean {
272+
if (!(other instanceof FieldValueDelegate)) {
273+
return false;
274+
}
275+
return this._delegate.isEqual(other._delegate);
222276
}
223277
}

packages/firestore/src/api/user_data_reader.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import {
4343
FieldPath as ExternalFieldPath,
4444
fromDotSeparatedString
4545
} from './field_path';
46-
import { DeleteFieldValueImpl, FieldValueImpl } from './field_value';
46+
import { DeleteFieldValueImpl, SerializableFieldValue } from './field_value';
4747
import { GeoPoint } from './geo_point';
4848
import { PlatformSupport } from '../platform/platform';
4949

@@ -383,7 +383,10 @@ export class UserDataReader {
383383
const path = fieldPathFromDotSeparatedString(methodName, key);
384384

385385
const childContext = context.childContextForFieldPath(path);
386-
if (value instanceof DeleteFieldValueImpl) {
386+
if (
387+
value instanceof SerializableFieldValue &&
388+
value._delegate instanceof DeleteFieldValueImpl
389+
) {
387390
// Add it to the field mask, but don't add anything to updateData.
388391
fieldMaskPaths.push(path);
389392
} else {
@@ -442,7 +445,10 @@ export class UserDataReader {
442445
const path = keys[i];
443446
const value = values[i];
444447
const childContext = context.childContextForFieldPath(path);
445-
if (value instanceof DeleteFieldValueImpl) {
448+
if (
449+
value instanceof SerializableFieldValue &&
450+
value._delegate instanceof DeleteFieldValueImpl
451+
) {
446452
// Add it to the field mask, but don't add anything to updateData.
447453
fieldMaskPaths.push(path);
448454
} else {
@@ -523,7 +529,7 @@ export function parseData(
523529
if (looksLikeJsonObject(input)) {
524530
validatePlainObject('Unsupported field value:', context, input);
525531
return parseObject(input, context);
526-
} else if (input instanceof FieldValueImpl) {
532+
} else if (input instanceof SerializableFieldValue) {
527533
// FieldValues usually parse into transforms (except FieldValue.delete())
528534
// in which case we do not want to include this field in our parsed data
529535
// (as doing so will overwrite the field directly prior to the transform
@@ -606,7 +612,7 @@ function parseArray(array: unknown[], context: ParseContext): api.Value {
606612
* context.fieldTransforms.
607613
*/
608614
function parseSentinelFieldValue(
609-
value: FieldValueImpl,
615+
value: SerializableFieldValue,
610616
context: ParseContext
611617
): void {
612618
// Sentinels are only supported with writes, and not within arrays.
@@ -621,7 +627,7 @@ function parseSentinelFieldValue(
621627
);
622628
}
623629

624-
const fieldTransform = value.toFieldTransform(context);
630+
const fieldTransform = value._toFieldTransform(context);
625631
if (fieldTransform) {
626632
context.fieldTransforms.push(fieldTransform);
627633
}
@@ -707,7 +713,7 @@ function looksLikeJsonObject(input: unknown): boolean {
707713
!(input instanceof GeoPoint) &&
708714
!(input instanceof Blob) &&
709715
!(input instanceof DocumentKeyReference) &&
710-
!(input instanceof FieldValueImpl)
716+
!(input instanceof SerializableFieldValue)
711717
);
712718
}
713719

packages/firestore/test/util/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ export function patchMutation(
241241
// Replace '<DELETE>' from JSON with FieldValue
242242
forEach(json, (k, v) => {
243243
if (v === '<DELETE>') {
244-
json[k] = new DeleteFieldValueImpl();
244+
json[k] = new DeleteFieldValueImpl('FieldValue.delete');
245245
}
246246
});
247247
const parsed = testUserDataReader().parseUpdateData('patchMutation', json);

0 commit comments

Comments
 (0)