Skip to content

Commit 5bc6afb

Browse files
author
Brian Chen
authored
Firestore: QoL improvements for converters (#5268)
1 parent f35b4e3 commit 5bc6afb

20 files changed

+1367
-606
lines changed

.changeset/dirty-pandas-pay.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'firebase': major
3+
'@firebase/firestore': major
4+
---
5+
6+
This change contains multiple quality-of-life improvements when using the `FirestoreDataConverter` in `@firebase/firestore/lite` and `@firebase/firestore`:
7+
- Support for passing in `FieldValue` property values when using a converter (via `WithFieldValue<T>` and `PartialWithFieldValue<T>`).
8+
- Support for omitting properties in nested fields when performing a set operation with `{merge: true}` with a converter (via `PartialWithFieldValue<T>`).
9+
- Support for typed update operations when using a converter (via the newly typed `UpdateData`). Improperly typed fields in
10+
update operations on typed document references will no longer compile.

common/api-review/firestore-lite.api.md

+41-15
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ import { FirebaseApp } from '@firebase/app-exp';
99
import { LogLevelString as LogLevel } from '@firebase/logger';
1010

1111
// @public
12-
export function addDoc<T>(reference: CollectionReference<T>, data: T): Promise<DocumentReference<T>>;
12+
export function addDoc<T>(reference: CollectionReference<T>, data: WithFieldValue<T>): Promise<DocumentReference<T>>;
13+
14+
// @public
15+
export type AddPrefixToKeys<Prefix extends string, T extends Record<string, unknown>> = {
16+
[K in keyof T & string as `${Prefix}.${K}`]+?: T[K];
17+
};
1318

1419
// @public
1520
export function arrayRemove(...elements: unknown[]): FieldValue;
@@ -132,8 +137,8 @@ export class Firestore {
132137
// @public
133138
export interface FirestoreDataConverter<T> {
134139
fromFirestore(snapshot: QueryDocumentSnapshot<DocumentData>): T;
135-
toFirestore(modelObject: T): DocumentData;
136-
toFirestore(modelObject: Partial<T>, options: SetOptions): DocumentData;
140+
toFirestore(modelObject: WithFieldValue<T>): DocumentData;
141+
toFirestore(modelObject: PartialWithFieldValue<T>, options: SetOptions): DocumentData;
137142
}
138143

139144
// @public
@@ -182,12 +187,25 @@ export function limitToLast(limit: number): QueryConstraint;
182187

183188
export { LogLevel }
184189

190+
// @public
191+
export type NestedUpdateFields<T extends Record<string, unknown>> = UnionToIntersection<{
192+
[K in keyof T & string]: T[K] extends Record<string, unknown> ? AddPrefixToKeys<K, UpdateData<T[K]>> : never;
193+
}[keyof T & string]>;
194+
185195
// @public
186196
export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDirection): QueryConstraint;
187197

188198
// @public
189199
export type OrderByDirection = 'desc' | 'asc';
190200

201+
// @public
202+
export type PartialWithFieldValue<T> = T extends Primitive ? T : T extends {} ? {
203+
[K in keyof T]?: PartialWithFieldValue<T[K]> | FieldValue;
204+
} : Partial<T>;
205+
206+
// @public
207+
export type Primitive = string | number | boolean | undefined | null;
208+
191209
// @public
192210
export class Query<T = DocumentData> {
193211
protected constructor();
@@ -237,10 +255,10 @@ export function runTransaction<T>(firestore: Firestore, updateFunction: (transac
237255
export function serverTimestamp(): FieldValue;
238256

239257
// @public
240-
export function setDoc<T>(reference: DocumentReference<T>, data: T): Promise<void>;
258+
export function setDoc<T>(reference: DocumentReference<T>, data: WithFieldValue<T>): Promise<void>;
241259

242260
// @public
243-
export function setDoc<T>(reference: DocumentReference<T>, data: Partial<T>, options: SetOptions): Promise<void>;
261+
export function setDoc<T>(reference: DocumentReference<T>, data: PartialWithFieldValue<T>, options: SetOptions): Promise<void>;
244262

245263
// @public
246264
export function setLogLevel(logLevel: LogLevel): void;
@@ -302,19 +320,22 @@ export class Timestamp {
302320
export class Transaction {
303321
delete(documentRef: DocumentReference<unknown>): this;
304322
get<T>(documentRef: DocumentReference<T>): Promise<DocumentSnapshot<T>>;
305-
set<T>(documentRef: DocumentReference<T>, data: T): this;
306-
set<T>(documentRef: DocumentReference<T>, data: Partial<T>, options: SetOptions): this;
307-
update(documentRef: DocumentReference<unknown>, data: UpdateData): this;
323+
set<T>(documentRef: DocumentReference<T>, data: WithFieldValue<T>): this;
324+
set<T>(documentRef: DocumentReference<T>, data: PartialWithFieldValue<T>, options: SetOptions): this;
325+
update<T>(documentRef: DocumentReference<T>, data: UpdateData<T>): this;
308326
update(documentRef: DocumentReference<unknown>, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): this;
309327
}
310328

311329
// @public
312-
export interface UpdateData {
313-
[fieldPath: string]: any;
314-
}
330+
export type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
315331

316332
// @public
317-
export function updateDoc(reference: DocumentReference<unknown>, data: UpdateData): Promise<void>;
333+
export type UpdateData<T> = T extends Primitive ? T : T extends Map<infer K, infer V> ? Map<UpdateData<K>, UpdateData<V>> : T extends {} ? {
334+
[K in keyof T]?: UpdateData<T[K]> | FieldValue;
335+
} & NestedUpdateFields<T> : Partial<T>;
336+
337+
// @public
338+
export function updateDoc<T>(reference: DocumentReference<T>, data: UpdateData<T>): Promise<void>;
318339

319340
// @public
320341
export function updateDoc(reference: DocumentReference<unknown>, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise<void>;
@@ -325,13 +346,18 @@ export function where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value
325346
// @public
326347
export type WhereFilterOp = '<' | '<=' | '==' | '!=' | '>=' | '>' | 'array-contains' | 'in' | 'array-contains-any' | 'not-in';
327348

349+
// @public
350+
export type WithFieldValue<T> = T extends Primitive ? T : T extends {} ? {
351+
[K in keyof T]: WithFieldValue<T[K]> | FieldValue;
352+
} : Partial<T>;
353+
328354
// @public
329355
export class WriteBatch {
330356
commit(): Promise<void>;
331357
delete(documentRef: DocumentReference<unknown>): WriteBatch;
332-
set<T>(documentRef: DocumentReference<T>, data: T): WriteBatch;
333-
set<T>(documentRef: DocumentReference<T>, data: Partial<T>, options: SetOptions): WriteBatch;
334-
update(documentRef: DocumentReference<unknown>, data: UpdateData): WriteBatch;
358+
set<T>(documentRef: DocumentReference<T>, data: WithFieldValue<T>): WriteBatch;
359+
set<T>(documentRef: DocumentReference<T>, data: PartialWithFieldValue<T>, options: SetOptions): WriteBatch;
360+
update<T>(documentRef: DocumentReference<T>, data: UpdateData<T>): WriteBatch;
335361
update(documentRef: DocumentReference<unknown>, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): WriteBatch;
336362
}
337363

0 commit comments

Comments
 (0)