Skip to content

Commit ad24ec3

Browse files
YuvAIRYuvix25
andauthored
Allow using custom masks to enable field removal (#143)
Co-authored-by: Yuval Rosen <[email protected]>
1 parent 292233e commit ad24ec3

File tree

4 files changed

+46
-6
lines changed

4 files changed

+46
-6
lines changed

.github/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ To update only specific fields of a document at this location, we can set the `m
9090
firestore.updateDocument("FirstCollection/FirstDocument", data, true);
9191
```
9292

93+
Or alternatiavely, we can set the `mask` parameter to an array of field names:
94+
```javascript
95+
firestore.updateDocument("FirstCollection/FirstDocument", data, ["field1", "field2", "fieldN"]);
96+
```
97+
this is useful for [this](https://firebase.google.com/docs/firestore/reference/rest/v1beta1/projects.databases.documents/patch#query-parameters):
98+
> If the document exists on the server and has fields not referenced in the mask, they are left unchanged. Fields referenced in the mask, but not present in the input document (the `data` in our example), are deleted from the document on the server.
99+
93100
##### Deleting Documents
94101
To delete a document at this location, we can use the `deleteDocument` function:
95102
```javascript

Firestore.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,12 @@ class Firestore implements FirestoreRead, FirestoreWrite, FirestoreDelete {
9393
*
9494
* @param {string} path the path of the document to update. If document name not provided, a random ID will be generated.
9595
* @param {object} fields the document's new fields
96-
* @param {boolean} mask if true, the update will use a mask
96+
* @param {boolean|string[]} mask if true, the update will mask the given fields,
97+
* if is an array (of field names), that array would be used as the mask.
98+
* (that way you can, for example, include a field in `mask`, but not in `fields`, and by doing so, delete that field)
9799
* @return {object} the Document object written to Firestore
98100
*/
99-
updateDocument(path: string, fields: Record<string, any>, mask = false): Document {
101+
updateDocument(path: string, fields: Record<string, any>, mask?: boolean | string[]): Document {
100102
const request = new Request(this.baseUrl, this.authToken);
101103
return this.updateDocument_(path, fields, request, mask);
102104
}

FirestoreWrite.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,25 @@ class FirestoreWrite {
3030
* @param {string} path the path of the document to update
3131
* @param {object} fields the document's new fields
3232
* @param {string} request the Firestore Request object to manipulate
33-
* @param {boolean} mask if true, the update will use a mask. i.e. true: updates only specific fields, false: overwrites document with specified fields
33+
* @param {boolean|string[]} mask the update will mask the given fields,
34+
* if is an array (of field names), that array would be used as the mask. i.e. true: updates only specific fields, false: overwrites document with specified fields
35+
* see jsdoc of the `updateDocument` method in Firestore.ts for more details
3436
* @return {object} the Document object written to Firestore
3537
*/
36-
updateDocument_(path: string, fields: Record<string, any>, request: Request, mask = false): Document {
38+
updateDocument_(path: string, fields: Record<string, any>, request: Request, mask?: boolean | string[]): Document {
3739
if (mask) {
40+
const maskData = typeof mask === 'boolean' ? Object.keys(fields) : mask;
41+
42+
// Object.keys always returns an array, so this is only for when the given mask is not a boolean.
43+
if (!Array.isArray(maskData)) {
44+
throw new Error('Mask must be a boolean or an array of strings!');
45+
}
46+
3847
// abort request if fields object is empty
39-
if (!Object.keys(fields).length) {
48+
if (!maskData.length) {
4049
throw new Error('Missing fields in Mask!');
4150
}
42-
for (const field in fields) {
51+
for (const field of maskData) {
4352
request.addParam('updateMask.fieldPaths', `\`${field.replace(/`/g, '\\`')}\``);
4453
}
4554
}

Tests.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,28 @@ class Tests implements TestManager {
168168
GSUnit.assertObjectEquals(expected, updatedDoc.obj);
169169
}
170170

171+
Test_Update_Document_Mask_Array(): void {
172+
const expected: { [key: string]: string } = {
173+
field1: 'value1',
174+
field2: 'value2',
175+
field3: 'value3',
176+
};
177+
const path = 'Test Collection/Updatable Document MaskArray';
178+
this.db.createDocument(path, expected);
179+
const updater: { [key: string]: string } = { field2: 'new value2' };
180+
const updaterMask = ['field1', 'field2'];
181+
const updatedDoc = this.db.updateDocument(path, updater, updaterMask);
182+
for (const field of updaterMask) {
183+
if (field in updater) {
184+
expected[field] = updater[field];
185+
} else {
186+
delete expected[field];
187+
}
188+
}
189+
GSUnit.assertEquals(path, updatedDoc.path);
190+
GSUnit.assertObjectEquals(expected, updatedDoc.obj);
191+
}
192+
171193
Test_Update_Document_Overwrite_Missing(): void {
172194
const path = 'Test Collection/Missing Document Overwrite';
173195
const expected = { 'boolean value': false };

0 commit comments

Comments
 (0)