Skip to content

Commit 738f429

Browse files
author
Brian Chen
authored
Merge fdd5072 into c38fc71
2 parents c38fc71 + fdd5072 commit 738f429

File tree

9 files changed

+130
-31
lines changed

9 files changed

+130
-31
lines changed

.changeset/cyan-books-melt.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/firestore': patch
3+
---
4+
5+
Add set() override for merge options in Firestore Lite SDK

packages/firestore/exp/index.d.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ export function setLogLevel(logLevel: LogLevel): void;
6464

6565
export interface FirestoreDataConverter<T> {
6666
toFirestore(modelObject: T): DocumentData;
67-
fromFirestore(snapshot: QueryDocumentSnapshot): T;
67+
toFirestore(modelObject: Partial<T>, options: SetOptions): DocumentData;
68+
fromFirestore(snapshot: QueryDocumentSnapshot<DocumentData>): T;
6869
}
6970

7071
export class FirebaseFirestore {
@@ -217,9 +218,10 @@ export class WriteBatch {
217218
commit(): Promise<void>;
218219
}
219220

220-
export type SetOptions =
221-
| { merge: true }
222-
| { mergeFields: Array<string | FieldPath> };
221+
export interface SetOptions {
222+
readonly merge?: boolean;
223+
readonly mergeFields?: Array<string | FieldPath>;
224+
}
223225

224226
export class DocumentReference<T = DocumentData> {
225227
private constructor();

packages/firestore/lite/index.d.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ export function setLogLevel(logLevel: LogLevel): void;
4545

4646
export interface FirestoreDataConverter<T> {
4747
toFirestore(modelObject: T): DocumentData;
48-
fromFirestore(snapshot: QueryDocumentSnapshot): T;
48+
toFirestore(modelObject: Partial<T>, options: SetOptions): DocumentData;
49+
fromFirestore(snapshot: QueryDocumentSnapshot<DocumentData>): T;
4950
}
5051

5152
export class FirebaseFirestore {
@@ -182,9 +183,10 @@ export class WriteBatch {
182183
commit(): Promise<void>;
183184
}
184185

185-
export type SetOptions =
186-
| { merge: true }
187-
| { mergeFields: Array<string | FieldPath> };
186+
export interface SetOptions {
187+
readonly merge?: boolean;
188+
readonly mergeFields?: Array<string | FieldPath>;
189+
}
188190

189191
export class DocumentReference<T = DocumentData> {
190192
private constructor();

packages/firestore/lite/src/api/reference.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,11 @@ export function setDoc<T>(
456456
): Promise<void> {
457457
const ref = cast(reference, DocumentReference);
458458

459-
const convertedValue = applyFirestoreDataConverter(ref._converter, data);
459+
const convertedValue = applyFirestoreDataConverter(
460+
ref._converter,
461+
data,
462+
options
463+
);
460464
const dataReader = newUserDataReader(ref.firestore);
461465
const parsed = dataReader.parseSetData(
462466
'setDoc',

packages/firestore/lite/src/api/transaction.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,11 @@ export class Transaction implements firestore.Transaction {
9595
options?: firestore.SetOptions
9696
): Transaction {
9797
const ref = validateReference(documentRef, this._firestore);
98-
const convertedValue = applyFirestoreDataConverter(ref._converter, value);
98+
const convertedValue = applyFirestoreDataConverter(
99+
ref._converter,
100+
value,
101+
options
102+
);
99103
const parsed = this._dataReader.parseSetData(
100104
'Transaction.set',
101105
ref._key,

packages/firestore/lite/src/api/write_batch.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,11 @@ export class WriteBatch implements firestore.WriteBatch {
5959
this.verifyNotCommitted();
6060
const ref = validateReference(documentRef, this._firestore);
6161

62-
const convertedValue = applyFirestoreDataConverter(ref._converter, value);
63-
62+
const convertedValue = applyFirestoreDataConverter(
63+
ref._converter,
64+
value,
65+
options
66+
);
6467
const parsed = this._dataReader.parseSetData(
6568
'WriteBatch.set',
6669
ref._key,

packages/firestore/lite/test/helpers.ts

+48
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
DEFAULT_SETTINGS
2727
} from '../../test/integration/util/settings';
2828
import { AutoId } from '../../src/util/misc';
29+
import { expect } from 'chai';
2930

3031
let appCount = 0;
3132

@@ -89,3 +90,50 @@ export function withTestCollection(
8990
return fn(collection(db, AutoId.newId()));
9091
});
9192
}
93+
94+
// Used for testing the FirestoreDataConverter.
95+
export class Post {
96+
constructor(readonly title: string, readonly author: string) {}
97+
byline(): string {
98+
return this.title + ', by ' + this.author;
99+
}
100+
}
101+
102+
export const postConverter = {
103+
toFirestore(post: Post): firestore.DocumentData {
104+
return { title: post.title, author: post.author };
105+
},
106+
fromFirestore(snapshot: firestore.QueryDocumentSnapshot): Post {
107+
const data = snapshot.data();
108+
return new Post(data.title, data.author);
109+
}
110+
};
111+
112+
export const postConverterMerge = {
113+
toFirestore(
114+
post: Partial<Post>,
115+
options?: firestore.SetOptions
116+
): firestore.DocumentData {
117+
if (
118+
options &&
119+
((options as { merge: true }).merge ||
120+
(options as { mergeFields: Array<string | number> }).mergeFields)
121+
) {
122+
expect(post).to.not.be.an.instanceof(Post);
123+
} else {
124+
expect(post).to.be.an.instanceof(Post);
125+
}
126+
const result: firestore.DocumentData = {};
127+
if (post.title) {
128+
result.title = post.title;
129+
}
130+
if (post.author) {
131+
result.author = post.author;
132+
}
133+
return result;
134+
},
135+
fromFirestore(snapshot: firestore.QueryDocumentSnapshot): Post {
136+
const data = snapshot.data();
137+
return new Post(data.title, data.author);
138+
}
139+
};

packages/firestore/lite/test/integration.test.ts

+46-19
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ import {
2828
terminate
2929
} from '../src/api/database';
3030
import {
31+
Post,
32+
postConverter,
33+
postConverterMerge,
3134
withTestCollection,
3235
withTestCollectionAndInitialData,
3336
withTestDb,
@@ -480,6 +483,30 @@ function genericMutationTests(
480483
});
481484
});
482485

486+
it('supports partials with merge', async () => {
487+
return withTestDb(async db => {
488+
const coll = collection(db, 'posts');
489+
const ref = doc(coll, 'post').withConverter(postConverterMerge);
490+
await setDoc(ref, new Post('walnut', 'author'));
491+
await setDoc(ref, { title: 'olive' }, { merge: true });
492+
const postDoc = await getDoc(ref);
493+
expect(postDoc.get('title')).to.equal('olive');
494+
expect(postDoc.get('author')).to.equal('author');
495+
});
496+
});
497+
498+
it('supports partials with mergeFields', async () => {
499+
return withTestDb(async db => {
500+
const coll = collection(db, 'posts');
501+
const ref = doc(coll, 'post').withConverter(postConverterMerge);
502+
await setDoc(ref, new Post('walnut', 'author'));
503+
await setDoc(ref, { title: 'olive' }, { mergeFields: ['title'] });
504+
const postDoc = await getDoc(ref);
505+
expect(postDoc.get('title')).to.equal('olive');
506+
expect(postDoc.get('author')).to.equal('author');
507+
});
508+
});
509+
483510
it('throws when user input fails validation', () => {
484511
return withTestDoc(async docRef => {
485512
if (validationUsesPromises) {
@@ -881,7 +908,8 @@ describe('equality', () => {
881908
expect(refEqual(coll1a, coll2)).to.be.false;
882909

883910
const coll1c = collection(firestore, 'a').withConverter({
884-
toFirestore: data => data as firestore.DocumentData,
911+
toFirestore: (data: firestore.DocumentData) =>
912+
data as firestore.DocumentData,
885913
fromFirestore: snap => snap.data()
886914
});
887915
expect(refEqual(coll1a, coll1c)).to.be.false;
@@ -900,7 +928,8 @@ describe('equality', () => {
900928
expect(refEqual(doc1a, doc2)).to.be.false;
901929

902930
const doc1c = collection(firestore, 'a').withConverter({
903-
toFirestore: data => data as firestore.DocumentData,
931+
toFirestore: (data: firestore.DocumentData) =>
932+
data as firestore.DocumentData,
904933
fromFirestore: snap => snap.data()
905934
});
906935
expect(refEqual(doc1a, doc1c)).to.be.false;
@@ -968,23 +997,6 @@ describe('equality', () => {
968997
});
969998

970999
describe('withConverter() support', () => {
971-
class Post {
972-
constructor(readonly title: string, readonly author: string) {}
973-
byline(): string {
974-
return this.title + ', by ' + this.author;
975-
}
976-
}
977-
978-
const postConverter = {
979-
toFirestore(post: Post): firestore.DocumentData {
980-
return { title: post.title, author: post.author };
981-
},
982-
fromFirestore(snapshot: firestore.QueryDocumentSnapshot): Post {
983-
const data = snapshot.data();
984-
return new Post(data.title, data.author);
985-
}
986-
};
987-
9881000
it('for DocumentReference.withConverter()', () => {
9891001
return withTestDoc(async docRef => {
9901002
docRef = docRef.withConverter(postConverter);
@@ -1045,4 +1057,19 @@ describe('withConverter() support', () => {
10451057
expect(refEqual(docRef, docRef2)).to.be.false;
10461058
});
10471059
});
1060+
1061+
it('requires the correct converter for Partial usage', async () => {
1062+
return withTestDb(async db => {
1063+
const coll = collection(db, 'posts');
1064+
const ref = doc(coll, 'post').withConverter(postConverter);
1065+
const batch = writeBatch(db);
1066+
expect(() =>
1067+
batch.set(ref, { title: 'olive' }, { merge: true })
1068+
).to.throw(
1069+
'Function WriteBatch.set() called with invalid data ' +
1070+
'(via `toFirestore()`). Unsupported field value: undefined ' +
1071+
'(found in field author in document posts/post)'
1072+
);
1073+
});
1074+
});
10481075
});

packages/firestore/src/api/user_data_reader.ts

+4
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ const RESERVED_FIELD_REGEX = /^__.*__$/;
5858
*/
5959
export interface UntypedFirestoreDataConverter<T> {
6060
toFirestore(modelObject: T): firestore.DocumentData;
61+
toFirestore(
62+
modelObject: Partial<T>,
63+
options: firestore.SetOptions
64+
): firestore.DocumentData;
6165
fromFirestore(snapshot: unknown, options?: unknown): T;
6266
}
6367

0 commit comments

Comments
 (0)