@@ -98,43 +98,162 @@ toFirestore(modelObject: PartialWithFieldValue<AppModelType>, options: SetOption
98
98
99
99
### Example
100
100
101
+ Simple Example
101
102
102
103
` ` ` typescript
103
- class Post {
104
- constructor(readonly title: string, readonly author: string) {}
104
+ const numberConverter = {
105
+ toFirestore(value: WithFieldValue<number>) {
106
+ return { value };
107
+ },
108
+ fromFirestore(snapshot: QueryDocumentSnapshot, options: SnapshotOptions) {
109
+ return snapshot.data(options).value as number;
110
+ }
111
+ };
112
+
113
+ async function simpleDemo(db: Firestore): Promise<void> {
114
+ const documentRef = doc(db, 'values/value123').withConverter(numberConverter);
115
+
116
+ // converters are used with ` setDoc ` , ` addDoc ` , and ` getDoc `
117
+ await setDoc(documentRef, 42);
118
+ const snapshot1 = await getDoc(documentRef);
119
+ assertEqual(snapshot1.data(), 42);
120
+
121
+ // converters are not used when writing data with ` updateDoc `
122
+ await updateDoc(documentRef, { value: 999 });
123
+ const snapshot2 = await getDoc(documentRef);
124
+ assertEqual(snapshot2.data(), 999);
125
+ }
105
126
106
- toString(): string {
107
- return this.title + ', by ' + this.author;
108
- }
127
+ ` ` `
128
+ Advanced Example
129
+
130
+ ` ` ` typescript
131
+ // The Post class is a model that is used by our application.
132
+ // This class may have properties and methods that are specific
133
+ // to our application execution, which do not need to be persisted
134
+ // to Firestore.
135
+ class Post {
136
+ constructor(
137
+ readonly title: string,
138
+ readonly author: string,
139
+ readonly lastUpdatedMillis: number
140
+ ) {}
141
+ toString(): string {
142
+ return ` $ {this .title } by $ {this .author }` ;
143
+ }
109
144
}
110
145
146
+ // The PostDbModel represents how we want our posts to be stored
147
+ // in Firestore. This DbModel has different properties ( ` ttl ` ,
148
+ // ` aut ` , and ` lut ` ) from the Post class we use in our application.
111
149
interface PostDbModel {
112
- title: string;
113
- author: string;
150
+ ttl: string;
151
+ aut: { firstName: string; lastName: string };
152
+ lut: Timestamp;
114
153
}
115
154
116
- const postConverter = {
117
- toFirestore(post: WithFieldValue<Post>): PostDbModel {
118
- return {title: post.title, author: post.author};
119
- },
120
- fromFirestore(
121
- snapshot: QueryDocumentSnapshot,
122
- options: SnapshotOptions
123
- ): Post {
124
- const data = snapshot.data(options) as PostDbModel;
125
- return new Post(data.title, data.author);
126
- }
127
- };
155
+ // The ` PostConverter ` implements ` FirestoreDataConverter ` and specifies
156
+ // how the Firestore SDK can convert ` Post ` objects to ` PostDbModel `
157
+ // objects and vice versa.
158
+ class PostConverter implements FirestoreDataConverter<Post, PostDbModel> {
159
+ toFirestore(post: WithFieldValue<Post>): WithFieldValue<PostDbModel> {
160
+ return {
161
+ ttl: post.title,
162
+ aut: this._autFromAuthor(post.author),
163
+ lut: this._lutFromLastUpdatedMillis(post.lastUpdatedMillis)
164
+ };
165
+ }
166
+
167
+ fromFirestore(snapshot: QueryDocumentSnapshot, options: SnapshotOptions): Post {
168
+ const data = snapshot.data(options) as PostDbModel;
169
+ const author = ` $ {data .aut .firstName } $ {data .aut .lastName }` ;
170
+ return new Post(data.ttl, author, data.lut.toMillis());
171
+ }
172
+
173
+ _autFromAuthor(
174
+ author: string | FieldValue
175
+ ): { firstName: string; lastName: string } | FieldValue {
176
+ if (typeof author !== 'string') {
177
+ // ` author ` is a FieldValue, so just return it.
178
+ return author;
179
+ }
180
+ const [firstName, lastName] = author.split(' ');
181
+ return {firstName, lastName};
182
+ }
183
+
184
+ _lutFromLastUpdatedMillis(
185
+ lastUpdatedMillis: number | FieldValue
186
+ ): Timestamp | FieldValue {
187
+ if (typeof lastUpdatedMillis !== 'number') {
188
+ // ` lastUpdatedMillis ` must be a FieldValue, so just return it.
189
+ return lastUpdatedMillis;
190
+ }
191
+ return Timestamp.fromMillis(lastUpdatedMillis);
192
+ }
193
+ }
128
194
129
- const postSnap = await firebase.firestore()
130
- .collection('posts')
131
- .withConverter(postConverter)
132
- .doc().get();
133
- const post = postSnap.data();
134
- if (post !== undefined) {
135
- post.title; // string
136
- post.toString(); // Should be defined
137
- post.someNonExistentProperty; // TS error
195
+ async function advancedDemo(db: Firestore): Promise<void> {
196
+ // Create a ` DocumentReference ` with a ` FirestoreDataConverter ` .
197
+ const documentRef = doc(db, 'posts/post123').withConverter(new PostConverter());
198
+
199
+ // The ` data ` argument specified to ` setDoc ()` is type checked by the
200
+ // TypeScript compiler to be compatible with ` Post ` . Since the ` data `
201
+ // argument is typed as ` WithFieldValue <Post >` rather than just ` Post ` ,
202
+ // this allows properties of the ` data ` argument to also be special
203
+ // Firestore values that perform server-side mutations, such as
204
+ // ` arrayRemove ()` , ` deleteField ()` , and ` serverTimestamp ()` .
205
+ await setDoc(documentRef, {
206
+ title: 'My Life',
207
+ author: 'Foo Bar',
208
+ lastUpdatedMillis: serverTimestamp()
209
+ });
210
+
211
+ // The TypeScript compiler will fail to compile if the ` data ` argument to
212
+ // ` setDoc ()` is _not_ compatible with ` WithFieldValue <Post >` . This
213
+ // type checking prevents the caller from specifying objects with incorrect
214
+ // properties or property values.
215
+ // @ts-expect-error "Argument of type { ttl: string; } is not assignable
216
+ // to parameter of type WithFieldValue<Post>"
217
+ await setDoc(documentRef, { ttl: 'The Title' });
218
+
219
+ // When retrieving a document with ` getDoc ()` the ` DocumentSnapshot `
220
+ // object's ` data ()` method returns a ` Post ` , rather than a generic object,
221
+ // which would have been returned if the ` DocumentReference ` did _not_ have a
222
+ // ` FirestoreDataConverter ` attached to it.
223
+ const snapshot1: DocumentSnapshot<Post> = await getDoc(documentRef);
224
+ const post1: Post = snapshot1.data()!;
225
+ if (post1) {
226
+ assertEqual(post1.title, 'My Life');
227
+ assertEqual(post1.author, 'Foo Bar');
228
+ }
229
+
230
+ // The ` data ` argument specified to ` updateDoc ()` is type checked by the
231
+ // TypeScript compiler to be compatible with ` PostDbModel ` . Note that
232
+ // unlike ` setDoc ()` , whose ` data ` argument must be compatible with ` Post ` ,
233
+ // the ` data ` argument to ` updateDoc ()` must be compatible with
234
+ // ` PostDbModel ` . Similar to ` setDoc ()` , since the ` data ` argument is typed
235
+ // as ` WithFieldValue <PostDbModel >` rather than just ` PostDbModel ` , this
236
+ // allows properties of the ` data ` argument to also be those special
237
+ // Firestore values, like ` arrayRemove ()` , ` deleteField ()` , and
238
+ // ` serverTimestamp ()` .
239
+ await updateDoc(documentRef, {
240
+ 'aut.firstName': 'NewFirstName',
241
+ lut: serverTimestamp()
242
+ });
243
+
244
+ // The TypeScript compiler will fail to compile if the ` data ` argument to
245
+ // ` updateDoc ()` is _not_ compatible with ` WithFieldValue <PostDbModel >` .
246
+ // This type checking prevents the caller from specifying objects with
247
+ // incorrect properties or property values.
248
+ // @ts-expect-error "Argument of type { title: string; } is not assignable
249
+ // to parameter of type WithFieldValue<PostDbModel>"
250
+ await updateDoc(documentRef, { title: 'New Title' });
251
+ const snapshot2: DocumentSnapshot<Post> = await getDoc(documentRef);
252
+ const post2: Post = snapshot2.data()!;
253
+ if (post2) {
254
+ assertEqual(post2.title, 'My Life');
255
+ assertEqual(post2.author, 'NewFirstName Bar');
256
+ }
138
257
}
139
258
140
259
` ` `
0 commit comments