@@ -151,40 +151,163 @@ declare namespace FirebaseFirestore {
151
151
* storing and retrieving objects from Firestore.
152
152
*
153
153
* @example
154
- * class Post {
155
- * constructor(readonly title: string, readonly author: string) {}
156
154
*
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
+ * }
160
195
* }
161
196
*
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.
162
200
* interface PostDbModel {
163
- * title: string;
164
- * author: string;
201
+ * ttl: string;
202
+ * aut: { firstName: string; lastName: string };
203
+ * lut: Timestamp;
165
204
* }
166
205
*
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
+ * });
178
297
*
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
+ * }
188
311
* }
189
312
*/
190
313
export interface FirestoreDataConverter <
0 commit comments