Skip to content

Commit 43138fa

Browse files
Brian Chenschmidt-sebastian
authored andcommitted
Firestore: QoL improvements for converters (#5268)
1 parent 2c15b3f commit 43138fa

File tree

20 files changed

+1258
-595
lines changed

20 files changed

+1258
-595
lines changed

.changeset/dirty-pandas-pay.md

Lines changed: 10 additions & 0 deletions
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

Lines changed: 41 additions & 15 deletions
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;
@@ -134,8 +139,8 @@ export class Firestore {
134139
// @public
135140
export interface FirestoreDataConverter<T> {
136141
fromFirestore(snapshot: QueryDocumentSnapshot<DocumentData>): T;
137-
toFirestore(modelObject: T): DocumentData;
138-
toFirestore(modelObject: Partial<T>, options: SetOptions): DocumentData;
142+
toFirestore(modelObject: WithFieldValue<T>): DocumentData;
143+
toFirestore(modelObject: PartialWithFieldValue<T>, options: SetOptions): DocumentData;
139144
}
140145

141146
// @public
@@ -184,12 +189,25 @@ export function limitToLast(limit: number): QueryConstraint;
184189

185190
export { LogLevel }
186191

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

190200
// @public
191201
export type OrderByDirection = 'desc' | 'asc';
192202

203+
// @public
204+
export type PartialWithFieldValue<T> = T extends Primitive ? T : T extends {} ? {
205+
[K in keyof T]?: PartialWithFieldValue<T[K]> | FieldValue;
206+
} : Partial<T>;
207+
208+
// @public
209+
export type Primitive = string | number | boolean | undefined | null;
210+
193211
// @public
194212
export class Query<T = DocumentData> {
195213
protected constructor();
@@ -239,10 +257,10 @@ export function runTransaction<T>(firestore: Firestore, updateFunction: (transac
239257
export function serverTimestamp(): FieldValue;
240258

241259
// @public
242-
export function setDoc<T>(reference: DocumentReference<T>, data: T): Promise<void>;
260+
export function setDoc<T>(reference: DocumentReference<T>, data: WithFieldValue<T>): Promise<void>;
243261

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

247265
// @public
248266
export function setLogLevel(logLevel: LogLevel): void;
@@ -304,19 +322,22 @@ export class Timestamp {
304322
export class Transaction {
305323
delete(documentRef: DocumentReference<unknown>): this;
306324
get<T>(documentRef: DocumentReference<T>): Promise<DocumentSnapshot<T>>;
307-
set<T>(documentRef: DocumentReference<T>, data: T): this;
308-
set<T>(documentRef: DocumentReference<T>, data: Partial<T>, options: SetOptions): this;
309-
update(documentRef: DocumentReference<unknown>, data: UpdateData): this;
325+
set<T>(documentRef: DocumentReference<T>, data: WithFieldValue<T>): this;
326+
set<T>(documentRef: DocumentReference<T>, data: PartialWithFieldValue<T>, options: SetOptions): this;
327+
update<T>(documentRef: DocumentReference<T>, data: UpdateData<T>): this;
310328
update(documentRef: DocumentReference<unknown>, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): this;
311329
}
312330

313331
// @public
314-
export interface UpdateData {
315-
[fieldPath: string]: any;
316-
}
332+
export type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
317333

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

321342
// @public
322343
export function updateDoc(reference: DocumentReference<unknown>, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise<void>;
@@ -327,13 +348,18 @@ export function where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value
327348
// @public
328349
export type WhereFilterOp = '<' | '<=' | '==' | '!=' | '>=' | '>' | 'array-contains' | 'in' | 'array-contains-any' | 'not-in';
329350

351+
// @public
352+
export type WithFieldValue<T> = T extends Primitive ? T : T extends {} ? {
353+
[K in keyof T]: WithFieldValue<T[K]> | FieldValue;
354+
} : Partial<T>;
355+
330356
// @public
331357
export class WriteBatch {
332358
commit(): Promise<void>;
333359
delete(documentRef: DocumentReference<unknown>): WriteBatch;
334-
set<T>(documentRef: DocumentReference<T>, data: T): WriteBatch;
335-
set<T>(documentRef: DocumentReference<T>, data: Partial<T>, options: SetOptions): WriteBatch;
336-
update(documentRef: DocumentReference<unknown>, data: UpdateData): WriteBatch;
360+
set<T>(documentRef: DocumentReference<T>, data: WithFieldValue<T>): WriteBatch;
361+
set<T>(documentRef: DocumentReference<T>, data: PartialWithFieldValue<T>, options: SetOptions): WriteBatch;
362+
update<T>(documentRef: DocumentReference<T>, data: UpdateData<T>): WriteBatch;
337363
update(documentRef: DocumentReference<unknown>, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): WriteBatch;
338364
}
339365

0 commit comments

Comments
 (0)