Skip to content

Commit 44fee1b

Browse files
committed
Re-write the sample code for FirestoreDataConverter.
The old sample had blatant syntax errors and was lacking in its demonstration of the power of type converters. These changes are ported from firebase/firebase-js-sdk#7673
1 parent 7e91656 commit 44fee1b

File tree

1 file changed

+150
-27
lines changed

1 file changed

+150
-27
lines changed

types/firestore.d.ts

+150-27
Original file line numberDiff line numberDiff line change
@@ -151,40 +151,163 @@ declare namespace FirebaseFirestore {
151151
* storing and retrieving objects from Firestore.
152152
*
153153
* @example
154-
* class Post {
155-
* constructor(readonly title: string, readonly author: string) {}
156154
*
157-
* toString(): string {
158-
* return this.title + ', by ' + this.author;
159-
* }
155+
* Simple Example
156+
*
157+
* const numberConverter = {
158+
* toFirestore(value: WithFieldValue<number>) {
159+
* return { value };
160+
* },
161+
* fromFirestore(snapshot: QueryDocumentSnapshot) {
162+
* return snapshot.data().value as number;
163+
* }
164+
* };
165+
*
166+
* async function simpleDemo(db: Firestore): Promise<void> {
167+
* const documentRef = db.doc('values/value123').withConverter(numberConverter);
168+
*
169+
* // converters are used with `setDoc`, `addDoc`, and `getDoc`
170+
* await documentRef.set(42);
171+
* const snapshot1 = await documentRef.get();
172+
* assertEqual(snapshot1.data(), 42);
173+
*
174+
* // converters are not used when writing data with `updateDoc`
175+
* await documentRef.update({ value: 999 });
176+
* const snapshot2 = await documentRef.get();
177+
* assertEqual(snapshot2.data(), 999);
178+
* }
179+
*
180+
* Advanced Example
181+
*
182+
* // The Post class is a model that is used by our application.
183+
* // This class may have properties and methods that are specific
184+
* // to our application execution, which do not need to be persisted
185+
* // to Firestore.
186+
* class Post {
187+
* constructor(
188+
* readonly title: string,
189+
* readonly author: string,
190+
* readonly lastUpdatedMillis: number
191+
* ) {}
192+
* toString(): string {
193+
* return `${this.title} by ${this.author}`;
194+
* }
160195
* }
161196
*
197+
* // The PostDbModel represents how we want our posts to be stored
198+
* // in Firestore. This DbModel has different properties (`ttl`,
199+
* // `aut`, and `lut`) from the Post class we use in our application.
162200
* interface PostDbModel {
163-
* title: string;
164-
* author: string;
201+
* ttl: string;
202+
* aut: { firstName: string; lastName: string };
203+
* lut: Timestamp;
165204
* }
166205
*
167-
* const postConverter = {
168-
* toFirestore(post: Post): PostDbModel {
169-
* return {title: post.title, author: post.author};
170-
* },
171-
* fromFirestore(
172-
* snapshot: FirebaseFirestore.QueryDocumentSnapshot
173-
* ): Post {
174-
* const data = snapshot.data() as PostDbModel;
175-
* return new Post(data.title, data.author);
176-
* }
177-
* };
206+
* // The `PostConverter` implements `FirestoreDataConverter` and specifies
207+
* // how the Firestore SDK can convert `Post` objects to `PostDbModel`
208+
* // objects and vice versa.
209+
* class PostConverter implements FirestoreDataConverter<Post, PostDbModel> {
210+
* toFirestore(post: WithFieldValue<Post>): WithFieldValue<PostDbModel> {
211+
* return {
212+
* ttl: post.title,
213+
* aut: this._autFromAuthor(post.author),
214+
* lut: this._lutFromLastUpdatedMillis(post.lastUpdatedMillis)
215+
* };
216+
* }
217+
*
218+
* fromFirestore(snapshot: QueryDocumentSnapshot): Post {
219+
* const data = snapshot.data() as PostDbModel;
220+
* const author = `${data.aut.firstName} ${data.aut.lastName}`;
221+
* return new Post(data.ttl, author, data.lut.toMillis());
222+
* }
223+
*
224+
* _autFromAuthor(
225+
* author: string | FieldValue
226+
* ): { firstName: string; lastName: string } | FieldValue {
227+
* if (typeof author !== 'string') {
228+
* // `author` is a FieldValue, so just return it.
229+
* return author;
230+
* }
231+
* const [firstName, lastName] = author.split(' ');
232+
* return {firstName, lastName};
233+
* }
234+
*
235+
* _lutFromLastUpdatedMillis(
236+
* lastUpdatedMillis: number | FieldValue
237+
* ): Timestamp | FieldValue {
238+
* if (typeof lastUpdatedMillis !== 'number') {
239+
* // `lastUpdatedMillis` must be a FieldValue, so just return it.
240+
* return lastUpdatedMillis;
241+
* }
242+
* return Timestamp.fromMillis(lastUpdatedMillis);
243+
* }
244+
* }
245+
*
246+
* async function advancedDemo(db: Firestore): Promise<void> {
247+
* // Create a `DocumentReference` with a `FirestoreDataConverter`.
248+
* const documentRef = db.doc('posts/post123').withConverter(new PostConverter());
249+
*
250+
* // The `data` argument specified to `DocumentReference.set()` is type
251+
* // checked by the TypeScript compiler to be compatible with `Post`. Since
252+
* // the `data` argument is typed as `WithFieldValue<Post>` rather than just
253+
* // `Post`, this allows properties of the `data` argument to also be special
254+
* // Firestore values that perform server-side mutations, such as
255+
* // `FieldValue.arrayRemove()`, `FieldValue.delete()`, and
256+
* // `FieldValue.serverTimestamp()`.
257+
* await documentRef.set({
258+
* title: 'My Life',
259+
* author: 'Foo Bar',
260+
* lastUpdatedMillis: FieldValue.serverTimestamp()
261+
* });
262+
*
263+
* // The TypeScript compiler will fail to compile if the `data` argument
264+
* // to `DocumentReference.set()` is _not_ compatible with
265+
* // `WithFieldValue<Post>`. This type checking prevents the caller from
266+
* // specifying objects with incorrect properties or property values.
267+
* // @ts-expect-error "Argument of type { ttl: string; } is not assignable
268+
* // to parameter of type WithFieldValue<Post>"
269+
* await documentRef.set(documentRef, { ttl: 'The Title' });
270+
*
271+
* // When retrieving a document with `DocumentReference.get()` the
272+
* // `DocumentSnapshot` object's `data()` method returns a `Post`, rather
273+
* // than a generic object, which would have been returned if the
274+
* // `DocumentReference` did _not_ have a `FirestoreDataConverter`
275+
* // attached to it.
276+
* const snapshot1: DocumentSnapshot<Post> = await documentRef.get();
277+
* const post1: Post = snapshot1.data()!;
278+
* if (post1) {
279+
* assertEqual(post1.title, 'My Life');
280+
* assertEqual(post1.author, 'Foo Bar');
281+
* }
282+
*
283+
* // The `data` argument specified to `DocumentReference.update()` is type
284+
* // checked by the TypeScript compiler to be compatible with
285+
* // `PostDbModel`. Note that unlike `DocumentReference.set()`, whose
286+
* // `data` argument must be compatible with `Post`, the `data` argument
287+
* // to `update()` must be compatible with `PostDbModel`. Similar to
288+
* // `set()`, since the `data` argument is typed as
289+
* // `WithFieldValue<PostDbModel>` rather than just `PostDbModel`, this
290+
* // allows properties of the `data` argument to also be those special
291+
* // Firestore values, like `FieldValue.arrayRemove()`,
292+
* // `FieldValue.delete()`, and `FieldValue.serverTimestamp()`.
293+
* await documentRef.update({
294+
* 'aut.firstName': 'NewFirstName',
295+
* lut: FieldValue.serverTimestamp()
296+
* });
178297
*
179-
* const postSnap = await Firestore()
180-
* .collection('posts')
181-
* .withConverter(postConverter)
182-
* .doc().get();
183-
* const post = postSnap.data();
184-
* if (post !== undefined) {
185-
* post.title; // string
186-
* post.toString(); // Should be defined
187-
* post.someNonExistentProperty; // TS error
298+
* // The TypeScript compiler will fail to compile if the `data` argument
299+
* // to `DocumentReference.update()` is _not_ compatible with
300+
* // `WithFieldValue<PostDbModel>`. This type checking prevents the caller
301+
* // from specifying objects with incorrect properties or property values.
302+
* // @ts-expect-error "Argument of type { title: string; } is not
303+
* // assignable to parameter of type WithFieldValue<PostDbModel>"
304+
* await documentRef.update({ title: 'New Title' });
305+
* const snapshot2: DocumentSnapshot<Post> = await documentRef.get();
306+
* const post2: Post = snapshot2.data()!;
307+
* if (post2) {
308+
* assertEqual(post2.title, 'My Life');
309+
* assertEqual(post2.author, 'NewFirstName Bar');
310+
* }
188311
* }
189312
*/
190313
export interface FirestoreDataConverter<

0 commit comments

Comments
 (0)