@@ -101,40 +101,115 @@ toFirestore(modelObject: PartialWithFieldValue<AppModelType>, options: SetOption
101
101
102
102
` ` ` typescript
103
103
class Post {
104
- constructor(readonly title: string, readonly author: string) {}
104
+ constructor(
105
+ readonly title: string,
106
+ readonly author: string,
107
+ readonly lastUpdatedMillis: number
108
+ ) {}
105
109
106
110
toString(): string {
107
- return this.title + ', by ' + this.author;
111
+ return ` $ { this .title } by $ { this .author } ` ;
108
112
}
109
113
}
110
114
111
115
interface PostDbModel {
112
- title: string;
113
- author: string;
116
+ ttl: string;
117
+ aut: { firstName: string; lastName: string };
118
+ lut: Timestamp;
114
119
}
115
120
116
121
const postConverter = {
117
- toFirestore(post: WithFieldValue<Post>): PostDbModel {
118
- return {title: post.title, author: post.author};
122
+ toFirestore(post: WithFieldValue<Post>) {
123
+ return {
124
+ ttl: post.title,
125
+ aut: this._autFromAuthor(post.author),
126
+ lut: this._lutFromLastUpdatedMillis(post.lastUpdatedMillis)
127
+ };
119
128
},
120
- fromFirestore(
121
- snapshot: QueryDocumentSnapshot,
122
- options: SnapshotOptions
123
- ): Post {
129
+
130
+ fromFirestore(snapshot: QueryDocumentSnapshot, options: SnapshotOptions) {
124
131
const data = snapshot.data(options) as PostDbModel;
125
- return new Post(data.title, data.author);
132
+ const author = ` $ {data .aut .firstName } $ {data .aut .lastName }` ;
133
+ return new Post(data.ttl, author, data.lut.toMillis());
134
+ },
135
+
136
+ _autFromAuthor(
137
+ author: string | FieldValue
138
+ ): { firstName: string; lastName: string } | FieldValue {
139
+ if (typeof author !== 'string') {
140
+ // ` author ` is a FieldValue, so just return it.
141
+ return author;
142
+ }
143
+ const [firstName, lastName] = author.split(' ');
144
+ return { firstName, lastName };
145
+ },
146
+
147
+ _lutFromLastUpdatedMillis(
148
+ lastUpdatedMillis: number | FieldValue
149
+ ): Timestamp | FieldValue {
150
+ if (typeof lastUpdatedMillis !== 'number') {
151
+ // ` lastUpdatedMillis ` must be a FieldValue, so just return it.
152
+ return lastUpdatedMillis;
153
+ }
154
+ return Timestamp.fromMillis(lastUpdatedMillis);
126
155
}
127
156
};
128
157
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
158
+ async function demo(db: Firestore): Promise<void> {
159
+ // Create a ` DocumentReference ` with a ` FirestoreDataConverter ` .
160
+ const documentRef = doc(db, 'posts/post123').withConverter(postConverter);
161
+
162
+ // The ` data ` argument specified to ` setDoc ()` is type checked by the
163
+ // TypeScript compiler to be compatible with ` Post ` . Since the ` data `
164
+ // argument is typed as ` WithFieldValue <Post >` rather than just ` Post ` ,
165
+ // this allows properties of the ` data ` argument to also be special
166
+ // Firestore values that perform server-side mutations, such as
167
+ // ` arrayRemove ()` , ` deleteField ()` , and ` serverTimestamp ()` .
168
+ await setDoc(documentRef, {
169
+ title: 'My Life',
170
+ author: 'Foo Bar',
171
+ lastUpdatedMillis: serverTimestamp()
172
+ });
173
+
174
+ // The TypeScript compiler will fail to compile if the ` data ` argument to
175
+ // ` setDoc ()` is _not_ be compatible with ` WithFieldValue <Post >` . This
176
+ // type checking prevents the caller from specifying objects with incorrect
177
+ // properties or property values.
178
+ // @ts-expect-error "Argument of type { ttl: String; } is not assignable to
179
+ // parameter of type WithFieldValue<Post>"
180
+ await setDoc(documentRef, { ttl: 'The Title' });
181
+
182
+ // When retrieving a document with ` getDoc ()` the ` DocumentSnapshot `
183
+ // object's ` data ()` method returns a ` Post ` , rather than a generic object,
184
+ // which is returned if the ` DocumentReference ` did _not_ have a
185
+ // ` FirestoreDataConverter ` attached to it.
186
+ const postSnap: DocumentSnapshot<Post> = await getDoc(documentRef);
187
+ const post: Post | undefined = postSnap.data();
188
+ if (post) {
189
+ console.log( ` Post $ {documentRef .path } has title = $ {post .title }` );
190
+ }
191
+
192
+ // The ` data ` argument specified to ` updateDoc ()` is type checked by the
193
+ // TypeScript compiler to be compatible with ` PostDbModel ` . Note that
194
+ // unlike ` setDoc ()` , whose ` data ` argument must be compatible with ` Post ` ,
195
+ // the ` data ` argument to ` updateDoc ()` must be compatible with
196
+ // ` PostDbModel ` . Similar to ` setDoc ()` , since the ` data ` argument is typed
197
+ // as ` WithFieldValue <PostDbModel >` rather than just ` PostDbModel ` , this
198
+ // allows properties of the ` data ` argument to also be those special
199
+ // Firestore values, like as ` arrayRemove ()` , ` deleteField ()` , and
200
+ // ` serverTimestamp ()` .
201
+ await updateDoc(documentRef, {
202
+ 'aut.firstName': 'NewFirstName',
203
+ lut: serverTimestamp()
204
+ });
205
+
206
+ // The TypeScript compiler will fail to compile if the ` data ` argument to
207
+ // ` updateDoc ()` is _not_ be compatible with ` WithFieldValue <PostDbModel >` .
208
+ // This type checking prevents the caller from specifying objects with
209
+ // incorrect properties or property values.
210
+ // @ts-expect-error "Argument of type { title: String; } is not assignable
211
+ // to parameter of type WithFieldValue<PostDbModel>"
212
+ await updateDoc(documentRef, { title: 'New Title' });
138
213
}
139
214
140
215
` ` `
0 commit comments