From f5a00fcd4cdeab32a61e5f340d8b8e78dde5da9d Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Tue, 10 Aug 2021 12:54:36 -0500 Subject: [PATCH 01/22] WIP: working NestedParial and WithFieldValue --- packages/firestore/src/api/database.ts | 19 ++++++++++++++----- packages/firestore/src/exp/reference_impl.ts | 12 +++++++----- packages/firestore/src/exp/snapshot.ts | 16 +++++++++++++--- packages/firestore/src/lite/reference.ts | 16 ++++++++++++++++ packages/firestore/src/lite/reference_impl.ts | 10 ++++++---- packages/firestore/src/lite/snapshot.ts | 11 ++++++++--- .../firestore/src/lite/user_data_reader.ts | 13 ++++++++++--- .../test/integration/api/database.test.ts | 3 ++- packages/firestore/test/lite/helpers.ts | 14 +++++++++++--- .../firestore/test/lite/integration.test.ts | 17 +++++++++++++---- 10 files changed, 100 insertions(+), 31 deletions(-) diff --git a/packages/firestore/src/api/database.ts b/packages/firestore/src/api/database.ts index c411534e117..b4444bd1665 100644 --- a/packages/firestore/src/api/database.ts +++ b/packages/firestore/src/api/database.ts @@ -127,6 +127,7 @@ import { NextFn, PartialObserver } from './observer'; +import { NestedPartialWithFieldValue, WithFieldValue } from '../lite/reference'; /** * A persistence provider for either memory-only or IndexedDB persistence. @@ -597,19 +598,19 @@ class FirestoreDataConverter ); } - toFirestore(modelObject: U): PublicDocumentData; + toFirestore(modelObject: WithFieldValue): PublicDocumentData; toFirestore( - modelObject: Partial, + modelObject: NestedPartialWithFieldValue, options: PublicSetOptions ): PublicDocumentData; toFirestore( - modelObject: U | Partial, + modelObject: WithFieldValue | NestedPartialWithFieldValue, options?: PublicSetOptions ): PublicDocumentData { if (!options) { return this._delegate.toFirestore(modelObject as U); } else { - return this._delegate.toFirestore(modelObject, options); + return this._delegate.toFirestore(modelObject as Partial, options); } } @@ -733,7 +734,15 @@ export class DocumentReference set(value: T | Partial, options?: PublicSetOptions): Promise { options = validateSetOptions('DocumentReference.set', options); try { - return setDoc(this._delegate, value, options); + if (options) { + return setDoc( + this._delegate, + value as NestedPartialWithFieldValue, + options + ); + } else { + return setDoc(this._delegate, value as WithFieldValue); + } } catch (e) { throw replaceFunctionName(e, 'setDoc()', 'DocumentReference.set()'); } diff --git a/packages/firestore/src/exp/reference_impl.ts b/packages/firestore/src/exp/reference_impl.ts index cf23286ceb7..8043aeb05f8 100644 --- a/packages/firestore/src/exp/reference_impl.ts +++ b/packages/firestore/src/exp/reference_impl.ts @@ -42,9 +42,11 @@ import { CollectionReference, doc, DocumentReference, + NestedPartialWithFieldValue, Query, SetOptions, - UpdateData + UpdateData, + WithFieldValue } from '../lite/reference'; import { applyFirestoreDataConverter } from '../lite/reference_impl'; import { @@ -243,7 +245,7 @@ export function getDocsFromServer( */ export function setDoc( reference: DocumentReference, - data: T + data: WithFieldValue ): Promise; /** * Writes to the document referred to by the specified `DocumentReference`. If @@ -258,12 +260,12 @@ export function setDoc( */ export function setDoc( reference: DocumentReference, - data: Partial, + data: NestedPartialWithFieldValue, options: SetOptions ): Promise; export function setDoc( reference: DocumentReference, - data: T, + data: WithFieldValue | NestedPartialWithFieldValue, options?: SetOptions ): Promise { reference = cast>(reference, DocumentReference); @@ -393,7 +395,7 @@ export function deleteDoc( */ export function addDoc( reference: CollectionReference, - data: T + data: WithFieldValue ): Promise> { const firestore = cast(reference.firestore, Firestore); diff --git a/packages/firestore/src/exp/snapshot.ts b/packages/firestore/src/exp/snapshot.ts index b452436dac3..b420201a7cb 100644 --- a/packages/firestore/src/exp/snapshot.ts +++ b/packages/firestore/src/exp/snapshot.ts @@ -18,7 +18,14 @@ import { newQueryComparator } from '../core/query'; import { ChangeType, ViewSnapshot } from '../core/view_snapshot'; import { FieldPath } from '../lite/field_path'; -import { DocumentData, Query, queryEqual, SetOptions } from '../lite/reference'; +import { + DocumentData, + NestedPartialWithFieldValue, + Query, + queryEqual, + SetOptions, + WithFieldValue +} from '../lite/reference'; import { DocumentSnapshot as LiteDocumentSnapshot, fieldPathFromArgument, @@ -84,7 +91,7 @@ export interface FirestoreDataConverter * Firestore database). To use `set()` with `merge` and `mergeFields`, * `toFirestore()` must be defined with `Partial`. */ - toFirestore(modelObject: T): DocumentData; + toFirestore(modelObject: WithFieldValue): DocumentData; /** * Called by the Firestore SDK to convert a custom model object of type `T` @@ -92,7 +99,10 @@ export interface FirestoreDataConverter * Firestore database). Used with {@link (setDoc:1)}, {@link (WriteBatch.set:1)} * and {@link (Transaction.set:1)} with `merge:true` or `mergeFields`. */ - toFirestore(modelObject: Partial, options: SetOptions): DocumentData; + toFirestore( + modelObject: NestedPartialWithFieldValue, + options: SetOptions + ): DocumentData; /** * Called by the Firestore SDK to convert Firestore data into an object of diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite/reference.ts index 68738982b72..08a845f22dc 100644 --- a/packages/firestore/src/lite/reference.ts +++ b/packages/firestore/src/lite/reference.ts @@ -36,6 +36,7 @@ import { AutoId } from '../util/misc'; import { Firestore } from './database'; import { FieldPath } from './field_path'; +import { FieldValue } from './field_value'; import { FirestoreDataConverter } from './snapshot'; /** @@ -48,6 +49,21 @@ export interface DocumentData { [field: string]: any; } +type Primitive = string | number | boolean | bigint | undefined | null; +// eslint-disable-next-line @typescript-eslint/ban-types +type Builtin = Primitive | Function; + +/** Like Partial but recursive */ +export type NestedPartialWithFieldValue = T extends Builtin + ? T + : T extends Map + ? Map, NestedPartialWithFieldValue> + : T extends {} + ? { [K in keyof T]?: NestedPartialWithFieldValue | FieldValue } + : Partial; + +export type WithFieldValue = { [P in keyof T]: T[P] | FieldValue }; + /** * Update data (for use with {@link @firebase/firestore/lite#(updateDoc:1)}) consists of field paths (e.g. * 'foo' or 'foo.baz') mapped to values. Fields that contain dots reference diff --git a/packages/firestore/src/lite/reference_impl.ts b/packages/firestore/src/lite/reference_impl.ts index fdbd0bc9774..dae29deecef 100644 --- a/packages/firestore/src/lite/reference_impl.ts +++ b/packages/firestore/src/lite/reference_impl.ts @@ -41,9 +41,11 @@ import { CollectionReference, doc, DocumentReference, + NestedPartialWithFieldValue, Query, SetOptions, - UpdateData + UpdateData, + WithFieldValue } from './reference'; import { DocumentSnapshot, @@ -197,7 +199,7 @@ export function getDocs(query: Query): Promise> { */ export function setDoc( reference: DocumentReference, - data: T + data: WithFieldValue ): Promise; /** * Writes to the document referred to by the specified `DocumentReference`. If @@ -217,12 +219,12 @@ export function setDoc( */ export function setDoc( reference: DocumentReference, - data: Partial, + data: NestedPartialWithFieldValue, options: SetOptions ): Promise; export function setDoc( reference: DocumentReference, - data: T, + data: WithFieldValue | NestedPartialWithFieldValue, options?: SetOptions ): Promise { reference = cast>(reference, DocumentReference); diff --git a/packages/firestore/src/lite/snapshot.ts b/packages/firestore/src/lite/snapshot.ts index b0a08ba8850..223370ba205 100644 --- a/packages/firestore/src/lite/snapshot.ts +++ b/packages/firestore/src/lite/snapshot.ts @@ -27,9 +27,11 @@ import { FieldPath } from './field_path'; import { DocumentData, DocumentReference, + NestedPartialWithFieldValue, Query, queryEqual, - SetOptions + SetOptions, + WithFieldValue } from './reference'; import { fieldPathFromDotSeparatedString, @@ -83,7 +85,7 @@ export interface FirestoreDataConverter { * Firestore database). Used with {@link @firebase/firestore/lite#(setDoc:1)}, {@link @firebase/firestore/lite#(WriteBatch.set:1)} * and {@link @firebase/firestore/lite#(Transaction.set:1)}. */ - toFirestore(modelObject: T): DocumentData; + toFirestore(modelObject: WithFieldValue): DocumentData; /** * Called by the Firestore SDK to convert a custom model object of type `T` @@ -91,7 +93,10 @@ export interface FirestoreDataConverter { * Firestore database). Used with {@link @firebase/firestore/lite#(setDoc:1)}, {@link @firebase/firestore/lite#(WriteBatch.set:1)} * and {@link @firebase/firestore/lite#(Transaction.set:1)} with `merge:true` or `mergeFields`. */ - toFirestore(modelObject: Partial, options: SetOptions): DocumentData; + toFirestore( + modelObject: NestedPartialWithFieldValue, + options: SetOptions + ): DocumentData; /** * Called by the Firestore SDK to convert Firestore data into an object of diff --git a/packages/firestore/src/lite/user_data_reader.ts b/packages/firestore/src/lite/user_data_reader.ts index 26fdf6c23c0..53950d7d125 100644 --- a/packages/firestore/src/lite/user_data_reader.ts +++ b/packages/firestore/src/lite/user_data_reader.ts @@ -63,7 +63,11 @@ import { Firestore } from './database'; import { FieldPath } from './field_path'; import { FieldValue } from './field_value'; import { GeoPoint } from './geo_point'; -import { DocumentReference } from './reference'; +import { + DocumentReference, + NestedPartialWithFieldValue, + WithFieldValue +} from './reference'; import { Timestamp } from './timestamp'; const RESERVED_FIELD_REGEX = /^__.*__$/; @@ -73,8 +77,11 @@ const RESERVED_FIELD_REGEX = /^__.*__$/; * lite, firestore-exp and classic SDK. */ export interface UntypedFirestoreDataConverter { - toFirestore(modelObject: T): DocumentData; - toFirestore(modelObject: Partial, options: SetOptions): DocumentData; + toFirestore(modelObject: WithFieldValue): DocumentData; + toFirestore( + modelObject: NestedPartialWithFieldValue, + options: SetOptions + ): DocumentData; fromFirestore(snapshot: unknown, options?: unknown): T; } diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 7597ad62b05..517e2513f20 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -32,6 +32,7 @@ import { withTestDocAndInitialData } from '../util/helpers'; import { DEFAULT_SETTINGS, DEFAULT_PROJECT_ID } from '../util/settings'; +import { NestedPartialWithFieldValue } from '../../../src/lite/reference'; use(chaiAsPromised); @@ -1351,7 +1352,7 @@ apiDescribe('Database', (persistence: boolean) => { const postConverterMerge = { toFirestore( - post: Partial, + post: NestedPartialWithFieldValue, options?: firestore.SetOptions ): firestore.DocumentData { if (options && (options.merge || options.mergeFields)) { diff --git a/packages/firestore/test/lite/helpers.ts b/packages/firestore/test/lite/helpers.ts index bd57fb14231..789f58614d4 100644 --- a/packages/firestore/test/lite/helpers.ts +++ b/packages/firestore/test/lite/helpers.ts @@ -26,7 +26,8 @@ import { DocumentData, CollectionReference, DocumentReference, - SetOptions + SetOptions, + NestedPartialWithFieldValue } from '../../src/lite/reference'; import { setDoc } from '../../src/lite/reference_impl'; import { FirestoreSettings } from '../../src/lite/settings'; @@ -102,7 +103,11 @@ export function withTestCollection( // Used for testing the FirestoreDataConverter. export class Post { - constructor(readonly title: string, readonly author: string) {} + constructor( + readonly title: string, + readonly author: string, + readonly id = 1 + ) {} byline(): string { return this.title + ', by ' + this.author; } @@ -119,7 +124,10 @@ export const postConverter = { }; export const postConverterMerge = { - toFirestore(post: Partial, options?: SetOptions): DocumentData { + toFirestore( + post: NestedPartialWithFieldValue, + options?: SetOptions + ): DocumentData { if ( options && ((options as { merge: true }).merge || diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 18b3e8b287b..df183e2db82 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -57,7 +57,9 @@ import { collectionGroup, SetOptions, UpdateData, - DocumentData + DocumentData, + WithFieldValue, + NestedPartialWithFieldValue } from '../../src/lite/reference'; import { addDoc, @@ -337,10 +339,13 @@ describe('getDoc()', () => { * DocumentReference-based mutation API. */ interface MutationTester { - set(documentRef: DocumentReference, data: T): Promise; set( documentRef: DocumentReference, - data: Partial, + data: WithFieldValue + ): Promise; + set( + documentRef: DocumentReference, + data: NestedPartialWithFieldValue, options: SetOptions ): Promise; update( @@ -580,7 +585,11 @@ function genericMutationTests( const coll = collection(db, 'posts'); const ref = doc(coll, 'post').withConverter(postConverterMerge); await setDoc(ref, new Post('walnut', 'author')); - await setDoc(ref, { title: 'olive' }, { merge: true }); + await setDoc( + ref, + { title: 'olive', id: increment(2) }, + { merge: true } + ); const postDoc = await getDoc(ref); expect(postDoc.get('title')).to.equal('olive'); expect(postDoc.get('author')).to.equal('author'); From e87b617c19bee844cb1ad2ed3be00935338296fa Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Tue, 10 Aug 2021 18:18:07 -0500 Subject: [PATCH 02/22] add support for TypedUpdateData + sanity tests --- packages/firestore/src/exp/reference_impl.ts | 11 +- packages/firestore/src/lite/reference.ts | 33 ++++ packages/firestore/src/lite/reference_impl.ts | 11 +- .../firestore/test/lite/integration.test.ts | 153 +++++++++++++++++- 4 files changed, 193 insertions(+), 15 deletions(-) diff --git a/packages/firestore/src/exp/reference_impl.ts b/packages/firestore/src/exp/reference_impl.ts index 8043aeb05f8..676e0407ed6 100644 --- a/packages/firestore/src/exp/reference_impl.ts +++ b/packages/firestore/src/exp/reference_impl.ts @@ -45,6 +45,7 @@ import { NestedPartialWithFieldValue, Query, SetOptions, + TypedUpdateData, UpdateData, WithFieldValue } from '../lite/reference'; @@ -302,9 +303,9 @@ export function setDoc( * @returns A Promise resolved once the data has been successfully written * to the backend (note that it won't resolve while you're offline). */ -export function updateDoc( - reference: DocumentReference, - data: UpdateData +export function updateDoc( + reference: DocumentReference, + data: TypedUpdateData ): Promise; /** * Updates fields in the document referred to by the specified @@ -327,9 +328,9 @@ export function updateDoc( value: unknown, ...moreFieldsAndValues: unknown[] ): Promise; -export function updateDoc( +export function updateDoc( reference: DocumentReference, - fieldOrUpdateData: string | FieldPath | UpdateData, + fieldOrUpdateData: string | FieldPath | TypedUpdateData, value?: unknown, ...moreFieldsAndValues: unknown[] ): Promise { diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite/reference.ts index 08a845f22dc..5c0e2e41cd5 100644 --- a/packages/firestore/src/lite/reference.ts +++ b/packages/firestore/src/lite/reference.ts @@ -74,6 +74,39 @@ export interface UpdateData { // eslint-disable-next-line @typescript-eslint/no-explicit-any [fieldPath: string]: any; } +// Represents an update object to Firestore document data, which can contain either fields like {a: 2} +// or dot-separated paths such as {"a.b" : 2} (which updates the nested property "b" in map field "a"). +export type TypedUpdateData = T extends Builtin + ? T + : T extends Map + ? Map, TypedUpdateData> + : T extends {} + ? { [K in keyof T]?: TypedUpdateData | FieldValue } & + NestedUpdateFields + : Partial; + +// For each field (e.g. "bar"), calculate its nested keys (e.g. {"bar.baz": T1, "bar.quax": T2}), and then +// intersect them together to make one giant map containing all possible keys (all marked as optional). +type NestedUpdateFields> = UnionToIntersection< + { + [K in keyof T & string]: T[K] extends Record // Only allow nesting for map values + ? AddPrefixToKeys> // Recurse into map and add "bar." in front of every key + : never; + }[keyof T & string] +>; + +// Return a new map where every key is prepended with Prefix + dot. +type AddPrefixToKeys> = + // Remap K => Prefix.K. See https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#key-remapping-via-as + { [K in keyof T & string as `${Prefix}.${K}`]+?: T[K] }; + +// This takes union type U = T1 | T2 | ... and returns a intersected type (T1 & T2 & ...) +type UnionToIntersection = + // Works because "multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred" + // https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-inference-in-conditional-types + (U extends any ? (k: U) => void : never) extends (k: infer I) => void + ? I + : never; /** * An options object that configures the behavior of {@link @firebase/firestore/lite#(setDoc:1)}, {@link diff --git a/packages/firestore/src/lite/reference_impl.ts b/packages/firestore/src/lite/reference_impl.ts index dae29deecef..d8b6c24109c 100644 --- a/packages/firestore/src/lite/reference_impl.ts +++ b/packages/firestore/src/lite/reference_impl.ts @@ -44,6 +44,7 @@ import { NestedPartialWithFieldValue, Query, SetOptions, + TypedUpdateData, UpdateData, WithFieldValue } from './reference'; @@ -266,9 +267,9 @@ export function setDoc( * @returns A Promise resolved once the data has been successfully written * to the backend. */ -export function updateDoc( - reference: DocumentReference, - data: UpdateData +export function updateDoc( + reference: DocumentReference, + data: TypedUpdateData ): Promise; /** * Updates fields in the document referred to by the specified @@ -296,9 +297,9 @@ export function updateDoc( value: unknown, ...moreFieldsAndValues: unknown[] ): Promise; -export function updateDoc( +export function updateDoc( reference: DocumentReference, - fieldOrUpdateData: string | FieldPath | UpdateData, + fieldOrUpdateData: string | FieldPath | TypedUpdateData, value?: unknown, ...moreFieldsAndValues: unknown[] ): Promise { diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index df183e2db82..04d7a0c1b96 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -59,7 +59,8 @@ import { UpdateData, DocumentData, WithFieldValue, - NestedPartialWithFieldValue + NestedPartialWithFieldValue, + TypedUpdateData } from '../../src/lite/reference'; import { addDoc, @@ -69,7 +70,11 @@ import { setDoc, updateDoc } from '../../src/lite/reference_impl'; -import { snapshotEqual, QuerySnapshot } from '../../src/lite/snapshot'; +import { + snapshotEqual, + QuerySnapshot, + QueryDocumentSnapshot +} from '../../src/lite/snapshot'; import { Timestamp } from '../../src/lite/timestamp'; import { runTransaction } from '../../src/lite/transaction'; import { writeBatch } from '../../src/lite/write_batch'; @@ -348,9 +353,9 @@ interface MutationTester { data: NestedPartialWithFieldValue, options: SetOptions ): Promise; - update( - documentRef: DocumentReference, - data: UpdateData + update( + documentRef: DocumentReference, + data: TypedUpdateData ): Promise; update( documentRef: DocumentReference, @@ -596,6 +601,144 @@ function genericMutationTests( }); }); + it('temporary sanity check tests', async () => { + class TestObject { + constructor( + readonly outerString: string, + readonly outerNum: number, + readonly outerArr: string[], + readonly nested: { + innerNested: { + innerNestedNum: number; + innerNestedString: string; + }; + innerArr: number[]; + timestamp: Timestamp; + } + ) {} + } + + const testConverterMerge = { + toFirestore(testObj: WithFieldValue, options?: SetOptions) { + return { ...testObj }; + }, + fromFirestore(snapshot: QueryDocumentSnapshot): TestObject { + const data = snapshot.data(); + return new TestObject( + data.outerString, + data.outerNum, + data.outerArr, + data.nested + ); + } + }; + + return withTestDb(async db => { + const coll = collection(db, 'posts'); + const ref = doc(coll, 'testobj').withConverter(testConverterMerge); + + // Allow Field Values and nested partials. + await setDoc( + ref, + { + outerString: deleteField(), + nested: { + innerNested: { + innerNestedNum: increment(1) + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }, + { merge: true } + ); + + // Checks for non-existent properties + await setDoc( + ref, + { + // @ts-expect-error + nonexistent: 'foo' + }, + { merge: true } + ); + await setDoc( + ref, + { + nested: { + // @ts-expect-error + nonexistent: 'foo' + } + }, + { merge: true } + ); + + // Nested Partials are checked + await setDoc( + ref, + { + nested: { + innerNested: { + // @ts-expect-error + innerNestedNum: 'string' + }, + // @ts-expect-error + innerArr: 2 + } + }, + { merge: true } + ); + await setDoc( + ref, + { + // @ts-expect-error + nested: 3 + }, + { merge: true } + ); + + // Can use update to verify fields + await updateDoc(ref, { + // @ts-expect-error + outerString: 3, + // @ts-expect-error + outerNum: [], + outerArr: arrayUnion('foo'), + nested: { + innerNested: { + // @ts-expect-error + innerNestedNum: 'string' + }, + // @ts-expect-error + innerArr: 2, + timestamp: serverTimestamp() + } + }); + + // Cannot update nonexistent fields + await updateDoc(ref, { + // @ts-expect-error + nonexistent: 'foo' + }); + await updateDoc(ref, { + nested: { + // @ts-expect-error + nonexistent: 'foo' + } + }); + + // Can use update to check string separated fields + await updateDoc(ref, { + 'nested.innerNested.innerNestedNum': 4, + // @ts-expect-error + 'nested.innerNested.innerNestedString': 4, + // @ts-expect-error + 'nested.innerArr': 3, + 'nested.timestamp': serverTimestamp() + }); + }); + }); + it('supports partials with mergeFields', async () => { return withTestDb(async db => { const coll = collection(db, 'posts'); From fdd432a8045e7e8f510976d9b0e699d611df952f Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Tue, 10 Aug 2021 18:28:43 -0500 Subject: [PATCH 03/22] add support for WriteBatch and Transaction --- packages/firestore/src/api/database.ts | 26 ++++++++++------- packages/firestore/src/lite/transaction.ts | 23 ++++++++++----- packages/firestore/src/lite/write_batch.ts | 29 ++++++++++++++----- .../firestore/test/lite/integration.test.ts | 20 ++++++------- 4 files changed, 62 insertions(+), 36 deletions(-) diff --git a/packages/firestore/src/api/database.ts b/packages/firestore/src/api/database.ts index b4444bd1665..11fdcc58ef1 100644 --- a/packages/firestore/src/api/database.ts +++ b/packages/firestore/src/api/database.ts @@ -441,21 +441,24 @@ export class Transaction implements PublicTransaction, Compat { set( documentRef: DocumentReference, - data: Partial, + data: NestedPartialWithFieldValue, options: PublicSetOptions ): Transaction; - set(documentRef: DocumentReference, data: T): Transaction; + set( + documentRef: DocumentReference, + data: WithFieldValue + ): Transaction; set( documentRef: PublicDocumentReference, - data: T | Partial, + data: WithFieldValue | NestedPartialWithFieldValue, options?: PublicSetOptions ): Transaction { const ref = castReference(documentRef); if (options) { validateSetOptions('Transaction.set', options); - this._delegate.set(ref, data, options); + this._delegate.set(ref, data as NestedPartialWithFieldValue, options); } else { - this._delegate.set(ref, data); + this._delegate.set(ref, data as WithFieldValue); } return this; } @@ -502,21 +505,24 @@ export class WriteBatch implements PublicWriteBatch, Compat { constructor(readonly _delegate: ExpWriteBatch) {} set( documentRef: DocumentReference, - data: Partial, + data: NestedPartialWithFieldValue, options: PublicSetOptions ): WriteBatch; - set(documentRef: DocumentReference, data: T): WriteBatch; + set( + documentRef: DocumentReference, + data: WithFieldValue + ): WriteBatch; set( documentRef: PublicDocumentReference, - data: T | Partial, + data: WithFieldValue | NestedPartialWithFieldValue, options?: PublicSetOptions ): WriteBatch { const ref = castReference(documentRef); if (options) { validateSetOptions('WriteBatch.set', options); - this._delegate.set(ref, data, options); + this._delegate.set(ref, data as NestedPartialWithFieldValue, options); } else { - this._delegate.set(ref, data); + this._delegate.set(ref, data as WithFieldValue); } return this; } diff --git a/packages/firestore/src/lite/transaction.ts b/packages/firestore/src/lite/transaction.ts index b9adbb67931..4272668d59c 100644 --- a/packages/firestore/src/lite/transaction.ts +++ b/packages/firestore/src/lite/transaction.ts @@ -27,7 +27,14 @@ import { Deferred } from '../util/promise'; import { getDatastore } from './components'; import { Firestore } from './database'; import { FieldPath } from './field_path'; -import { DocumentReference, SetOptions, UpdateData } from './reference'; +import { + DocumentReference, + NestedPartialWithFieldValue, + SetOptions, + TypedUpdateData, + UpdateData, + WithFieldValue +} from './reference'; import { applyFirestoreDataConverter, LiteUserDataWriter @@ -114,7 +121,7 @@ export class Transaction { * @param data - An object of the fields and values for the document. * @returns This `Transaction` instance. Used for chaining method calls. */ - set(documentRef: DocumentReference, data: T): this; + set(documentRef: DocumentReference, data: WithFieldValue): this; /** * Writes to the document referred to by the provided {@link * DocumentReference}. If the document does not exist yet, it will be created. @@ -128,12 +135,12 @@ export class Transaction { */ set( documentRef: DocumentReference, - data: Partial, + data: NestedPartialWithFieldValue, options: SetOptions ): this; set( documentRef: DocumentReference, - value: T, + value: WithFieldValue | NestedPartialWithFieldValue, options?: SetOptions ): this { const ref = validateReference(documentRef, this._firestore); @@ -165,7 +172,7 @@ export class Transaction { * within the document. * @returns This `Transaction` instance. Used for chaining method calls. */ - update(documentRef: DocumentReference, data: UpdateData): this; + update(documentRef: DocumentReference, data: TypedUpdateData): this; /** * Updates fields in the document referred to by the provided {@link * DocumentReference}. The update will fail if applied to a document that does @@ -186,9 +193,9 @@ export class Transaction { value: unknown, ...moreFieldsAndValues: unknown[] ): this; - update( - documentRef: DocumentReference, - fieldOrUpdateData: string | FieldPath | UpdateData, + update( + documentRef: DocumentReference, + fieldOrUpdateData: string | FieldPath | TypedUpdateData, value?: unknown, ...moreFieldsAndValues: unknown[] ): this { diff --git a/packages/firestore/src/lite/write_batch.ts b/packages/firestore/src/lite/write_batch.ts index ce1f786c8e2..592f337c04b 100644 --- a/packages/firestore/src/lite/write_batch.ts +++ b/packages/firestore/src/lite/write_batch.ts @@ -25,7 +25,14 @@ import { cast } from '../util/input_validation'; import { getDatastore } from './components'; import { Firestore } from './database'; import { FieldPath } from './field_path'; -import { DocumentReference, SetOptions, UpdateData } from './reference'; +import { + DocumentReference, + NestedPartialWithFieldValue, + SetOptions, + TypedUpdateData, + UpdateData, + WithFieldValue +} from './reference'; import { applyFirestoreDataConverter } from './reference_impl'; import { newUserDataReader, @@ -67,7 +74,10 @@ export class WriteBatch { * @param data - An object of the fields and values for the document. * @returns This `WriteBatch` instance. Used for chaining method calls. */ - set(documentRef: DocumentReference, data: T): WriteBatch; + set( + documentRef: DocumentReference, + data: WithFieldValue + ): WriteBatch; /** * Writes to the document referred to by the provided {@link * DocumentReference}. If the document does not exist yet, it will be created. @@ -81,12 +91,12 @@ export class WriteBatch { */ set( documentRef: DocumentReference, - data: Partial, + data: NestedPartialWithFieldValue, options: SetOptions ): WriteBatch; set( documentRef: DocumentReference, - data: T, + data: WithFieldValue | NestedPartialWithFieldValue, options?: SetOptions ): WriteBatch { this._verifyNotCommitted(); @@ -120,7 +130,10 @@ export class WriteBatch { * within the document. * @returns This `WriteBatch` instance. Used for chaining method calls. */ - update(documentRef: DocumentReference, data: UpdateData): WriteBatch; + update( + documentRef: DocumentReference, + data: TypedUpdateData + ): WriteBatch; /** * Updates fields in the document referred to by this {@link * DocumentReference}. The update will fail if applied to a document that does @@ -141,9 +154,9 @@ export class WriteBatch { value: unknown, ...moreFieldsAndValues: unknown[] ): WriteBatch; - update( - documentRef: DocumentReference, - fieldOrUpdateData: string | FieldPath | UpdateData, + update( + documentRef: DocumentReference, + fieldOrUpdateData: string | FieldPath | TypedUpdateData, value?: unknown, ...moreFieldsAndValues: unknown[] ): WriteBatch { diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 04d7a0c1b96..e3f38c580cd 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -382,7 +382,7 @@ describe('WriteBatch', () => { set( ref: DocumentReference, - data: T | Partial, + data: WithFieldValue | NestedPartialWithFieldValue, options?: SetOptions ): Promise { const batch = writeBatch(ref.firestore); @@ -392,9 +392,9 @@ describe('WriteBatch', () => { return batch.commit(); } - update( - ref: DocumentReference, - dataOrField: UpdateData | string | FieldPath, + update( + ref: DocumentReference, + dataOrField: TypedUpdateData | string | FieldPath, value?: unknown, ...moreFieldsAndValues: unknown[] ): Promise { @@ -445,21 +445,21 @@ describe('Transaction', () => { set( ref: DocumentReference, - data: T | Partial, + data: WithFieldValue | NestedPartialWithFieldValue, options?: SetOptions ): Promise { return runTransaction(ref.firestore, async transaction => { if (options) { - transaction.set(ref, data, options); + transaction.set(ref, data as NestedPartialWithFieldValue, options); } else { - transaction.set(ref, data); + transaction.set(ref, data as WithFieldValue); } }); } - update( - ref: DocumentReference, - dataOrField: UpdateData | string | FieldPath, + update( + ref: DocumentReference, + dataOrField: TypedUpdateData | string | FieldPath, value?: unknown, ...moreFieldsAndValues: unknown[] ): Promise { From 01e1529b4b18e0ae258da82e0c1aedd9b16c37e1 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Wed, 11 Aug 2021 11:48:45 -0500 Subject: [PATCH 04/22] incremental fixes --- packages/firestore/src/api/database.ts | 36 +++----- packages/firestore/src/exp/reference_impl.ts | 8 +- packages/firestore/src/exp/snapshot.ts | 7 +- packages/firestore/src/lite/reference.ts | 16 ++-- packages/firestore/src/lite/reference_impl.ts | 14 ++-- packages/firestore/src/lite/snapshot.ts | 7 +- packages/firestore/src/lite/transaction.ts | 6 +- .../firestore/src/lite/user_data_reader.ts | 11 +-- packages/firestore/src/lite/write_batch.ts | 6 +- .../test/integration/api/database.test.ts | 4 +- packages/firestore/test/lite/helpers.ts | 7 +- .../firestore/test/lite/integration.test.ts | 82 +++++++++++++++++-- 12 files changed, 123 insertions(+), 81 deletions(-) diff --git a/packages/firestore/src/api/database.ts b/packages/firestore/src/api/database.ts index 11fdcc58ef1..8e08bd5382e 100644 --- a/packages/firestore/src/api/database.ts +++ b/packages/firestore/src/api/database.ts @@ -127,7 +127,7 @@ import { NextFn, PartialObserver } from './observer'; -import { NestedPartialWithFieldValue, WithFieldValue } from '../lite/reference'; +import { NestedPartial, WithFieldValue } from '../lite/reference'; /** * A persistence provider for either memory-only or IndexedDB persistence. @@ -441,22 +441,19 @@ export class Transaction implements PublicTransaction, Compat { set( documentRef: DocumentReference, - data: NestedPartialWithFieldValue, + data: Partial, options: PublicSetOptions ): Transaction; - set( - documentRef: DocumentReference, - data: WithFieldValue - ): Transaction; + set(documentRef: DocumentReference, data: T): Transaction; set( documentRef: PublicDocumentReference, - data: WithFieldValue | NestedPartialWithFieldValue, + data: T | Partial, options?: PublicSetOptions ): Transaction { const ref = castReference(documentRef); if (options) { validateSetOptions('Transaction.set', options); - this._delegate.set(ref, data as NestedPartialWithFieldValue, options); + this._delegate.set(ref, data as NestedPartial, options); } else { this._delegate.set(ref, data as WithFieldValue); } @@ -505,22 +502,19 @@ export class WriteBatch implements PublicWriteBatch, Compat { constructor(readonly _delegate: ExpWriteBatch) {} set( documentRef: DocumentReference, - data: NestedPartialWithFieldValue, + data: Partial, options: PublicSetOptions ): WriteBatch; - set( - documentRef: DocumentReference, - data: WithFieldValue - ): WriteBatch; + set(documentRef: DocumentReference, data: T): WriteBatch; set( documentRef: PublicDocumentReference, - data: WithFieldValue | NestedPartialWithFieldValue, + data: T | Partial, options?: PublicSetOptions ): WriteBatch { const ref = castReference(documentRef); if (options) { validateSetOptions('WriteBatch.set', options); - this._delegate.set(ref, data as NestedPartialWithFieldValue, options); + this._delegate.set(ref, data as NestedPartial, options); } else { this._delegate.set(ref, data as WithFieldValue); } @@ -606,11 +600,11 @@ class FirestoreDataConverter toFirestore(modelObject: WithFieldValue): PublicDocumentData; toFirestore( - modelObject: NestedPartialWithFieldValue, + modelObject: NestedPartial, options: PublicSetOptions ): PublicDocumentData; toFirestore( - modelObject: WithFieldValue | NestedPartialWithFieldValue, + modelObject: WithFieldValue | NestedPartial, options?: PublicSetOptions ): PublicDocumentData { if (!options) { @@ -741,11 +735,7 @@ export class DocumentReference options = validateSetOptions('DocumentReference.set', options); try { if (options) { - return setDoc( - this._delegate, - value as NestedPartialWithFieldValue, - options - ); + return setDoc(this._delegate, value as NestedPartial, options); } else { return setDoc(this._delegate, value as WithFieldValue); } @@ -1302,7 +1292,7 @@ export class CollectionReference } add(data: T): Promise> { - return addDoc(this._delegate, data).then( + return addDoc(this._delegate, data as WithFieldValue).then( docRef => new DocumentReference(this.firestore, docRef) ); } diff --git a/packages/firestore/src/exp/reference_impl.ts b/packages/firestore/src/exp/reference_impl.ts index 676e0407ed6..8c2804e3dc1 100644 --- a/packages/firestore/src/exp/reference_impl.ts +++ b/packages/firestore/src/exp/reference_impl.ts @@ -42,7 +42,7 @@ import { CollectionReference, doc, DocumentReference, - NestedPartialWithFieldValue, + NestedPartial, Query, SetOptions, TypedUpdateData, @@ -261,12 +261,12 @@ export function setDoc( */ export function setDoc( reference: DocumentReference, - data: NestedPartialWithFieldValue, + data: NestedPartial, options: SetOptions ): Promise; export function setDoc( reference: DocumentReference, - data: WithFieldValue | NestedPartialWithFieldValue, + data: WithFieldValue | NestedPartial, options?: SetOptions ): Promise { reference = cast>(reference, DocumentReference); @@ -274,7 +274,7 @@ export function setDoc( const convertedValue = applyFirestoreDataConverter( reference.converter, - data, + data as WithFieldValue, options ); const dataReader = newUserDataReader(firestore); diff --git a/packages/firestore/src/exp/snapshot.ts b/packages/firestore/src/exp/snapshot.ts index b420201a7cb..a1c7ab32071 100644 --- a/packages/firestore/src/exp/snapshot.ts +++ b/packages/firestore/src/exp/snapshot.ts @@ -20,7 +20,7 @@ import { ChangeType, ViewSnapshot } from '../core/view_snapshot'; import { FieldPath } from '../lite/field_path'; import { DocumentData, - NestedPartialWithFieldValue, + NestedPartial, Query, queryEqual, SetOptions, @@ -99,10 +99,7 @@ export interface FirestoreDataConverter * Firestore database). Used with {@link (setDoc:1)}, {@link (WriteBatch.set:1)} * and {@link (Transaction.set:1)} with `merge:true` or `mergeFields`. */ - toFirestore( - modelObject: NestedPartialWithFieldValue, - options: SetOptions - ): DocumentData; + toFirestore(modelObject: NestedPartial, options: SetOptions): DocumentData; /** * Called by the Firestore SDK to convert Firestore data into an object of diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite/reference.ts index 5c0e2e41cd5..67cb3292bec 100644 --- a/packages/firestore/src/lite/reference.ts +++ b/packages/firestore/src/lite/reference.ts @@ -50,19 +50,21 @@ export interface DocumentData { } type Primitive = string | number | boolean | bigint | undefined | null; -// eslint-disable-next-line @typescript-eslint/ban-types -type Builtin = Primitive | Function; /** Like Partial but recursive */ -export type NestedPartialWithFieldValue = T extends Builtin +export type NestedPartial = T extends Primitive ? T : T extends Map - ? Map, NestedPartialWithFieldValue> + ? Map, NestedPartial> : T extends {} - ? { [K in keyof T]?: NestedPartialWithFieldValue | FieldValue } + ? { [K in keyof T]?: NestedPartial | FieldValue } : Partial; -export type WithFieldValue = { [P in keyof T]: T[P] | FieldValue }; +export type WithFieldValue = T extends Primitive + ? T + : T extends {} + ? { [K in keyof T]: WithFieldValue | FieldValue } + : Partial; /** * Update data (for use with {@link @firebase/firestore/lite#(updateDoc:1)}) consists of field paths (e.g. @@ -76,7 +78,7 @@ export interface UpdateData { } // Represents an update object to Firestore document data, which can contain either fields like {a: 2} // or dot-separated paths such as {"a.b" : 2} (which updates the nested property "b" in map field "a"). -export type TypedUpdateData = T extends Builtin +export type TypedUpdateData = T extends Primitive ? T : T extends Map ? Map, TypedUpdateData> diff --git a/packages/firestore/src/lite/reference_impl.ts b/packages/firestore/src/lite/reference_impl.ts index d8b6c24109c..077d0a5d863 100644 --- a/packages/firestore/src/lite/reference_impl.ts +++ b/packages/firestore/src/lite/reference_impl.ts @@ -41,7 +41,7 @@ import { CollectionReference, doc, DocumentReference, - NestedPartialWithFieldValue, + NestedPartial, Query, SetOptions, TypedUpdateData, @@ -74,7 +74,7 @@ import { AbstractUserDataWriter } from './user_data_writer'; */ export function applyFirestoreDataConverter( converter: UntypedFirestoreDataConverter | null, - value: T, + value: WithFieldValue | NestedPartial, options?: PublicSetOptions ): PublicDocumentData { let convertedValue; @@ -85,7 +85,7 @@ export function applyFirestoreDataConverter( // eslint-disable-next-line @typescript-eslint/no-explicit-any convertedValue = (converter as any).toFirestore(value, options); } else { - convertedValue = converter.toFirestore(value); + convertedValue = converter.toFirestore(value as WithFieldValue); } } else { convertedValue = value as PublicDocumentData; @@ -220,18 +220,18 @@ export function setDoc( */ export function setDoc( reference: DocumentReference, - data: NestedPartialWithFieldValue, + data: NestedPartial, options: SetOptions ): Promise; export function setDoc( reference: DocumentReference, - data: WithFieldValue | NestedPartialWithFieldValue, + data: WithFieldValue | NestedPartial, options?: SetOptions ): Promise { reference = cast>(reference, DocumentReference); const convertedValue = applyFirestoreDataConverter( reference.converter, - data, + data as WithFieldValue, options ); const dataReader = newUserDataReader(reference.firestore); @@ -376,7 +376,7 @@ export function deleteDoc( */ export function addDoc( reference: CollectionReference, - data: T + data: WithFieldValue ): Promise> { reference = cast>(reference, CollectionReference); const docRef = doc(reference); diff --git a/packages/firestore/src/lite/snapshot.ts b/packages/firestore/src/lite/snapshot.ts index 223370ba205..22bc4e499dd 100644 --- a/packages/firestore/src/lite/snapshot.ts +++ b/packages/firestore/src/lite/snapshot.ts @@ -27,7 +27,7 @@ import { FieldPath } from './field_path'; import { DocumentData, DocumentReference, - NestedPartialWithFieldValue, + NestedPartial, Query, queryEqual, SetOptions, @@ -93,10 +93,7 @@ export interface FirestoreDataConverter { * Firestore database). Used with {@link @firebase/firestore/lite#(setDoc:1)}, {@link @firebase/firestore/lite#(WriteBatch.set:1)} * and {@link @firebase/firestore/lite#(Transaction.set:1)} with `merge:true` or `mergeFields`. */ - toFirestore( - modelObject: NestedPartialWithFieldValue, - options: SetOptions - ): DocumentData; + toFirestore(modelObject: NestedPartial, options: SetOptions): DocumentData; /** * Called by the Firestore SDK to convert Firestore data into an object of diff --git a/packages/firestore/src/lite/transaction.ts b/packages/firestore/src/lite/transaction.ts index 4272668d59c..f425a0bd594 100644 --- a/packages/firestore/src/lite/transaction.ts +++ b/packages/firestore/src/lite/transaction.ts @@ -29,7 +29,7 @@ import { Firestore } from './database'; import { FieldPath } from './field_path'; import { DocumentReference, - NestedPartialWithFieldValue, + NestedPartial, SetOptions, TypedUpdateData, UpdateData, @@ -135,12 +135,12 @@ export class Transaction { */ set( documentRef: DocumentReference, - data: NestedPartialWithFieldValue, + data: NestedPartial, options: SetOptions ): this; set( documentRef: DocumentReference, - value: WithFieldValue | NestedPartialWithFieldValue, + value: WithFieldValue | NestedPartial, options?: SetOptions ): this { const ref = validateReference(documentRef, this._firestore); diff --git a/packages/firestore/src/lite/user_data_reader.ts b/packages/firestore/src/lite/user_data_reader.ts index 53950d7d125..1091ef15e8e 100644 --- a/packages/firestore/src/lite/user_data_reader.ts +++ b/packages/firestore/src/lite/user_data_reader.ts @@ -63,11 +63,7 @@ import { Firestore } from './database'; import { FieldPath } from './field_path'; import { FieldValue } from './field_value'; import { GeoPoint } from './geo_point'; -import { - DocumentReference, - NestedPartialWithFieldValue, - WithFieldValue -} from './reference'; +import { DocumentReference, NestedPartial, WithFieldValue } from './reference'; import { Timestamp } from './timestamp'; const RESERVED_FIELD_REGEX = /^__.*__$/; @@ -78,10 +74,7 @@ const RESERVED_FIELD_REGEX = /^__.*__$/; */ export interface UntypedFirestoreDataConverter { toFirestore(modelObject: WithFieldValue): DocumentData; - toFirestore( - modelObject: NestedPartialWithFieldValue, - options: SetOptions - ): DocumentData; + toFirestore(modelObject: NestedPartial, options: SetOptions): DocumentData; fromFirestore(snapshot: unknown, options?: unknown): T; } diff --git a/packages/firestore/src/lite/write_batch.ts b/packages/firestore/src/lite/write_batch.ts index 592f337c04b..402cad4c822 100644 --- a/packages/firestore/src/lite/write_batch.ts +++ b/packages/firestore/src/lite/write_batch.ts @@ -27,7 +27,7 @@ import { Firestore } from './database'; import { FieldPath } from './field_path'; import { DocumentReference, - NestedPartialWithFieldValue, + NestedPartial, SetOptions, TypedUpdateData, UpdateData, @@ -91,12 +91,12 @@ export class WriteBatch { */ set( documentRef: DocumentReference, - data: NestedPartialWithFieldValue, + data: NestedPartial, options: SetOptions ): WriteBatch; set( documentRef: DocumentReference, - data: WithFieldValue | NestedPartialWithFieldValue, + data: WithFieldValue | NestedPartial, options?: SetOptions ): WriteBatch { this._verifyNotCommitted(); diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 517e2513f20..798cf0812fb 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -32,7 +32,7 @@ import { withTestDocAndInitialData } from '../util/helpers'; import { DEFAULT_SETTINGS, DEFAULT_PROJECT_ID } from '../util/settings'; -import { NestedPartialWithFieldValue } from '../../../src/lite/reference'; +import { NestedPartial } from '../../../src/lite/reference'; use(chaiAsPromised); @@ -1352,7 +1352,7 @@ apiDescribe('Database', (persistence: boolean) => { const postConverterMerge = { toFirestore( - post: NestedPartialWithFieldValue, + post: NestedPartial, options?: firestore.SetOptions ): firestore.DocumentData { if (options && (options.merge || options.mergeFields)) { diff --git a/packages/firestore/test/lite/helpers.ts b/packages/firestore/test/lite/helpers.ts index 789f58614d4..a41a1412078 100644 --- a/packages/firestore/test/lite/helpers.ts +++ b/packages/firestore/test/lite/helpers.ts @@ -27,7 +27,7 @@ import { CollectionReference, DocumentReference, SetOptions, - NestedPartialWithFieldValue + NestedPartial } from '../../src/lite/reference'; import { setDoc } from '../../src/lite/reference_impl'; import { FirestoreSettings } from '../../src/lite/settings'; @@ -124,10 +124,7 @@ export const postConverter = { }; export const postConverterMerge = { - toFirestore( - post: NestedPartialWithFieldValue, - options?: SetOptions - ): DocumentData { + toFirestore(post: NestedPartial, options?: SetOptions): DocumentData { if ( options && ((options as { merge: true }).merge || diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index e3f38c580cd..0cf33c41a94 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -59,7 +59,7 @@ import { UpdateData, DocumentData, WithFieldValue, - NestedPartialWithFieldValue, + NestedPartial, TypedUpdateData } from '../../src/lite/reference'; import { @@ -350,7 +350,7 @@ interface MutationTester { ): Promise; set( documentRef: DocumentReference, - data: NestedPartialWithFieldValue, + data: NestedPartial, options: SetOptions ): Promise; update( @@ -382,7 +382,7 @@ describe('WriteBatch', () => { set( ref: DocumentReference, - data: WithFieldValue | NestedPartialWithFieldValue, + data: WithFieldValue | NestedPartial, options?: SetOptions ): Promise { const batch = writeBatch(ref.firestore); @@ -445,12 +445,12 @@ describe('Transaction', () => { set( ref: DocumentReference, - data: WithFieldValue | NestedPartialWithFieldValue, + data: WithFieldValue | NestedPartial, options?: SetOptions ): Promise { return runTransaction(ref.firestore, async transaction => { if (options) { - transaction.set(ref, data as NestedPartialWithFieldValue, options); + transaction.set(ref, data as NestedPartial, options); } else { transaction.set(ref, data as WithFieldValue); } @@ -472,7 +472,7 @@ describe('Transaction', () => { ...moreFieldsAndValues ); } else { - transaction.update(ref, dataOrField as UpdateData); + transaction.update(ref, dataOrField as TypedUpdateData); } }); } @@ -619,7 +619,22 @@ function genericMutationTests( } const testConverterMerge = { - toFirestore(testObj: WithFieldValue, options?: SetOptions) { + toFirestore(testObj: NestedPartial, options?: SetOptions) { + return { ...testObj }; + }, + fromFirestore(snapshot: QueryDocumentSnapshot): TestObject { + const data = snapshot.data(); + return new TestObject( + data.outerString, + data.outerNum, + data.outerArr, + data.nested + ); + } + }; + + const testConverter = { + toFirestore(testObj: WithFieldValue) { return { ...testObj }; }, fromFirestore(snapshot: QueryDocumentSnapshot): TestObject { @@ -635,7 +650,7 @@ function genericMutationTests( return withTestDb(async db => { const coll = collection(db, 'posts'); - const ref = doc(coll, 'testobj').withConverter(testConverterMerge); + let ref = doc(coll, 'testobj').withConverter(testConverterMerge); // Allow Field Values and nested partials. await setDoc( @@ -736,6 +751,57 @@ function genericMutationTests( 'nested.innerArr': 3, 'nested.timestamp': serverTimestamp() }); + + // Tests for `WithFieldValue` + ref = doc(coll, 'testobj').withConverter(testConverter); + // Allow Field Values and nested partials. + await setDoc(ref, { + outerString: deleteField(), + outerNum: 3, + outerArr: [], + nested: { + innerNested: { + innerNestedNum: increment(1), + innerNestedString: deleteField() + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }); + + // Type validation still works for outer and nested fields + await setDoc(ref, { + outerString: deleteField(), + outerNum: 3, + // @ts-expect-error + outerArr: 2, + nested: { + innerNested: { + // @ts-expect-error + innerNestedNum: 'string', + innerNestedString: deleteField() + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }); + + // Nonexistent fields should error + await setDoc(ref, { + outerString: deleteField(), + outerNum: 3, + outerArr: [], + nested: { + innerNested: { + // @ts-expect-error + nonexistent: string, + innerNestedNum: 2, + innerNestedString: deleteField() + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }); }); }); From 1d48049bdfb32ccfd412cb17a47bc0e5fd039617 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Wed, 11 Aug 2021 12:24:10 -0500 Subject: [PATCH 05/22] fix exports/imports for exp and lite --- packages/firestore/exp/api.ts | 2 ++ packages/firestore/lite/index.ts | 2 +- packages/firestore/src/exp/reference.ts | 4 +++- packages/firestore/src/exp/reference_impl.ts | 1 - packages/firestore/src/lite/reference.ts | 10 +++++----- packages/firestore/src/lite/reference_impl.ts | 1 - packages/firestore/src/lite/transaction.ts | 1 - packages/firestore/src/lite/write_batch.ts | 1 - 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/firestore/exp/api.ts b/packages/firestore/exp/api.ts index 9ede5594455..a65e3d2997a 100644 --- a/packages/firestore/exp/api.ts +++ b/packages/firestore/exp/api.ts @@ -64,6 +64,8 @@ export { SetOptions, DocumentData, UpdateData, + WithFieldValue, + NestedPartial, refEqual, queryEqual } from '../src/exp/reference'; diff --git a/packages/firestore/lite/index.ts b/packages/firestore/lite/index.ts index 5ed139fcaae..e705149235d 100644 --- a/packages/firestore/lite/index.ts +++ b/packages/firestore/lite/index.ts @@ -40,7 +40,7 @@ export { export { SetOptions, DocumentData, - UpdateData, + TypedUpdateData as UpdateData, DocumentReference, Query, CollectionReference, diff --git a/packages/firestore/src/exp/reference.ts b/packages/firestore/src/exp/reference.ts index 64e4155a2b7..fabd40be52b 100644 --- a/packages/firestore/src/exp/reference.ts +++ b/packages/firestore/src/exp/reference.ts @@ -25,6 +25,8 @@ export { queryEqual, SetOptions, DocumentData, - UpdateData, + TypedUpdateData as UpdateData, + NestedPartial, + WithFieldValue, refEqual } from '../lite/reference'; diff --git a/packages/firestore/src/exp/reference_impl.ts b/packages/firestore/src/exp/reference_impl.ts index 8c2804e3dc1..93c53115c95 100644 --- a/packages/firestore/src/exp/reference_impl.ts +++ b/packages/firestore/src/exp/reference_impl.ts @@ -46,7 +46,6 @@ import { Query, SetOptions, TypedUpdateData, - UpdateData, WithFieldValue } from '../lite/reference'; import { applyFirestoreDataConverter } from '../lite/reference_impl'; diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite/reference.ts index 67cb3292bec..c64b92369a5 100644 --- a/packages/firestore/src/lite/reference.ts +++ b/packages/firestore/src/lite/reference.ts @@ -71,11 +71,11 @@ export type WithFieldValue = T extends Primitive * 'foo' or 'foo.baz') mapped to values. Fields that contain dots reference * nested fields within the document. */ -export interface UpdateData { - /** A mapping between a dot-separated field path and its value. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [fieldPath: string]: any; -} +// export interface UpdateData { +// /** A mapping between a dot-separated field path and its value. */ +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// [fieldPath: string]: any; +// } // Represents an update object to Firestore document data, which can contain either fields like {a: 2} // or dot-separated paths such as {"a.b" : 2} (which updates the nested property "b" in map field "a"). export type TypedUpdateData = T extends Primitive diff --git a/packages/firestore/src/lite/reference_impl.ts b/packages/firestore/src/lite/reference_impl.ts index 077d0a5d863..d529ca6c7cd 100644 --- a/packages/firestore/src/lite/reference_impl.ts +++ b/packages/firestore/src/lite/reference_impl.ts @@ -45,7 +45,6 @@ import { Query, SetOptions, TypedUpdateData, - UpdateData, WithFieldValue } from './reference'; import { diff --git a/packages/firestore/src/lite/transaction.ts b/packages/firestore/src/lite/transaction.ts index f425a0bd594..745622e2293 100644 --- a/packages/firestore/src/lite/transaction.ts +++ b/packages/firestore/src/lite/transaction.ts @@ -32,7 +32,6 @@ import { NestedPartial, SetOptions, TypedUpdateData, - UpdateData, WithFieldValue } from './reference'; import { diff --git a/packages/firestore/src/lite/write_batch.ts b/packages/firestore/src/lite/write_batch.ts index 402cad4c822..3d47697f2a7 100644 --- a/packages/firestore/src/lite/write_batch.ts +++ b/packages/firestore/src/lite/write_batch.ts @@ -30,7 +30,6 @@ import { NestedPartial, SetOptions, TypedUpdateData, - UpdateData, WithFieldValue } from './reference'; import { applyFirestoreDataConverter } from './reference_impl'; From d2df5e8f21cfa5a56c2e84dbeff99de127950c82 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Wed, 11 Aug 2021 15:04:54 -0500 Subject: [PATCH 06/22] Fix docs and imports --- packages/firestore/src/api/database.ts | 2 +- packages/firestore/src/lite/reference.ts | 74 ++++++++++++------- .../test/integration/api/database.test.ts | 3 +- .../firestore/test/lite/integration.test.ts | 3 +- 4 files changed, 52 insertions(+), 30 deletions(-) diff --git a/packages/firestore/src/api/database.ts b/packages/firestore/src/api/database.ts index 8e08bd5382e..13d8d5375c7 100644 --- a/packages/firestore/src/api/database.ts +++ b/packages/firestore/src/api/database.ts @@ -106,6 +106,7 @@ import { AbstractUserDataWriter } from '../../exp/index'; // import from the exp public API import { DatabaseId } from '../core/database_info'; +import { NestedPartial, WithFieldValue } from '../lite/reference'; import { UntypedFirestoreDataConverter } from '../lite/user_data_reader'; import { DocumentKey } from '../model/document_key'; import { FieldPath, ResourcePath } from '../model/path'; @@ -127,7 +128,6 @@ import { NextFn, PartialObserver } from './observer'; -import { NestedPartial, WithFieldValue } from '../lite/reference'; /** * A persistence provider for either memory-only or IndexedDB persistence. diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite/reference.ts index c64b92369a5..719d833fc21 100644 --- a/packages/firestore/src/lite/reference.ts +++ b/packages/firestore/src/lite/reference.ts @@ -51,7 +51,10 @@ export interface DocumentData { type Primitive = string | number | boolean | bigint | undefined | null; -/** Like Partial but recursive */ +/** + * Similar to Typescript's `Partial`, but allows nested fields to be + * omitted and FieldValues to be passed in as property values. + */ export type NestedPartial = T extends Primitive ? T : T extends Map @@ -60,6 +63,10 @@ export type NestedPartial = T extends Primitive ? { [K in keyof T]?: NestedPartial | FieldValue } : Partial; +/** + * Allows FieldValues to be passed in as a property value while maintaining + * type safety. + */ export type WithFieldValue = T extends Primitive ? T : T extends {} @@ -67,17 +74,10 @@ export type WithFieldValue = T extends Primitive : Partial; /** - * Update data (for use with {@link @firebase/firestore/lite#(updateDoc:1)}) consists of field paths (e.g. - * 'foo' or 'foo.baz') mapped to values. Fields that contain dots reference - * nested fields within the document. + * Update data (for use with {@link @firebase/firestore/lite#(updateDoc:1)}) + * that consists of field paths (e.g. 'foo' or 'foo.baz') mapped to values. + * Fields that contain dots reference nested fields within the document. */ -// export interface UpdateData { -// /** A mapping between a dot-separated field path and its value. */ -// // eslint-disable-next-line @typescript-eslint/no-explicit-any -// [fieldPath: string]: any; -// } -// Represents an update object to Firestore document data, which can contain either fields like {a: 2} -// or dot-separated paths such as {"a.b" : 2} (which updates the nested property "b" in map field "a"). export type TypedUpdateData = T extends Primitive ? T : T extends Map @@ -87,28 +87,52 @@ export type TypedUpdateData = T extends Primitive NestedUpdateFields : Partial; -// For each field (e.g. "bar"), calculate its nested keys (e.g. {"bar.baz": T1, "bar.quax": T2}), and then -// intersect them together to make one giant map containing all possible keys (all marked as optional). +/** + * For each field (e.g. 'bar'), find all nested keys (e.g. {'bar.baz': T1, + * 'bar.qux': T2}). Intersect them together to make a single map containing + * all possible keys that are all marked as optional + */ +// Mapping between a field and its value. +// eslint-disable-next-line @typescript-eslint/no-explicit-any type NestedUpdateFields> = UnionToIntersection< { - [K in keyof T & string]: T[K] extends Record // Only allow nesting for map values - ? AddPrefixToKeys> // Recurse into map and add "bar." in front of every key - : never; - }[keyof T & string] + // Check that T[K] extends Record to only allow nesting for map values. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [K in keyof T & string]: T[K] extends Record + ? // Recurse into the map and add the prefix in front of each key + // (e.g. Prefix 'bar.' to create: 'bar.baz' and 'bar.qux'. + AddPrefixToKeys> + : // TypedUpdateData is always a map of values. + never; + }[keyof T & string] // Also include the generated prefix-string keys. >; -// Return a new map where every key is prepended with Prefix + dot. +/** + * Returns a new map where every key is prefixed with the outer key appended + * to a dot. + */ +// Mapping between a field and its value. +// eslint-disable-next-line @typescript-eslint/no-explicit-any type AddPrefixToKeys> = // Remap K => Prefix.K. See https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#key-remapping-via-as { [K in keyof T & string as `${Prefix}.${K}`]+?: T[K] }; -// This takes union type U = T1 | T2 | ... and returns a intersected type (T1 & T2 & ...) -type UnionToIntersection = - // Works because "multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred" - // https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-inference-in-conditional-types - (U extends any ? (k: U) => void : never) extends (k: infer I) => void - ? I - : never; +/** + * Given a union type `U = T1 | T2 | ...`, returns an intersected type + * `(T1 & T2 & ...)`. + * + * Uses distributive conditional types and inference from conditional types. + * This works because multiple candidates for the same type variable in + * contra-variant positions causes an intersection type to be inferred. + * https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-inference-in-conditional-types + * https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( + k: infer I +) => void + ? I + : never; /** * An options object that configures the behavior of {@link @firebase/firestore/lite#(setDoc:1)}, {@link diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 798cf0812fb..7597ad62b05 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -32,7 +32,6 @@ import { withTestDocAndInitialData } from '../util/helpers'; import { DEFAULT_SETTINGS, DEFAULT_PROJECT_ID } from '../util/settings'; -import { NestedPartial } from '../../../src/lite/reference'; use(chaiAsPromised); @@ -1352,7 +1351,7 @@ apiDescribe('Database', (persistence: boolean) => { const postConverterMerge = { toFirestore( - post: NestedPartial, + post: Partial, options?: firestore.SetOptions ): firestore.DocumentData { if (options && (options.merge || options.mergeFields)) { diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 0cf33c41a94..6e18fe8f1c1 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -56,7 +56,6 @@ import { queryEqual, collectionGroup, SetOptions, - UpdateData, DocumentData, WithFieldValue, NestedPartial, @@ -794,7 +793,7 @@ function genericMutationTests( nested: { innerNested: { // @ts-expect-error - nonexistent: string, + nonexistent: 'string', innerNestedNum: 2, innerNestedString: deleteField() }, From 21cc4bf23fe0cfb7e069db54bdcc5504bd94041b Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Wed, 11 Aug 2021 15:50:30 -0500 Subject: [PATCH 07/22] Fixing imports --- common/api-review/firestore-lite.api.md | 56 +- common/api-review/firestore.api.md | 1014 +++++++++++----------- packages/firestore/compat/config.ts | 2 +- packages/firestore/exp/api.ts | 4 + packages/firestore/lite/index.ts | 8 +- packages/firestore/src/exp/reference.ts | 6 +- packages/firestore/src/lite/reference.ts | 38 +- 7 files changed, 599 insertions(+), 529 deletions(-) diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 624958d0239..5933498439b 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -9,7 +9,12 @@ import { FirebaseApp } from '@firebase/app-exp'; import { LogLevelString as LogLevel } from '@firebase/logger'; // @public -export function addDoc(reference: CollectionReference, data: T): Promise>; +export function addDoc(reference: CollectionReference, data: WithFieldValue): Promise>; + +// @public +export type AddPrefixToKeys> = { + [K in keyof T & string as `${Prefix}.${K}`]+?: T[K]; +}; // @public export function arrayRemove(...elements: unknown[]): FieldValue; @@ -132,8 +137,8 @@ export class Firestore { // @public export interface FirestoreDataConverter { fromFirestore(snapshot: QueryDocumentSnapshot): T; - toFirestore(modelObject: T): DocumentData; - toFirestore(modelObject: Partial, options: SetOptions): DocumentData; + toFirestore(modelObject: WithFieldValue): DocumentData; + toFirestore(modelObject: NestedPartial, options: SetOptions): DocumentData; } // @public @@ -182,12 +187,25 @@ export function limitToLast(limit: number): QueryConstraint; export { LogLevel } +// @public +export type NestedPartial = T extends Primitive ? T : T extends Map ? Map, NestedPartial> : T extends {} ? { + [K in keyof T]?: NestedPartial | FieldValue; +} : Partial; + +// @public +export type NestedUpdateFields> = UnionToIntersection<{ + [K in keyof T & string]: T[K] extends Record ? AddPrefixToKeys> : never; +}[keyof T & string]>; + // @public export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDirection): QueryConstraint; // @public export type OrderByDirection = 'desc' | 'asc'; +// @public (undocumented) +export type Primitive = string | number | boolean | bigint | undefined | null; + // @public export class Query { protected constructor(); @@ -237,10 +255,10 @@ export function runTransaction(firestore: Firestore, updateFunction: (transac export function serverTimestamp(): FieldValue; // @public -export function setDoc(reference: DocumentReference, data: T): Promise; +export function setDoc(reference: DocumentReference, data: WithFieldValue): Promise; // @public -export function setDoc(reference: DocumentReference, data: Partial, options: SetOptions): Promise; +export function setDoc(reference: DocumentReference, data: NestedPartial, options: SetOptions): Promise; // @public export function setLogLevel(logLevel: LogLevel): void; @@ -302,19 +320,22 @@ export class Timestamp { export class Transaction { delete(documentRef: DocumentReference): this; get(documentRef: DocumentReference): Promise>; - set(documentRef: DocumentReference, data: T): this; - set(documentRef: DocumentReference, data: Partial, options: SetOptions): this; - update(documentRef: DocumentReference, data: UpdateData): this; + set(documentRef: DocumentReference, data: WithFieldValue): this; + set(documentRef: DocumentReference, data: NestedPartial, options: SetOptions): this; + update(documentRef: DocumentReference, data: UpdateData): this; update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): this; } // @public -export interface UpdateData { - [fieldPath: string]: any; -} +export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; + +// @public +export type UpdateData = T extends Primitive ? T : T extends Map ? Map, UpdateData> : T extends {} ? { + [K in keyof T]?: UpdateData | FieldValue; +} & NestedUpdateFields : Partial; // @public -export function updateDoc(reference: DocumentReference, data: UpdateData): Promise; +export function updateDoc(reference: DocumentReference, data: UpdateData): Promise; // @public export function updateDoc(reference: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise; @@ -325,13 +346,18 @@ export function where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value // @public export type WhereFilterOp = '<' | '<=' | '==' | '!=' | '>=' | '>' | 'array-contains' | 'in' | 'array-contains-any' | 'not-in'; +// @public +export type WithFieldValue = T extends Primitive ? T : T extends {} ? { + [K in keyof T]: WithFieldValue | FieldValue; +} : Partial; + // @public export class WriteBatch { commit(): Promise; delete(documentRef: DocumentReference): WriteBatch; - set(documentRef: DocumentReference, data: T): WriteBatch; - set(documentRef: DocumentReference, data: Partial, options: SetOptions): WriteBatch; - update(documentRef: DocumentReference, data: UpdateData): WriteBatch; + set(documentRef: DocumentReference, data: WithFieldValue): WriteBatch; + set(documentRef: DocumentReference, data: NestedPartial, options: SetOptions): WriteBatch; + update(documentRef: DocumentReference, data: UpdateData): WriteBatch; update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): WriteBatch; } diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 301c6c2f8fc..25e91450f5f 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -1,494 +1,520 @@ -## API Report File for "@firebase/firestore" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { EmulatorMockTokenOptions } from '@firebase/util'; -import { FirebaseApp } from '@firebase/app-exp'; -import { LogLevelString as LogLevel } from '@firebase/logger'; - -// @public -export function addDoc(reference: CollectionReference, data: T): Promise>; - -// @public -export function arrayRemove(...elements: unknown[]): FieldValue; - -// @public -export function arrayUnion(...elements: unknown[]): FieldValue; - -// @public -export class Bytes { - static fromBase64String(base64: string): Bytes; - static fromUint8Array(array: Uint8Array): Bytes; - isEqual(other: Bytes): boolean; - toBase64(): string; - toString(): string; - toUint8Array(): Uint8Array; -} - -// @public -export const CACHE_SIZE_UNLIMITED = -1; - -// @public -export function clearIndexedDbPersistence(firestore: Firestore): Promise; - -// @public -export function collection(firestore: Firestore, path: string, ...pathSegments: string[]): CollectionReference; - -// @public -export function collection(reference: CollectionReference, path: string, ...pathSegments: string[]): CollectionReference; - -// @public -export function collection(reference: DocumentReference, path: string, ...pathSegments: string[]): CollectionReference; - -// @public -export function collectionGroup(firestore: Firestore, collectionId: string): Query; - -// @public -export class CollectionReference extends Query { - get id(): string; - get parent(): DocumentReference | null; - get path(): string; - readonly type = "collection"; - withConverter(converter: FirestoreDataConverter): CollectionReference; - withConverter(converter: null): CollectionReference; -} - -// @public -export function connectFirestoreEmulator(firestore: Firestore, host: string, port: number, options?: { - mockUserToken?: EmulatorMockTokenOptions; -}): void; - -// @public -export function deleteDoc(reference: DocumentReference): Promise; - -// @public -export function deleteField(): FieldValue; - -// @public -export function disableNetwork(firestore: Firestore): Promise; - -// @public -export function doc(firestore: Firestore, path: string, ...pathSegments: string[]): DocumentReference; - -// @public -export function doc(reference: CollectionReference, path?: string, ...pathSegments: string[]): DocumentReference; - -// @public -export function doc(reference: DocumentReference, path: string, ...pathSegments: string[]): DocumentReference; - -// @public -export interface DocumentChange { - readonly doc: QueryDocumentSnapshot; - readonly newIndex: number; - readonly oldIndex: number; - readonly type: DocumentChangeType; -} - -// @public -export type DocumentChangeType = 'added' | 'removed' | 'modified'; - -// @public -export interface DocumentData { - [field: string]: any; -} - -// @public -export function documentId(): FieldPath; - -// @public -export class DocumentReference { - readonly converter: FirestoreDataConverter | null; - readonly firestore: Firestore; - get id(): string; - get parent(): CollectionReference; - get path(): string; - readonly type = "document"; - withConverter(converter: FirestoreDataConverter): DocumentReference; - withConverter(converter: null): DocumentReference; -} - -// @public -export class DocumentSnapshot { - protected constructor(); - data(options?: SnapshotOptions): T | undefined; - exists(): this is QueryDocumentSnapshot; - get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; - get id(): string; - readonly metadata: SnapshotMetadata; - get ref(): DocumentReference; -} - -// @public -export function enableIndexedDbPersistence(firestore: Firestore, persistenceSettings?: PersistenceSettings): Promise; - -// @public -export function enableMultiTabIndexedDbPersistence(firestore: Firestore): Promise; - -// @public -export function enableNetwork(firestore: Firestore): Promise; - -// @public -export function endAt(snapshot: DocumentSnapshot): QueryConstraint; - -// @public -export function endAt(...fieldValues: unknown[]): QueryConstraint; - -// @public -export function endBefore(snapshot: DocumentSnapshot): QueryConstraint; - -// @public -export function endBefore(...fieldValues: unknown[]): QueryConstraint; - -// @public -export class FieldPath { - constructor(...fieldNames: string[]); - isEqual(other: FieldPath): boolean; -} - -// @public -export abstract class FieldValue { - abstract isEqual(other: FieldValue): boolean; -} - -// @public -export class Firestore { - get app(): FirebaseApp; - toJSON(): object; - type: 'firestore-lite' | 'firestore'; -} - -// @public -export interface FirestoreDataConverter { - fromFirestore(snapshot: QueryDocumentSnapshot, options?: SnapshotOptions): T; - toFirestore(modelObject: T): DocumentData; - toFirestore(modelObject: Partial, options: SetOptions): DocumentData; -} - -// @public -export class FirestoreError extends Error { - readonly code: FirestoreErrorCode; - readonly message: string; - readonly name: string; - readonly stack?: string; -} - -// @public -export type FirestoreErrorCode = 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' | 'data-loss' | 'unauthenticated'; - -// @public -export interface FirestoreSettings { - cacheSizeBytes?: number; - experimentalAutoDetectLongPolling?: boolean; - experimentalForceLongPolling?: boolean; - host?: string; - ignoreUndefinedProperties?: boolean; - ssl?: boolean; -} - -// @public -export class GeoPoint { - constructor(latitude: number, longitude: number); - isEqual(other: GeoPoint): boolean; - get latitude(): number; - get longitude(): number; - toJSON(): { - latitude: number; - longitude: number; - }; -} - -// @public -export function getDoc(reference: DocumentReference): Promise>; - -// @public -export function getDocFromCache(reference: DocumentReference): Promise>; - -// @public -export function getDocFromServer(reference: DocumentReference): Promise>; - -// @public -export function getDocs(query: Query): Promise>; - -// @public -export function getDocsFromCache(query: Query): Promise>; - -// @public -export function getDocsFromServer(query: Query): Promise>; - -// @public -export function getFirestore(app?: FirebaseApp): Firestore; - -// @public -export function increment(n: number): FieldValue; - -// @public -export function initializeFirestore(app: FirebaseApp, settings: FirestoreSettings): Firestore; - -// @public -export function limit(limit: number): QueryConstraint; - -// @public -export function limitToLast(limit: number): QueryConstraint; - -// @public -export function loadBundle(firestore: Firestore, bundleData: ReadableStream | ArrayBuffer | string): LoadBundleTask; - -// @public -export class LoadBundleTask implements PromiseLike { - catch(onRejected: (a: Error) => R | PromiseLike): Promise; - onProgress(next?: (progress: LoadBundleTaskProgress) => unknown, error?: (err: Error) => unknown, complete?: () => void): void; - then(onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike, onRejected?: (a: Error) => R | PromiseLike): Promise; -} - -// @public -export interface LoadBundleTaskProgress { - bytesLoaded: number; - documentsLoaded: number; - taskState: TaskState; - totalBytes: number; - totalDocuments: number; -} - -export { LogLevel } - -// @public -export function namedQuery(firestore: Firestore, name: string): Promise; - -// @public -export function onSnapshot(reference: DocumentReference, observer: { - next?: (snapshot: DocumentSnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; -}): Unsubscribe; - -// @public -export function onSnapshot(reference: DocumentReference, options: SnapshotListenOptions, observer: { - next?: (snapshot: DocumentSnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; -}): Unsubscribe; - -// @public -export function onSnapshot(reference: DocumentReference, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; - -// @public -export function onSnapshot(reference: DocumentReference, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; - -// @public -export function onSnapshot(query: Query, observer: { - next?: (snapshot: QuerySnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; -}): Unsubscribe; - -// @public -export function onSnapshot(query: Query, options: SnapshotListenOptions, observer: { - next?: (snapshot: QuerySnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; -}): Unsubscribe; - -// @public -export function onSnapshot(query: Query, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; - -// @public -export function onSnapshot(query: Query, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; - -// @public -export function onSnapshotsInSync(firestore: Firestore, observer: { - next?: (value: void) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; -}): Unsubscribe; - -// @public -export function onSnapshotsInSync(firestore: Firestore, onSync: () => void): Unsubscribe; - -// @public -export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDirection): QueryConstraint; - -// @public -export type OrderByDirection = 'desc' | 'asc'; - -// @public -export interface PersistenceSettings { - forceOwnership?: boolean; -} - -// @public -export class Query { - protected constructor(); - readonly converter: FirestoreDataConverter | null; - readonly firestore: Firestore; - readonly type: 'query' | 'collection'; - withConverter(converter: null): Query; - withConverter(converter: FirestoreDataConverter): Query; -} - -// @public -export function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; - -// @public -export abstract class QueryConstraint { - abstract readonly type: QueryConstraintType; -} - -// @public -export type QueryConstraintType = 'where' | 'orderBy' | 'limit' | 'limitToLast' | 'startAt' | 'startAfter' | 'endAt' | 'endBefore'; - -// @public -export class QueryDocumentSnapshot extends DocumentSnapshot { - // @override - data(options?: SnapshotOptions): T; -} - -// @public -export function queryEqual(left: Query, right: Query): boolean; - -// @public -export class QuerySnapshot { - docChanges(options?: SnapshotListenOptions): Array>; - get docs(): Array>; - get empty(): boolean; - forEach(callback: (result: QueryDocumentSnapshot) => void, thisArg?: unknown): void; - readonly metadata: SnapshotMetadata; - readonly query: Query; - get size(): number; -} - -// @public -export function refEqual(left: DocumentReference | CollectionReference, right: DocumentReference | CollectionReference): boolean; - -// @public -export function runTransaction(firestore: Firestore, updateFunction: (transaction: Transaction) => Promise): Promise; - -// @public -export function serverTimestamp(): FieldValue; - -// @public -export function setDoc(reference: DocumentReference, data: T): Promise; - -// @public -export function setDoc(reference: DocumentReference, data: Partial, options: SetOptions): Promise; - -// @public -export function setLogLevel(logLevel: LogLevel): void; - -// @public -export type SetOptions = { - readonly merge?: boolean; -} | { - readonly mergeFields?: Array; -}; - -// @public -export function snapshotEqual(left: DocumentSnapshot | QuerySnapshot, right: DocumentSnapshot | QuerySnapshot): boolean; - -// @public -export interface SnapshotListenOptions { - readonly includeMetadataChanges?: boolean; -} - -// @public -export class SnapshotMetadata { - readonly fromCache: boolean; - readonly hasPendingWrites: boolean; - isEqual(other: SnapshotMetadata): boolean; -} - -// @public -export interface SnapshotOptions { - readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; -} - -// @public -export function startAfter(snapshot: DocumentSnapshot): QueryConstraint; - -// @public -export function startAfter(...fieldValues: unknown[]): QueryConstraint; - -// @public -export function startAt(snapshot: DocumentSnapshot): QueryConstraint; - -// @public -export function startAt(...fieldValues: unknown[]): QueryConstraint; - -// @public -export type TaskState = 'Error' | 'Running' | 'Success'; - -// @public -export function terminate(firestore: Firestore): Promise; - -// @public -export class Timestamp { - constructor( - seconds: number, - nanoseconds: number); - static fromDate(date: Date): Timestamp; - static fromMillis(milliseconds: number): Timestamp; - isEqual(other: Timestamp): boolean; - readonly nanoseconds: number; - static now(): Timestamp; - readonly seconds: number; - toDate(): Date; - toJSON(): { - seconds: number; - nanoseconds: number; - }; - toMillis(): number; - toString(): string; - valueOf(): string; -} - -// @public -export class Transaction { - delete(documentRef: DocumentReference): this; - get(documentRef: DocumentReference): Promise>; - set(documentRef: DocumentReference, data: T): this; - set(documentRef: DocumentReference, data: Partial, options: SetOptions): this; - update(documentRef: DocumentReference, data: UpdateData): this; - update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): this; -} - -// @public -export interface Unsubscribe { - (): void; -} - -// @public -export interface UpdateData { - [fieldPath: string]: any; -} - -// @public -export function updateDoc(reference: DocumentReference, data: UpdateData): Promise; - -// @public -export function updateDoc(reference: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise; - -// @public -export function waitForPendingWrites(firestore: Firestore): Promise; - -// @public -export function where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value: unknown): QueryConstraint; - -// @public -export type WhereFilterOp = '<' | '<=' | '==' | '!=' | '>=' | '>' | 'array-contains' | 'in' | 'array-contains-any' | 'not-in'; - -// @public -export class WriteBatch { - commit(): Promise; - delete(documentRef: DocumentReference): WriteBatch; - set(documentRef: DocumentReference, data: T): WriteBatch; - set(documentRef: DocumentReference, data: Partial, options: SetOptions): WriteBatch; - update(documentRef: DocumentReference, data: UpdateData): WriteBatch; - update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): WriteBatch; -} - -// @public -export function writeBatch(firestore: Firestore): WriteBatch; - - -``` +## API Report File for "@firebase/firestore" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { EmulatorMockTokenOptions } from '@firebase/util'; +import { FirebaseApp } from '@firebase/app-exp'; +import { LogLevelString as LogLevel } from '@firebase/logger'; + +// @public +export function addDoc(reference: CollectionReference, data: WithFieldValue): Promise>; + +// @public +export type AddPrefixToKeys> = { + [K in keyof T & string as `${Prefix}.${K}`]+?: T[K]; +}; + +// @public +export function arrayRemove(...elements: unknown[]): FieldValue; + +// @public +export function arrayUnion(...elements: unknown[]): FieldValue; + +// @public +export class Bytes { + static fromBase64String(base64: string): Bytes; + static fromUint8Array(array: Uint8Array): Bytes; + isEqual(other: Bytes): boolean; + toBase64(): string; + toString(): string; + toUint8Array(): Uint8Array; +} + +// @public +export const CACHE_SIZE_UNLIMITED = -1; + +// @public +export function clearIndexedDbPersistence(firestore: Firestore): Promise; + +// @public +export function collection(firestore: Firestore, path: string, ...pathSegments: string[]): CollectionReference; + +// @public +export function collection(reference: CollectionReference, path: string, ...pathSegments: string[]): CollectionReference; + +// @public +export function collection(reference: DocumentReference, path: string, ...pathSegments: string[]): CollectionReference; + +// @public +export function collectionGroup(firestore: Firestore, collectionId: string): Query; + +// @public +export class CollectionReference extends Query { + get id(): string; + get parent(): DocumentReference | null; + get path(): string; + readonly type = "collection"; + withConverter(converter: FirestoreDataConverter): CollectionReference; + withConverter(converter: null): CollectionReference; +} + +// @public +export function connectFirestoreEmulator(firestore: Firestore, host: string, port: number, options?: { + mockUserToken?: EmulatorMockTokenOptions; +}): void; + +// @public +export function deleteDoc(reference: DocumentReference): Promise; + +// @public +export function deleteField(): FieldValue; + +// @public +export function disableNetwork(firestore: Firestore): Promise; + +// @public +export function doc(firestore: Firestore, path: string, ...pathSegments: string[]): DocumentReference; + +// @public +export function doc(reference: CollectionReference, path?: string, ...pathSegments: string[]): DocumentReference; + +// @public +export function doc(reference: DocumentReference, path: string, ...pathSegments: string[]): DocumentReference; + +// @public +export interface DocumentChange { + readonly doc: QueryDocumentSnapshot; + readonly newIndex: number; + readonly oldIndex: number; + readonly type: DocumentChangeType; +} + +// @public +export type DocumentChangeType = 'added' | 'removed' | 'modified'; + +// @public +export interface DocumentData { + [field: string]: any; +} + +// @public +export function documentId(): FieldPath; + +// @public +export class DocumentReference { + readonly converter: FirestoreDataConverter | null; + readonly firestore: Firestore; + get id(): string; + get parent(): CollectionReference; + get path(): string; + readonly type = "document"; + withConverter(converter: FirestoreDataConverter): DocumentReference; + withConverter(converter: null): DocumentReference; +} + +// @public +export class DocumentSnapshot { + protected constructor(); + data(options?: SnapshotOptions): T | undefined; + exists(): this is QueryDocumentSnapshot; + get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; + get id(): string; + readonly metadata: SnapshotMetadata; + get ref(): DocumentReference; +} + +// @public +export function enableIndexedDbPersistence(firestore: Firestore, persistenceSettings?: PersistenceSettings): Promise; + +// @public +export function enableMultiTabIndexedDbPersistence(firestore: Firestore): Promise; + +// @public +export function enableNetwork(firestore: Firestore): Promise; + +// @public +export function endAt(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function endAt(...fieldValues: unknown[]): QueryConstraint; + +// @public +export function endBefore(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function endBefore(...fieldValues: unknown[]): QueryConstraint; + +// @public +export class FieldPath { + constructor(...fieldNames: string[]); + isEqual(other: FieldPath): boolean; +} + +// @public +export abstract class FieldValue { + abstract isEqual(other: FieldValue): boolean; +} + +// @public +export class Firestore { + get app(): FirebaseApp; + toJSON(): object; + type: 'firestore-lite' | 'firestore'; +} + +// @public +export interface FirestoreDataConverter { + fromFirestore(snapshot: QueryDocumentSnapshot, options?: SnapshotOptions): T; + toFirestore(modelObject: WithFieldValue): DocumentData; + toFirestore(modelObject: NestedPartial, options: SetOptions): DocumentData; +} + +// @public +export class FirestoreError extends Error { + readonly code: FirestoreErrorCode; + readonly message: string; + readonly name: string; + readonly stack?: string; +} + +// @public +export type FirestoreErrorCode = 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' | 'data-loss' | 'unauthenticated'; + +// @public +export interface FirestoreSettings { + cacheSizeBytes?: number; + experimentalAutoDetectLongPolling?: boolean; + experimentalForceLongPolling?: boolean; + host?: string; + ignoreUndefinedProperties?: boolean; + ssl?: boolean; +} + +// @public +export class GeoPoint { + constructor(latitude: number, longitude: number); + isEqual(other: GeoPoint): boolean; + get latitude(): number; + get longitude(): number; + toJSON(): { + latitude: number; + longitude: number; + }; +} + +// @public +export function getDoc(reference: DocumentReference): Promise>; + +// @public +export function getDocFromCache(reference: DocumentReference): Promise>; + +// @public +export function getDocFromServer(reference: DocumentReference): Promise>; + +// @public +export function getDocs(query: Query): Promise>; + +// @public +export function getDocsFromCache(query: Query): Promise>; + +// @public +export function getDocsFromServer(query: Query): Promise>; + +// @public +export function getFirestore(app?: FirebaseApp): Firestore; + +// @public +export function increment(n: number): FieldValue; + +// @public +export function initializeFirestore(app: FirebaseApp, settings: FirestoreSettings): Firestore; + +// @public +export function limit(limit: number): QueryConstraint; + +// @public +export function limitToLast(limit: number): QueryConstraint; + +// @public +export function loadBundle(firestore: Firestore, bundleData: ReadableStream | ArrayBuffer | string): LoadBundleTask; + +// @public +export class LoadBundleTask implements PromiseLike { + catch(onRejected: (a: Error) => R | PromiseLike): Promise; + onProgress(next?: (progress: LoadBundleTaskProgress) => unknown, error?: (err: Error) => unknown, complete?: () => void): void; + then(onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike, onRejected?: (a: Error) => R | PromiseLike): Promise; +} + +// @public +export interface LoadBundleTaskProgress { + bytesLoaded: number; + documentsLoaded: number; + taskState: TaskState; + totalBytes: number; + totalDocuments: number; +} + +export { LogLevel } + +// @public +export function namedQuery(firestore: Firestore, name: string): Promise; + +// @public +export type NestedPartial = T extends Primitive ? T : T extends Map ? Map, NestedPartial> : T extends {} ? { + [K in keyof T]?: NestedPartial | FieldValue; +} : Partial; + +// @public +export type NestedUpdateFields> = UnionToIntersection<{ + [K in keyof T & string]: T[K] extends Record ? AddPrefixToKeys> : never; +}[keyof T & string]>; + +// @public +export function onSnapshot(reference: DocumentReference, observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(reference: DocumentReference, options: SnapshotListenOptions, observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(reference: DocumentReference, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshot(reference: DocumentReference, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshot(query: Query, observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(query: Query, options: SnapshotListenOptions, observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(query: Query, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshot(query: Query, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshotsInSync(firestore: Firestore, observer: { + next?: (value: void) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshotsInSync(firestore: Firestore, onSync: () => void): Unsubscribe; + +// @public +export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDirection): QueryConstraint; + +// @public +export type OrderByDirection = 'desc' | 'asc'; + +// @public +export interface PersistenceSettings { + forceOwnership?: boolean; +} + +// @public (undocumented) +export type Primitive = string | number | boolean | bigint | undefined | null; + +// @public +export class Query { + protected constructor(); + readonly converter: FirestoreDataConverter | null; + readonly firestore: Firestore; + readonly type: 'query' | 'collection'; + withConverter(converter: null): Query; + withConverter(converter: FirestoreDataConverter): Query; +} + +// @public +export function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; + +// @public +export abstract class QueryConstraint { + abstract readonly type: QueryConstraintType; +} + +// @public +export type QueryConstraintType = 'where' | 'orderBy' | 'limit' | 'limitToLast' | 'startAt' | 'startAfter' | 'endAt' | 'endBefore'; + +// @public +export class QueryDocumentSnapshot extends DocumentSnapshot { + // @override + data(options?: SnapshotOptions): T; +} + +// @public +export function queryEqual(left: Query, right: Query): boolean; + +// @public +export class QuerySnapshot { + docChanges(options?: SnapshotListenOptions): Array>; + get docs(): Array>; + get empty(): boolean; + forEach(callback: (result: QueryDocumentSnapshot) => void, thisArg?: unknown): void; + readonly metadata: SnapshotMetadata; + readonly query: Query; + get size(): number; +} + +// @public +export function refEqual(left: DocumentReference | CollectionReference, right: DocumentReference | CollectionReference): boolean; + +// @public +export function runTransaction(firestore: Firestore, updateFunction: (transaction: Transaction) => Promise): Promise; + +// @public +export function serverTimestamp(): FieldValue; + +// @public +export function setDoc(reference: DocumentReference, data: WithFieldValue): Promise; + +// @public +export function setDoc(reference: DocumentReference, data: NestedPartial, options: SetOptions): Promise; + +// @public +export function setLogLevel(logLevel: LogLevel): void; + +// @public +export type SetOptions = { + readonly merge?: boolean; +} | { + readonly mergeFields?: Array; +}; + +// @public +export function snapshotEqual(left: DocumentSnapshot | QuerySnapshot, right: DocumentSnapshot | QuerySnapshot): boolean; + +// @public +export interface SnapshotListenOptions { + readonly includeMetadataChanges?: boolean; +} + +// @public +export class SnapshotMetadata { + readonly fromCache: boolean; + readonly hasPendingWrites: boolean; + isEqual(other: SnapshotMetadata): boolean; +} + +// @public +export interface SnapshotOptions { + readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; +} + +// @public +export function startAfter(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function startAfter(...fieldValues: unknown[]): QueryConstraint; + +// @public +export function startAt(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function startAt(...fieldValues: unknown[]): QueryConstraint; + +// @public +export type TaskState = 'Error' | 'Running' | 'Success'; + +// @public +export function terminate(firestore: Firestore): Promise; + +// @public +export class Timestamp { + constructor( + seconds: number, + nanoseconds: number); + static fromDate(date: Date): Timestamp; + static fromMillis(milliseconds: number): Timestamp; + isEqual(other: Timestamp): boolean; + readonly nanoseconds: number; + static now(): Timestamp; + readonly seconds: number; + toDate(): Date; + toJSON(): { + seconds: number; + nanoseconds: number; + }; + toMillis(): number; + toString(): string; + valueOf(): string; +} + +// @public +export class Transaction { + delete(documentRef: DocumentReference): this; + get(documentRef: DocumentReference): Promise>; + set(documentRef: DocumentReference, data: WithFieldValue): this; + set(documentRef: DocumentReference, data: NestedPartial, options: SetOptions): this; + update(documentRef: DocumentReference, data: UpdateData): this; + update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): this; +} + +// @public +export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; + +// @public +export interface Unsubscribe { + (): void; +} + +// @public +export type UpdateData = T extends Primitive ? T : T extends Map ? Map, UpdateData> : T extends {} ? { + [K in keyof T]?: UpdateData | FieldValue; +} & NestedUpdateFields : Partial; + +// @public +export function updateDoc(reference: DocumentReference, data: UpdateData): Promise; + +// @public +export function updateDoc(reference: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise; + +// @public +export function waitForPendingWrites(firestore: Firestore): Promise; + +// @public +export function where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value: unknown): QueryConstraint; + +// @public +export type WhereFilterOp = '<' | '<=' | '==' | '!=' | '>=' | '>' | 'array-contains' | 'in' | 'array-contains-any' | 'not-in'; + +// @public +export type WithFieldValue = T extends Primitive ? T : T extends {} ? { + [K in keyof T]: WithFieldValue | FieldValue; +} : Partial; + +// @public +export class WriteBatch { + commit(): Promise; + delete(documentRef: DocumentReference): WriteBatch; + set(documentRef: DocumentReference, data: WithFieldValue): WriteBatch; + set(documentRef: DocumentReference, data: NestedPartial, options: SetOptions): WriteBatch; + update(documentRef: DocumentReference, data: UpdateData): WriteBatch; + update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): WriteBatch; +} + +// @public +export function writeBatch(firestore: Firestore): WriteBatch; + + +``` diff --git a/packages/firestore/compat/config.ts b/packages/firestore/compat/config.ts index a33ed09181f..295ecdc8ecc 100644 --- a/packages/firestore/compat/config.ts +++ b/packages/firestore/compat/config.ts @@ -22,7 +22,7 @@ import { _FirebaseNamespace } from '@firebase/app-types/private'; import { Component, ComponentType } from '@firebase/component'; import { - FirebaseFirestore, + Firestore as FirebaseFirestore, CACHE_SIZE_UNLIMITED, GeoPoint, Timestamp diff --git a/packages/firestore/exp/api.ts b/packages/firestore/exp/api.ts index a65e3d2997a..5b40554c73c 100644 --- a/packages/firestore/exp/api.ts +++ b/packages/firestore/exp/api.ts @@ -64,8 +64,12 @@ export { SetOptions, DocumentData, UpdateData, + Primitive, WithFieldValue, + NestedUpdateFields, + AddPrefixToKeys, NestedPartial, + UnionToIntersection, refEqual, queryEqual } from '../src/exp/reference'; diff --git a/packages/firestore/lite/index.ts b/packages/firestore/lite/index.ts index e705149235d..87ba465647c 100644 --- a/packages/firestore/lite/index.ts +++ b/packages/firestore/lite/index.ts @@ -38,9 +38,15 @@ export { } from '../src/lite/database'; export { - SetOptions, DocumentData, TypedUpdateData as UpdateData, + Primitive, + WithFieldValue, + NestedUpdateFields, + AddPrefixToKeys, + NestedPartial, + UnionToIntersection, + SetOptions, DocumentReference, Query, CollectionReference, diff --git a/packages/firestore/src/exp/reference.ts b/packages/firestore/src/exp/reference.ts index fabd40be52b..87c684372c2 100644 --- a/packages/firestore/src/exp/reference.ts +++ b/packages/firestore/src/exp/reference.ts @@ -26,7 +26,11 @@ export { SetOptions, DocumentData, TypedUpdateData as UpdateData, - NestedPartial, + Primitive, WithFieldValue, + NestedUpdateFields, + AddPrefixToKeys, + NestedPartial, + UnionToIntersection, refEqual } from '../lite/reference'; diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite/reference.ts index 719d833fc21..dc70dce6d3d 100644 --- a/packages/firestore/src/lite/reference.ts +++ b/packages/firestore/src/lite/reference.ts @@ -49,7 +49,7 @@ export interface DocumentData { [field: string]: any; } -type Primitive = string | number | boolean | bigint | undefined | null; +export type Primitive = string | number | boolean | bigint | undefined | null; /** * Similar to Typescript's `Partial`, but allows nested fields to be @@ -94,18 +94,19 @@ export type TypedUpdateData = T extends Primitive */ // Mapping between a field and its value. // eslint-disable-next-line @typescript-eslint/no-explicit-any -type NestedUpdateFields> = UnionToIntersection< - { - // Check that T[K] extends Record to only allow nesting for map values. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [K in keyof T & string]: T[K] extends Record - ? // Recurse into the map and add the prefix in front of each key - // (e.g. Prefix 'bar.' to create: 'bar.baz' and 'bar.qux'. - AddPrefixToKeys> - : // TypedUpdateData is always a map of values. - never; - }[keyof T & string] // Also include the generated prefix-string keys. ->; +export type NestedUpdateFields> = + UnionToIntersection< + { + // Check that T[K] extends Record to only allow nesting for map values. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [K in keyof T & string]: T[K] extends Record + ? // Recurse into the map and add the prefix in front of each key + // (e.g. Prefix 'bar.' to create: 'bar.baz' and 'bar.qux'. + AddPrefixToKeys> + : // TypedUpdateData is always a map of values. + never; + }[keyof T & string] // Also include the generated prefix-string keys. + >; /** * Returns a new map where every key is prefixed with the outer key appended @@ -113,7 +114,10 @@ type NestedUpdateFields> = UnionToIntersection< */ // Mapping between a field and its value. // eslint-disable-next-line @typescript-eslint/no-explicit-any -type AddPrefixToKeys> = +export type AddPrefixToKeys< + Prefix extends string, + T extends Record +> = // Remap K => Prefix.K. See https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#key-remapping-via-as { [K in keyof T & string as `${Prefix}.${K}`]+?: T[K] }; @@ -128,9 +132,9 @@ type AddPrefixToKeys> = * https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( - k: infer I -) => void +export type UnionToIntersection = ( + U extends any ? (k: U) => void : never +) extends (k: infer I) => void ? I : never; From c5bb26f4c317ecd64f7f5312781958fcd095c7ca Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Wed, 11 Aug 2021 16:01:52 -0500 Subject: [PATCH 08/22] move ts-lint --- packages/firestore/src/lite/reference.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite/reference.ts index dc70dce6d3d..4cda0b29309 100644 --- a/packages/firestore/src/lite/reference.ts +++ b/packages/firestore/src/lite/reference.ts @@ -113,9 +113,9 @@ export type NestedUpdateFields> = * to a dot. */ // Mapping between a field and its value. -// eslint-disable-next-line @typescript-eslint/no-explicit-any export type AddPrefixToKeys< Prefix extends string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any T extends Record > = // Remap K => Prefix.K. See https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#key-remapping-via-as @@ -131,9 +131,11 @@ export type AddPrefixToKeys< * https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-inference-in-conditional-types * https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any export type UnionToIntersection = ( - U extends any ? (k: U) => void : never + // eslint-disable-next-line @typescript-eslint/no-explicit-any + U extends any + ? (k: U) => void + : never ) extends (k: infer I) => void ? I : never; From c2e0e38dce849c7912c5c56262eace6ecebed5b1 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Thu, 12 Aug 2021 11:13:24 -0500 Subject: [PATCH 09/22] add comments and skip test --- packages/firestore/src/lite/reference.ts | 3 ++- packages/firestore/test/lite/integration.test.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite/reference.ts index 4cda0b29309..482ece63e1d 100644 --- a/packages/firestore/src/lite/reference.ts +++ b/packages/firestore/src/lite/reference.ts @@ -49,7 +49,8 @@ export interface DocumentData { [field: string]: any; } -export type Primitive = string | number | boolean | bigint | undefined | null; +/** Primitive types. */ +export type Primitive = string | number | boolean | undefined | null; /** * Similar to Typescript's `Partial`, but allows nested fields to be diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 6e18fe8f1c1..16095e73999 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -600,7 +600,8 @@ function genericMutationTests( }); }); - it('temporary sanity check tests', async () => { + // Skip test. This is just here to just make sure things compile. + it.skip('temporary sanity check tests', async () => { class TestObject { constructor( readonly outerString: string, From 479b6bbc0bf49187cbf32534c0f326119e4904a1 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Thu, 12 Aug 2021 14:51:16 -0700 Subject: [PATCH 10/22] trying fei's rollup hack --- common/api-review/firestore-lite.api.md | 4 ++-- common/api-review/firestore.api.md | 4 ++-- packages/firestore/rollup.shared.js | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 5933498439b..ee4692168c7 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -203,8 +203,8 @@ export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDir // @public export type OrderByDirection = 'desc' | 'asc'; -// @public (undocumented) -export type Primitive = string | number | boolean | bigint | undefined | null; +// @public +export type Primitive = string | number | boolean | undefined | null; // @public export class Query { diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 25e91450f5f..b312f989063 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -332,8 +332,8 @@ export interface PersistenceSettings { forceOwnership?: boolean; } -// @public (undocumented) -export type Primitive = string | number | boolean | bigint | undefined | null; +// @public +export type Primitive = string | number | boolean | undefined | null; // @public export class Query { diff --git a/packages/firestore/rollup.shared.js b/packages/firestore/rollup.shared.js index 36cdff5206a..5c06866e0ff 100644 --- a/packages/firestore/rollup.shared.js +++ b/packages/firestore/rollup.shared.js @@ -135,11 +135,11 @@ exports.importTransformer = importTransformer; */ const removeAssertAndPrefixInternalTransformer = service => ({ before: [ - removeAsserts(service.getProgram()), - renameInternals(service.getProgram(), { - publicIdentifiers, - prefix: '__PRIVATE_' - }) + removeAsserts(service.getProgram()) + // renameInternals(service.getProgram(), { + // publicIdentifiers, + // prefix: '__PRIVATE_' + // }) ], after: [] }); From 1ffaad286231000916ed102f8388871d0cca3559 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Thu, 12 Aug 2021 15:16:39 -0700 Subject: [PATCH 11/22] allow skip --- packages/firestore/test/lite/integration.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 16095e73999..4162e986cae 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -601,6 +601,7 @@ function genericMutationTests( }); // Skip test. This is just here to just make sure things compile. + // eslint-disable-next-line no-restricted-properties it.skip('temporary sanity check tests', async () => { class TestObject { constructor( From b0568bbc69e6332ccc3f544f87b46adbde2194e7 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Mon, 16 Aug 2021 11:13:42 -0700 Subject: [PATCH 12/22] revert rollup changes --- .../src/core/providers/facebook.test.ts | 6 +- .../src/core/providers/github.test.ts | 6 +- .../src/core/providers/google.test.ts | 6 +- .../auth-exp/src/core/providers/oauth.test.ts | 6 +- .../src/core/providers/twitter.test.ts | 6 +- .../abstract_popup_redirect_operation.test.ts | 2 +- .../src/core/strategies/credential.test.ts | 6 +- .../src/core/strategies/custom_token.test.ts | 13 ++-- .../strategies/email_and_password.test.ts | 30 +++---- .../auth-exp/src/core/strategies/idp.test.ts | 6 +- .../src/core/strategies/redirect.test.ts | 17 ++-- .../core/user/additional_user_info.test.ts | 78 +++++-------------- .../src/core/user/reauthenticate.test.ts | 10 +-- .../auth-exp/src/core/user/reload.test.ts | 3 +- .../auth-exp/src/model/public_types.ts | 4 +- .../platform_browser/popup_redirect.test.ts | 16 ++-- .../recaptcha/recaptcha_verifier.test.ts | 2 +- .../strategies/redirect.test.ts | 52 +++++-------- packages/firebase/index.d.ts | 10 ++- packages/firestore/rollup.shared.js | 10 +-- packages/firestore/src/lite/reference.ts | 3 +- packages/firestore/src/lite/write_batch.ts | 5 +- 22 files changed, 104 insertions(+), 193 deletions(-) diff --git a/packages-exp/auth-exp/src/core/providers/facebook.test.ts b/packages-exp/auth-exp/src/core/providers/facebook.test.ts index 7b6ba70cbbf..7f71d04cc94 100644 --- a/packages-exp/auth-exp/src/core/providers/facebook.test.ts +++ b/packages-exp/auth-exp/src/core/providers/facebook.test.ts @@ -17,11 +17,7 @@ import { expect } from 'chai'; -import { - OperationType, - ProviderId, - SignInMethod -} from '../../model/enums'; +import { OperationType, ProviderId, SignInMethod } from '../../model/enums'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; diff --git a/packages-exp/auth-exp/src/core/providers/github.test.ts b/packages-exp/auth-exp/src/core/providers/github.test.ts index 56d8bfcf64c..9e7d0d73de8 100644 --- a/packages-exp/auth-exp/src/core/providers/github.test.ts +++ b/packages-exp/auth-exp/src/core/providers/github.test.ts @@ -17,11 +17,7 @@ import { expect } from 'chai'; -import { - OperationType, - ProviderId, - SignInMethod -} from '../../model/enums'; +import { OperationType, ProviderId, SignInMethod } from '../../model/enums'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; diff --git a/packages-exp/auth-exp/src/core/providers/google.test.ts b/packages-exp/auth-exp/src/core/providers/google.test.ts index 544860711f6..d600cb2c17c 100644 --- a/packages-exp/auth-exp/src/core/providers/google.test.ts +++ b/packages-exp/auth-exp/src/core/providers/google.test.ts @@ -17,11 +17,7 @@ import { expect } from 'chai'; -import { - OperationType, - ProviderId, - SignInMethod -} from '../../model/enums'; +import { OperationType, ProviderId, SignInMethod } from '../../model/enums'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; diff --git a/packages-exp/auth-exp/src/core/providers/oauth.test.ts b/packages-exp/auth-exp/src/core/providers/oauth.test.ts index 5c831e8a5fc..c9f50b3866f 100644 --- a/packages-exp/auth-exp/src/core/providers/oauth.test.ts +++ b/packages-exp/auth-exp/src/core/providers/oauth.test.ts @@ -17,11 +17,7 @@ import { expect } from 'chai'; -import { - OperationType, - ProviderId, - SignInMethod -} from '../../model/enums'; +import { OperationType, ProviderId, SignInMethod } from '../../model/enums'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; diff --git a/packages-exp/auth-exp/src/core/providers/twitter.test.ts b/packages-exp/auth-exp/src/core/providers/twitter.test.ts index 31d1f382cf3..b1d4a05fcd3 100644 --- a/packages-exp/auth-exp/src/core/providers/twitter.test.ts +++ b/packages-exp/auth-exp/src/core/providers/twitter.test.ts @@ -34,11 +34,7 @@ import { expect } from 'chai'; -import { - OperationType, - ProviderId, - SignInMethod -} from '../../model/enums'; +import { OperationType, ProviderId, SignInMethod } from '../../model/enums'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; diff --git a/packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.test.ts b/packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.test.ts index ba818c0b9dd..f3500130a2a 100644 --- a/packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.test.ts @@ -175,7 +175,7 @@ describe('core/strategies/abstract_popup_redirect_operation', () => { context('idp tasks', () => { function updateFilter(type: AuthEventType): void { - ((operation as unknown) as Record).filter = type; + (operation as unknown as Record).filter = type; } function expectedIdpTaskParams(): idp.IdpTaskParams { diff --git a/packages-exp/auth-exp/src/core/strategies/credential.test.ts b/packages-exp/auth-exp/src/core/strategies/credential.test.ts index c0604f69d9a..654004c0a13 100644 --- a/packages-exp/auth-exp/src/core/strategies/credential.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/credential.test.ts @@ -19,11 +19,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { stub } from 'sinon'; -import { - OperationType, - ProviderId, - SignInMethod -} from '../../model/enums'; +import { OperationType, ProviderId, SignInMethod } from '../../model/enums'; import { FirebaseError } from '@firebase/util'; import { mockEndpoint } from '../../../test/helpers/api/helper'; diff --git a/packages-exp/auth-exp/src/core/strategies/custom_token.test.ts b/packages-exp/auth-exp/src/core/strategies/custom_token.test.ts index 5c046ec4d5c..cf83673a714 100644 --- a/packages-exp/auth-exp/src/core/strategies/custom_token.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/custom_token.test.ts @@ -68,14 +68,11 @@ describe('core/strategies/signInWithCustomToken', () => { afterEach(mockFetch.tearDown); it('should return a valid user credential', async () => { - const { - user, - operationType, - _tokenResponse - } = (await signInWithCustomToken( - auth, - 'look-at-me-im-a-jwt' - )) as UserCredentialInternal; + const { user, operationType, _tokenResponse } = + (await signInWithCustomToken( + auth, + 'look-at-me-im-a-jwt' + )) as UserCredentialInternal; expect(_tokenResponse).to.eql(idTokenResponse); expect(user.uid).to.eq('local-id'); expect(user.displayName).to.eq('display-name'); diff --git a/packages-exp/auth-exp/src/core/strategies/email_and_password.test.ts b/packages-exp/auth-exp/src/core/strategies/email_and_password.test.ts index af941c31760..75440f6067c 100644 --- a/packages-exp/auth-exp/src/core/strategies/email_and_password.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/email_and_password.test.ts @@ -384,15 +384,12 @@ describe('core/strategies/email_and_password/createUserWithEmailAndPassword', () afterEach(mockFetch.tearDown); it('should sign in the user', async () => { - const { - _tokenResponse, - user, - operationType - } = (await createUserWithEmailAndPassword( - auth, - 'some-email', - 'some-password' - )) as UserCredentialInternal; + const { _tokenResponse, user, operationType } = + (await createUserWithEmailAndPassword( + auth, + 'some-email', + 'some-password' + )) as UserCredentialInternal; expect(_tokenResponse).to.eql({ idToken: 'id-token', refreshToken: 'refresh-token', @@ -427,15 +424,12 @@ describe('core/strategies/email_and_password/signInWithEmailAndPassword', () => afterEach(mockFetch.tearDown); it('should sign in the user', async () => { - const { - _tokenResponse, - user, - operationType - } = (await signInWithEmailAndPassword( - auth, - 'some-email', - 'some-password' - )) as UserCredentialInternal; + const { _tokenResponse, user, operationType } = + (await signInWithEmailAndPassword( + auth, + 'some-email', + 'some-password' + )) as UserCredentialInternal; expect(_tokenResponse).to.eql({ idToken: 'id-token', refreshToken: 'refresh-token', diff --git a/packages-exp/auth-exp/src/core/strategies/idp.test.ts b/packages-exp/auth-exp/src/core/strategies/idp.test.ts index 0fcef68c246..d918bbcf724 100644 --- a/packages-exp/auth-exp/src/core/strategies/idp.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/idp.test.ts @@ -104,7 +104,7 @@ describe('core/strategies/idb', () => { it('passes through the bypassAuthState flag', async () => { const stub = sinon .stub(credential, '_signInWithCredential') - .returns(Promise.resolve(({} as unknown) as UserCredentialImpl)); + .returns(Promise.resolve({} as unknown as UserCredentialImpl)); await idpTasks._signIn({ auth, user, @@ -160,7 +160,7 @@ describe('core/strategies/idb', () => { it('passes through the bypassAuthState flag', async () => { const stub = sinon .stub(reauthenticate, '_reauthenticate') - .returns(Promise.resolve(({} as unknown) as UserCredentialImpl)); + .returns(Promise.resolve({} as unknown as UserCredentialImpl)); await idpTasks._reauth({ auth, user, @@ -218,7 +218,7 @@ describe('core/strategies/idb', () => { it('passes through the bypassAuthState flag', async () => { const stub = sinon .stub(linkUnlink, '_link') - .returns(Promise.resolve(({} as unknown) as UserCredentialImpl)); + .returns(Promise.resolve({} as unknown as UserCredentialImpl)); await idpTasks._link({ auth, user, diff --git a/packages-exp/auth-exp/src/core/strategies/redirect.test.ts b/packages-exp/auth-exp/src/core/strategies/redirect.test.ts index 0b94ae8bfd0..b79d19eb3b6 100644 --- a/packages-exp/auth-exp/src/core/strategies/redirect.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/redirect.test.ts @@ -15,10 +15,7 @@ * limitations under the License. */ -import { - AuthError, - PopupRedirectResolver -} from '../../model/public_types'; +import { AuthError, PopupRedirectResolver } from '../../model/public_types'; import { OperationType, ProviderId } from '../../model/enums'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; @@ -59,12 +56,11 @@ describe('core/strategies/redirect', () => { let redirectPersistence: RedirectPersistence; beforeEach(async () => { - eventManager = new AuthEventManager(({} as unknown) as TestAuth); + eventManager = new AuthEventManager({} as unknown as TestAuth); idpStubs = sinon.stub(idpTasks); resolver = makeMockPopupRedirectResolver(eventManager); - _getInstance( - resolver - )._redirectPersistence = RedirectPersistence; + _getInstance(resolver)._redirectPersistence = + RedirectPersistence; auth = await testAuth(); redirectAction = new RedirectAction(auth, _getInstance(resolver), false); redirectPersistence = _getInstance(RedirectPersistence); @@ -197,9 +193,8 @@ describe('core/strategies/redirect', () => { it('bypasses initialization if no key set', async () => { await reInitAuthWithRedirectUser(MATCHING_EVENT_ID); - const resolverInstance = _getInstance( - resolver - ); + const resolverInstance = + _getInstance(resolver); sinon.spy(resolverInstance, '_initialize'); redirectPersistence.hasPendingRedirect = false; diff --git a/packages-exp/auth-exp/src/core/user/additional_user_info.test.ts b/packages-exp/auth-exp/src/core/user/additional_user_info.test.ts index 3e89ecefacb..8e701c3cb45 100644 --- a/packages-exp/auth-exp/src/core/user/additional_user_info.test.ts +++ b/packages-exp/auth-exp/src/core/user/additional_user_info.test.ts @@ -47,12 +47,8 @@ describe('core/user/additional_user_info', () => { providerId: ProviderId.FACEBOOK, rawUserInfo: rawUserInfoWithLogin }); - const { - isNewUser, - providerId, - username, - profile - } = _fromIdTokenResponse(idResponse)!; + const { isNewUser, providerId, username, profile } = + _fromIdTokenResponse(idResponse)!; expect(isNewUser).to.be.false; expect(providerId).to.eq(ProviderId.FACEBOOK); expect(username).to.be.undefined; @@ -64,12 +60,8 @@ describe('core/user/additional_user_info', () => { providerId: ProviderId.GITHUB, rawUserInfo: rawUserInfoWithLogin }); - const { - isNewUser, - providerId, - username, - profile - } = _fromIdTokenResponse(idResponse)!; + const { isNewUser, providerId, username, profile } = + _fromIdTokenResponse(idResponse)!; expect(isNewUser).to.be.false; expect(providerId).to.eq(ProviderId.GITHUB); expect(username).to.eq('scott'); @@ -81,12 +73,8 @@ describe('core/user/additional_user_info', () => { providerId: ProviderId.GOOGLE, rawUserInfo: rawUserInfoWithLogin }); - const { - isNewUser, - providerId, - username, - profile - } = _fromIdTokenResponse(idResponse)!; + const { isNewUser, providerId, username, profile } = + _fromIdTokenResponse(idResponse)!; expect(isNewUser).to.be.false; expect(providerId).to.eq(ProviderId.GOOGLE); expect(username).to.be.undefined; @@ -99,12 +87,8 @@ describe('core/user/additional_user_info', () => { rawUserInfo: rawUserInfoNoLogin, screenName: 'scott' }); - const { - isNewUser, - providerId, - username, - profile - } = _fromIdTokenResponse(idResponse)!; + const { isNewUser, providerId, username, profile } = + _fromIdTokenResponse(idResponse)!; expect(isNewUser).to.be.false; expect(providerId).to.eq(ProviderId.TWITTER); expect(username).to.eq('scott'); @@ -162,12 +146,8 @@ describe('core/user/additional_user_info', () => { } }) }); - const { - isNewUser, - providerId, - username, - profile - } = _fromIdTokenResponse(idResponse)!; + const { isNewUser, providerId, username, profile } = + _fromIdTokenResponse(idResponse)!; expect(isNewUser).to.be.false; expect(providerId).to.be.null; expect(username).to.be.undefined; @@ -183,12 +163,8 @@ describe('core/user/additional_user_info', () => { } }) }); - const { - isNewUser, - providerId, - username, - profile - } = _fromIdTokenResponse(idResponse)!; + const { isNewUser, providerId, username, profile } = + _fromIdTokenResponse(idResponse)!; expect(isNewUser).to.be.false; expect(providerId).to.be.null; expect(username).to.be.undefined; @@ -204,14 +180,10 @@ describe('core/user/additional_user_info', () => { }) ) + '.signature'; - const { - isNewUser, - providerId, - username, - profile - } = _fromIdTokenResponse( - idTokenResponse({ rawUserInfo: rawUserInfoWithLogin, idToken }) - )!; + const { isNewUser, providerId, username, profile } = + _fromIdTokenResponse( + idTokenResponse({ rawUserInfo: rawUserInfoWithLogin, idToken }) + )!; expect(isNewUser).to.be.false; expect(providerId).to.eq(ProviderId.FACEBOOK); expect(username).to.be.undefined; @@ -246,12 +218,8 @@ describe('core/user/additional_user_info', () => { providerId: ProviderId.ANONYMOUS, rawUserInfo: rawUserInfoWithLogin }); - const { - isNewUser, - providerId, - username, - profile - } = getAdditionalUserInfo(cred)!; + const { isNewUser, providerId, username, profile } = + getAdditionalUserInfo(cred)!; expect(isNewUser).to.be.false; expect(providerId).to.be.null; expect(username).to.be.undefined; @@ -264,12 +232,8 @@ describe('core/user/additional_user_info', () => { rawUserInfo: rawUserInfoWithLogin, isNewUser: true }); - const { - isNewUser, - providerId, - username, - profile - } = getAdditionalUserInfo(cred)!; + const { isNewUser, providerId, username, profile } = + getAdditionalUserInfo(cred)!; expect(isNewUser).to.be.true; expect(providerId).to.be.null; expect(username).to.be.undefined; @@ -278,7 +242,7 @@ describe('core/user/additional_user_info', () => { it('returns bespoke info if existing anonymous user', () => { // Note that _tokenResponse is not set on cred - ((user as unknown) as Record).isAnonymous = true; + (user as unknown as Record).isAnonymous = true; const { isNewUser, providerId, profile } = getAdditionalUserInfo(cred)!; expect(isNewUser).to.be.false; expect(providerId).to.be.null; diff --git a/packages-exp/auth-exp/src/core/user/reauthenticate.test.ts b/packages-exp/auth-exp/src/core/user/reauthenticate.test.ts index d75f20dbb74..793c0b57882 100644 --- a/packages-exp/auth-exp/src/core/user/reauthenticate.test.ts +++ b/packages-exp/auth-exp/src/core/user/reauthenticate.test.ts @@ -19,11 +19,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { stub } from 'sinon'; -import { - OperationType, - ProviderId, - SignInMethod -} from '../../model/enums'; +import { OperationType, ProviderId, SignInMethod } from '../../model/enums'; import { FirebaseError } from '@firebase/util'; import { mockEndpoint } from '../../../test/helpers/api/helper'; @@ -63,10 +59,10 @@ describe('core/user/reauthenticate', () => { it('should error if the idToken is missing', async () => { stub(credential, '_getReauthenticationResolver').returns( - Promise.resolve(({ + Promise.resolve({ ...TEST_ID_TOKEN_RESPONSE, idToken: undefined - } as unknown) as IdTokenResponse) + } as unknown as IdTokenResponse) ); await expect(_reauthenticate(user, credential)).to.be.rejectedWith( diff --git a/packages-exp/auth-exp/src/core/user/reload.test.ts b/packages-exp/auth-exp/src/core/user/reload.test.ts index 29c9cd1ce8a..b426df6a209 100644 --- a/packages-exp/auth-exp/src/core/user/reload.test.ts +++ b/packages-exp/auth-exp/src/core/user/reload.test.ts @@ -23,7 +23,6 @@ import * as sinonChai from 'sinon-chai'; import { UserInfo } from '../../model/public_types'; import { ProviderId } from '../../model/enums'; - import { mockEndpoint } from '../../../test/helpers/api/helper'; import { testAuth, TestAuth, testUser } from '../../../test/helpers/mock_auth'; import * as fetch from '../../../test/helpers/mock_fetch'; @@ -182,7 +181,7 @@ describe('core/user/reload', () => { providerData: Array<{ providerId: string }> ): void { // Get around readonly property - const mutUser = (user as unknown) as Record; + const mutUser = user as unknown as Record; mutUser.isAnonymous = isAnonStart; mutUser.email = emailStart; diff --git a/packages-exp/auth-exp/src/model/public_types.ts b/packages-exp/auth-exp/src/model/public_types.ts index 4c31ae75317..e8ea9f3693b 100644 --- a/packages-exp/auth-exp/src/model/public_types.ts +++ b/packages-exp/auth-exp/src/model/public_types.ts @@ -67,8 +67,8 @@ export interface Config { /** * Interface representing reCAPTCHA parameters. * - * See the [reCAPTCHA docs](https://developers.google.com/recaptcha/docs/display#render_param) - * for the list of accepted parameters. All parameters are accepted except for `sitekey`: Firebase Auth + * See the [reCAPTCHA docs](https://developers.google.com/recaptcha/docs/display#render_param) + * for the list of accepted parameters. All parameters are accepted except for `sitekey`: Firebase Auth * provisions a reCAPTCHA for each project and will configure the site key upon rendering. * * For an invisible reCAPTCHA, set the `size` key to `invisible`. diff --git a/packages-exp/auth-exp/src/platform_browser/popup_redirect.test.ts b/packages-exp/auth-exp/src/platform_browser/popup_redirect.test.ts index 4f394572354..529c5169a6b 100644 --- a/packages-exp/auth-exp/src/platform_browser/popup_redirect.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/popup_redirect.test.ts @@ -57,13 +57,14 @@ describe('platform_browser/popup_redirect', () => { beforeEach(async () => { auth = await testAuth(); - resolver = new (browserPopupRedirectResolver as SingletonInstantiator)(); + resolver = + new (browserPopupRedirectResolver as SingletonInstantiator)(); sinon.stub(validateOrigin, '_validateOrigin').returns(Promise.resolve()); iframeSendStub = sinon.stub(); sinon.stub(gapiLoader, '_loadGapi').returns( - Promise.resolve(({ + Promise.resolve({ open: () => Promise.resolve({ register: ( @@ -72,7 +73,7 @@ describe('platform_browser/popup_redirect', () => { ) => (onIframeMessage = cb), send: iframeSendStub }) - } as unknown) as gapi.iframes.Context) + } as unknown as gapi.iframes.Context) ); sinon.stub(authWindow._window(), 'gapi').value({ @@ -264,7 +265,7 @@ describe('platform_browser/popup_redirect', () => { expect(() => onIframeMessage({ type: 'authEvent', - authEvent: (null as unknown) as AuthEvent + authEvent: null as unknown as AuthEvent }) ).to.throw(FirebaseError, 'auth/invalid-auth-event'); }); @@ -272,9 +273,10 @@ describe('platform_browser/popup_redirect', () => { it('errors with invalid event if everything is null', async () => { const manager = (await resolver._initialize(auth)) as AuthEventManager; sinon.stub(manager, 'onEvent').returns(true); - expect(() => - onIframeMessage((null as unknown) as GapiAuthEvent) - ).to.throw(FirebaseError, 'auth/invalid-auth-event'); + expect(() => onIframeMessage(null as unknown as GapiAuthEvent)).to.throw( + FirebaseError, + 'auth/invalid-auth-event' + ); }); it('returns error to the iframe if the event was not handled', async () => { diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts index 59e299b627d..ebd48bb02b2 100644 --- a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts @@ -67,7 +67,7 @@ describe('platform_browser/recaptcha/recaptcha_verifier', () => { context('#render', () => { it('caches the promise if not completed and returns if called multiple times', () => { // This will force the loader to never return so the render promise never completes - sinon.stub(recaptchaLoader, 'load').returns(new Promise(() => { })); + sinon.stub(recaptchaLoader, 'load').returns(new Promise(() => {})); const renderPromise = verifier.render(); expect(verifier.render()).to.eq(renderPromise); }); diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts b/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts index c722a231ceb..06c2afb7c5e 100644 --- a/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts @@ -20,10 +20,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { - AuthError, - PopupRedirectResolver -} from '../../model/public_types'; +import { AuthError, PopupRedirectResolver } from '../../model/public_types'; import { OperationType, ProviderId } from '../../model/enums'; import { delay } from '../../../test/helpers/delay'; @@ -74,17 +71,15 @@ describe('platform_browser/strategies/redirect', () => { let idpStubs: sinon.SinonStubbedInstance; beforeEach(async () => { - eventManager = new AuthEventManager(({} as unknown) as TestAuth); + eventManager = new AuthEventManager({} as unknown as TestAuth); provider = new OAuthProvider(ProviderId.GOOGLE); resolver = makeMockPopupRedirectResolver(eventManager); - _getInstance( - resolver - )._redirectPersistence = RedirectPersistence; + _getInstance(resolver)._redirectPersistence = + RedirectPersistence; auth = await testAuth(resolver); idpStubs = sinon.stub(idpTasks); - _getInstance( - RedirectPersistence - ).hasPendingRedirect = true; + _getInstance(RedirectPersistence).hasPendingRedirect = + true; }); afterEach(() => { @@ -173,9 +168,8 @@ describe('platform_browser/strategies/redirect', () => { }); it('persists the redirect user and current user', async () => { - const redirectPersistence: PersistenceInternal = _getInstance( - RedirectPersistence - ); + const redirectPersistence: PersistenceInternal = + _getInstance(RedirectPersistence); sinon.spy(redirectPersistence, '_set'); sinon.spy(auth.persistenceLayer, '_set'); @@ -193,9 +187,8 @@ describe('platform_browser/strategies/redirect', () => { it('persists the redirect user but not current user if diff currentUser', async () => { await auth._updateCurrentUser(testUser(auth, 'not-uid', 'email', true)); - const redirectPersistence: PersistenceInternal = _getInstance( - RedirectPersistence - ); + const redirectPersistence: PersistenceInternal = + _getInstance(RedirectPersistence); sinon.spy(redirectPersistence, '_set'); sinon.spy(auth.persistenceLayer, '_set'); @@ -251,9 +244,8 @@ describe('platform_browser/strategies/redirect', () => { }); it('persists the redirect user and current user', async () => { - const redirectPersistence: PersistenceInternal = _getInstance( - RedirectPersistence - ); + const redirectPersistence: PersistenceInternal = + _getInstance(RedirectPersistence); sinon.spy(redirectPersistence, '_set'); sinon.spy(auth.persistenceLayer, '_set'); @@ -271,9 +263,8 @@ describe('platform_browser/strategies/redirect', () => { it('persists the redirect user but not current user if diff currentUser', async () => { await auth._updateCurrentUser(testUser(auth, 'not-uid', 'email', true)); - const redirectPersistence: PersistenceInternal = _getInstance( - RedirectPersistence - ); + const redirectPersistence: PersistenceInternal = + _getInstance(RedirectPersistence); sinon.spy(redirectPersistence, '_set'); sinon.spy(auth.persistenceLayer, '_set'); @@ -300,9 +291,8 @@ describe('platform_browser/strategies/redirect', () => { } async function reInitAuthWithRedirectUser(eventId: string): Promise { - const redirectPersistence: RedirectPersistence = _getInstance( - RedirectPersistence - ); + const redirectPersistence: RedirectPersistence = + _getInstance(RedirectPersistence); const mainPersistence = new MockPersistenceLayer(); const oldAuth = await testAuth(); const user = testUser(oldAuth, 'uid'); @@ -410,9 +400,8 @@ describe('platform_browser/strategies/redirect', () => { it('removes the redirect user and clears eventId from currentuser', async () => { await reInitAuthWithRedirectUser(MATCHING_EVENT_ID); - const redirectPersistence: PersistenceInternal = _getInstance( - RedirectPersistence - ); + const redirectPersistence: PersistenceInternal = + _getInstance(RedirectPersistence); sinon.spy(redirectPersistence, '_remove'); const cred = new UserCredentialImpl({ @@ -436,9 +425,8 @@ describe('platform_browser/strategies/redirect', () => { it('does not mutate authstate if bypassAuthState is true', async () => { await reInitAuthWithRedirectUser(MATCHING_EVENT_ID); - const redirectPersistence: PersistenceInternal = _getInstance( - RedirectPersistence - ); + const redirectPersistence: PersistenceInternal = + _getInstance(RedirectPersistence); sinon.spy(redirectPersistence, '_remove'); const cred = new UserCredentialImpl({ diff --git a/packages/firebase/index.d.ts b/packages/firebase/index.d.ts index 4a8a58dc713..1aec8b84575 100644 --- a/packages/firebase/index.d.ts +++ b/packages/firebase/index.d.ts @@ -7852,9 +7852,13 @@ declare namespace firebase.storage { * @param port - The emulator port (ex: 5001) * @param options.mockUserToken the mock auth token to use for unit testing Security Rules */ - useEmulator(host: string, port: number, options?: { - mockUserToken?: EmulatorMockTokenOptions | string; - }): void; + useEmulator( + host: string, + port: number, + options?: { + mockUserToken?: EmulatorMockTokenOptions | string; + } + ): void; } /** diff --git a/packages/firestore/rollup.shared.js b/packages/firestore/rollup.shared.js index 5c06866e0ff..36cdff5206a 100644 --- a/packages/firestore/rollup.shared.js +++ b/packages/firestore/rollup.shared.js @@ -135,11 +135,11 @@ exports.importTransformer = importTransformer; */ const removeAssertAndPrefixInternalTransformer = service => ({ before: [ - removeAsserts(service.getProgram()) - // renameInternals(service.getProgram(), { - // publicIdentifiers, - // prefix: '__PRIVATE_' - // }) + removeAsserts(service.getProgram()), + renameInternals(service.getProgram(), { + publicIdentifiers, + prefix: '__PRIVATE_' + }) ], after: [] }); diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite/reference.ts index 076134b6123..89ed441ed27 100644 --- a/packages/firestore/src/lite/reference.ts +++ b/packages/firestore/src/lite/reference.ts @@ -84,8 +84,7 @@ export type UpdateData = T extends Primitive : T extends Map ? Map, UpdateData> : T extends {} - ? { [K in keyof T]?: UpdateData | FieldValue } & - NestedUpdateFields + ? { [K in keyof T]?: UpdateData | FieldValue } & NestedUpdateFields : Partial; /** diff --git a/packages/firestore/src/lite/write_batch.ts b/packages/firestore/src/lite/write_batch.ts index 3cc5ad01269..088fe7da1bf 100644 --- a/packages/firestore/src/lite/write_batch.ts +++ b/packages/firestore/src/lite/write_batch.ts @@ -129,10 +129,7 @@ export class WriteBatch { * within the document. * @returns This `WriteBatch` instance. Used for chaining method calls. */ - update( - documentRef: DocumentReference, - data: UpdateData - ): WriteBatch; + update(documentRef: DocumentReference, data: UpdateData): WriteBatch; /** * Updates fields in the document referred to by this {@link * DocumentReference}. The update will fail if applied to a document that does From 826fff279487e72cb250af2c007181fd6e8a555c Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Tue, 17 Aug 2021 09:34:33 -0500 Subject: [PATCH 13/22] add integration tests --- .../firestore/test/lite/integration.test.ts | 715 +++++++++++++----- 1 file changed, 509 insertions(+), 206 deletions(-) diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 9a1cf08e0c4..9420501030a 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -600,212 +600,6 @@ function genericMutationTests( }); }); - // Skip test. This is just here to just make sure things compile. - // eslint-disable-next-line no-restricted-properties - it.skip('temporary sanity check tests', async () => { - class TestObject { - constructor( - readonly outerString: string, - readonly outerNum: number, - readonly outerArr: string[], - readonly nested: { - innerNested: { - innerNestedNum: number; - innerNestedString: string; - }; - innerArr: number[]; - timestamp: Timestamp; - } - ) {} - } - - const testConverterMerge = { - toFirestore(testObj: NestedPartial, options?: SetOptions) { - return { ...testObj }; - }, - fromFirestore(snapshot: QueryDocumentSnapshot): TestObject { - const data = snapshot.data(); - return new TestObject( - data.outerString, - data.outerNum, - data.outerArr, - data.nested - ); - } - }; - - const testConverter = { - toFirestore(testObj: WithFieldValue) { - return { ...testObj }; - }, - fromFirestore(snapshot: QueryDocumentSnapshot): TestObject { - const data = snapshot.data(); - return new TestObject( - data.outerString, - data.outerNum, - data.outerArr, - data.nested - ); - } - }; - - return withTestDb(async db => { - const coll = collection(db, 'posts'); - let ref = doc(coll, 'testobj').withConverter(testConverterMerge); - - // Allow Field Values and nested partials. - await setDoc( - ref, - { - outerString: deleteField(), - nested: { - innerNested: { - innerNestedNum: increment(1) - }, - innerArr: arrayUnion(2), - timestamp: serverTimestamp() - } - }, - { merge: true } - ); - - // Checks for non-existent properties - await setDoc( - ref, - { - // @ts-expect-error - nonexistent: 'foo' - }, - { merge: true } - ); - await setDoc( - ref, - { - nested: { - // @ts-expect-error - nonexistent: 'foo' - } - }, - { merge: true } - ); - - // Nested Partials are checked - await setDoc( - ref, - { - nested: { - innerNested: { - // @ts-expect-error - innerNestedNum: 'string' - }, - // @ts-expect-error - innerArr: 2 - } - }, - { merge: true } - ); - await setDoc( - ref, - { - // @ts-expect-error - nested: 3 - }, - { merge: true } - ); - - // Can use update to verify fields - await updateDoc(ref, { - // @ts-expect-error - outerString: 3, - // @ts-expect-error - outerNum: [], - outerArr: arrayUnion('foo'), - nested: { - innerNested: { - // @ts-expect-error - innerNestedNum: 'string' - }, - // @ts-expect-error - innerArr: 2, - timestamp: serverTimestamp() - } - }); - - // Cannot update nonexistent fields - await updateDoc(ref, { - // @ts-expect-error - nonexistent: 'foo' - }); - await updateDoc(ref, { - nested: { - // @ts-expect-error - nonexistent: 'foo' - } - }); - - // Can use update to check string separated fields - await updateDoc(ref, { - 'nested.innerNested.innerNestedNum': 4, - // @ts-expect-error - 'nested.innerNested.innerNestedString': 4, - // @ts-expect-error - 'nested.innerArr': 3, - 'nested.timestamp': serverTimestamp() - }); - - // Tests for `WithFieldValue` - ref = doc(coll, 'testobj').withConverter(testConverter); - // Allow Field Values and nested partials. - await setDoc(ref, { - outerString: deleteField(), - outerNum: 3, - outerArr: [], - nested: { - innerNested: { - innerNestedNum: increment(1), - innerNestedString: deleteField() - }, - innerArr: arrayUnion(2), - timestamp: serverTimestamp() - } - }); - - // Type validation still works for outer and nested fields - await setDoc(ref, { - outerString: deleteField(), - outerNum: 3, - // @ts-expect-error - outerArr: 2, - nested: { - innerNested: { - // @ts-expect-error - innerNestedNum: 'string', - innerNestedString: deleteField() - }, - innerArr: arrayUnion(2), - timestamp: serverTimestamp() - } - }); - - // Nonexistent fields should error - await setDoc(ref, { - outerString: deleteField(), - outerNum: 3, - outerArr: [], - nested: { - innerNested: { - // @ts-expect-error - nonexistent: 'string', - innerNestedNum: 2, - innerNestedString: deleteField() - }, - innerArr: arrayUnion(2), - timestamp: serverTimestamp() - } - }); - }); - }); - it('supports partials with mergeFields', async () => { return withTestDb(async db => { const coll = collection(db, 'posts'); @@ -1438,4 +1232,513 @@ describe('withConverter() support', () => { ); }); }); + + describe('types test', () => { + class TestObject { + constructor( + readonly outerString: string, + readonly outerArr: string[], + readonly nested: { + innerNested: { + innerNestedNum: number; + }; + innerArr: number[]; + timestamp: Timestamp; + } + ) {} + } + + const testConverter = { + toFirestore(testObj: WithFieldValue) { + return { ...testObj }; + }, + fromFirestore(snapshot: QueryDocumentSnapshot): TestObject { + const data = snapshot.data(); + return new TestObject(data.outerString, data.outerArr, data.nested); + } + }; + + function setBaseDoc(ref: DocumentReference): Promise { + return setDoc(ref, { + outerString: 'foo', + outerArr: [], + nested: { + innerNested: { + innerNestedNum: 2 + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }); + } + + describe('NestedPartial', () => { + const testConverterMerge = { + toFirestore(testObj: NestedPartial, options?: SetOptions) { + return { ...testObj }; + }, + fromFirestore(snapshot: QueryDocumentSnapshot): TestObject { + const data = snapshot.data(); + return new TestObject(data.outerString, data.outerArr, data.nested); + } + }; + + it('supports FieldValues', async () => { + return withTestDb(async db => { + const ref = doc(collection(db, 'testobj')).withConverter( + testConverterMerge + ); + + // Allow Field Values in nested partials. + await setDoc( + ref, + { + outerString: deleteField(), + nested: { + innerNested: { + innerNestedNum: increment(1) + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }, + { merge: true } + ); + + // Allow setting FieldValue on entire object field. + await setDoc( + ref, + { + nested: deleteField() + }, + { merge: true } + ); + }); + }); + + it('validates types in outer and inner fields', async () => { + return withTestDb(async db => { + const ref = doc(collection(db, 'testobj')).withConverter( + testConverterMerge + ); + + // Check top-level fields. + await setDoc( + ref, + { + // @ts-expect-error + outerString: 3, + // @ts-expect-error + outerArr: null + }, + { merge: true } + ); + + // Check nested fields. + await setDoc( + ref, + { + nested: { + innerNested: { + // @ts-expect-error + innerNestedNum: 'string' + }, + // @ts-expect-error + innerArr: null + } + }, + { merge: true } + ); + await setDoc( + ref, + { + // @ts-expect-error + nested: 3 + }, + { merge: true } + ); + }); + }); + + it('checks for nonexistent properties', async () => { + return withTestDb(async db => { + const ref = doc(collection(db, 'testobj')).withConverter( + testConverterMerge + ); + // Top-level property. + await setDoc( + ref, + { + // @ts-expect-error + nonexistent: 'foo' + }, + { merge: true } + ); + + // Nested property + await setDoc( + ref, + { + nested: { + // @ts-expect-error + nonexistent: 'foo' + } + }, + { merge: true } + ); + }); + }); + }); + + describe('WithFieldValue', () => { + it('supports FieldValues', async () => { + return withTestDb(async db => { + const ref = doc(collection(db, 'testobj')).withConverter( + testConverter + ); + + // Allow Field Values and nested partials. + await setDoc(ref, { + outerString: 'foo', + outerArr: [], + nested: { + innerNested: { + innerNestedNum: increment(1) + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }); + }); + }); + + it('requires all fields to be present', async () => { + return withTestDb(async db => { + const ref = doc(collection(db, 'testobj')).withConverter( + testConverter + ); + + // Allow Field Values and nested partials. + // @ts-expect-error + await setDoc(ref, { + outerArr: [], + nested: { + innerNested: { + innerNestedNum: increment(1) + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }); + }); + }); + + it('validates inner and outer fields', async () => { + return withTestDb(async db => { + const ref = doc(collection(db, 'testobj')).withConverter( + testConverter + ); + + await setDoc(ref, { + outerString: 'foo', + // @ts-expect-error + outerArr: 2, + nested: { + innerNested: { + // @ts-expect-error + innerNestedNum: 'string' + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }); + }); + }); + + it('checks for nonexistent properties', async () => { + return withTestDb(async db => { + const ref = doc(collection(db, 'testobj')).withConverter( + testConverter + ); + + // Top-level nonexistent fields should error + await setDoc(ref, { + outerString: 'foo', + // @ts-expect-error + outerNum: 3, + outerArr: [], + nested: { + innerNested: { + innerNestedNum: 2 + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }); + + // Nested nonexistent fields should error + await setDoc(ref, { + outerString: 'foo', + outerNum: 3, + outerArr: [], + nested: { + innerNested: { + // @ts-expect-error + nonexistent: 'string', + innerNestedNum: 2 + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }); + }); + }); + }); + + describe('UpdateData', () => { + // Skip test. This is just here to just make sure things compile. + // eslint-disable-next-line no-restricted-properties + + it('supports FieldValues', () => { + return withTestDb(async db => { + const ref = doc(collection(db, 'testobj')).withConverter( + testConverter + ); + await setBaseDoc(ref); + + await updateDoc(ref, { + outerString: deleteField(), + nested: { + innerNested: { + innerNestedNum: increment(2) + }, + innerArr: arrayUnion(3) + } + }); + }); + }); + + it('validates inner and outer fields', async () => { + return withTestDb(async db => { + const ref = doc(collection(db, 'testobj')).withConverter( + testConverter + ); + await setBaseDoc(ref); + + await updateDoc(ref, { + // @ts-expect-error + outerString: 3, + nested: { + innerNested: { + // @ts-expect-error + innerNestedNum: 'string' + }, + // @ts-expect-error + innerArr: 2 + } + }); + }); + }); + + it('supports string-separated fields', () => { + return withTestDb(async db => { + const ref = doc(collection(db, 'testobj')).withConverter( + testConverter + ); + await setBaseDoc(ref); + + await updateDoc(ref, { + // @ts-expect-error + outerString: 3, + // @ts-expect-error + 'nested.innerNested.innerNestedNum': 'string', + // @ts-expect-error + 'nested.innerArr': 3, + 'nested.timestamp': serverTimestamp() + }); + + // String comprehension works in nested fields. + await updateDoc(ref, { + nested: { + innerNested: { + // @ts-expect-error + 'innerNestedNum': 'string' + }, + // @ts-expect-error + 'innerArr': 3 + } + }); + }); + }); + + it('checks for nonexistent fields', () => { + return withTestDb(async db => { + const ref = doc(collection(db, 'testobj')).withConverter( + testConverter + ); + await setBaseDoc(ref); + + // Top-level fields. + await updateDoc(ref, { + // @ts-expect-error + nonexistent: 'foo' + }); + + // Nested Fields. + await updateDoc(ref, { + nested: { + // @ts-expect-error + nonexistent: 'foo' + } + }); + + // String fields. + await updateDoc(ref, { + // @ts-expect-error + 'nonexistent': 'foo' + }); + await updateDoc(ref, { + // @ts-expect-error + 'nested.nonexistent': 'foo' + }); + }); + }); + }); + + describe('methods', () => { + it('addDoc()', () => { + return withTestDb(async db => { + const ref = collection(db, 'testobj').withConverter(testConverter); + + // Requires all fields to be present + // @ts-expect-error + await addDoc(ref, { + outerArr: [], + nested: { + innerNested: { + innerNestedNum: 2 + }, + innerArr: [], + timestamp: serverTimestamp() + } + }); + }); + }); + + it('WriteBatch.set()', () => { + return withTestDb(async db => { + const ref = doc(collection(db, 'testobj')).withConverter( + testConverter + ); + const batch = writeBatch(db); + + // Requires full object if {merge: true} is not set. + // @ts-expect-error + batch.set(ref, { + outerArr: [], + nested: { + innerNested: { + innerNestedNum: increment(1) + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }); + + batch.set( + ref, + { + outerArr: [], + nested: { + innerNested: { + innerNestedNum: increment(1) + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }, + { merge: true } + ); + }); + }); + + it('WriteBatch.update()', () => { + return withTestDb(async db => { + const ref = doc(collection(db, 'testobj')).withConverter( + testConverter + ); + const batch = writeBatch(db); + + batch.update(ref, { + outerArr: [], + nested: { + 'innerNested.innerNestedNum': increment(1), + 'innerArr': arrayUnion(2), + timestamp: serverTimestamp() + } + }); + }); + }); + + it('Transaction.set()', () => { + return withTestDb(async db => { + const ref = doc(collection(db, 'testobj')).withConverter( + testConverter + ); + + return runTransaction(db, async tx => { + // Requires full object if {merge: true} is not set. + // @ts-expect-error + tx.set(ref, { + outerArr: [], + nested: { + innerNested: { + innerNestedNum: increment(1) + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }); + + tx.set( + ref, + { + outerArr: [], + nested: { + innerNested: { + innerNestedNum: increment(1) + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }, + { merge: true } + ); + }); + }); + }); + + it('Transaction.update()', () => { + return withTestDb(async db => { + const ref = doc(collection(db, 'testobj')).withConverter( + testConverter + ); + await setBaseDoc(ref); + + return runTransaction(db, async tx => { + tx.update(ref, { + outerArr: [], + nested: { + innerNested: { + innerNestedNum: increment(1) + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }); + }); + }); + }); + }); + }); }); From 0b620ca0edca4de9ede7205b74f56e23522cb95e Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Tue, 17 Aug 2021 09:46:55 -0500 Subject: [PATCH 14/22] revert auth-exp lint --- .../src/core/providers/facebook.test.ts | 6 +- .../src/core/providers/github.test.ts | 6 +- .../src/core/providers/google.test.ts | 6 +- .../auth-exp/src/core/providers/oauth.test.ts | 6 +- .../src/core/providers/twitter.test.ts | 6 +- .../abstract_popup_redirect_operation.test.ts | 2 +- .../src/core/strategies/credential.test.ts | 6 +- .../src/core/strategies/custom_token.test.ts | 13 ++-- .../strategies/email_and_password.test.ts | 30 ++++--- .../auth-exp/src/core/strategies/idp.test.ts | 6 +- .../src/core/strategies/redirect.test.ts | 17 ++-- .../core/user/additional_user_info.test.ts | 78 ++++++++++++++----- .../src/core/user/reauthenticate.test.ts | 10 ++- .../auth-exp/src/core/user/reload.test.ts | 3 +- .../auth-exp/src/model/public_types.ts | 4 +- .../platform_browser/popup_redirect.test.ts | 16 ++-- .../recaptcha/recaptcha_verifier.test.ts | 2 +- .../strategies/redirect.test.ts | 52 ++++++++----- 18 files changed, 179 insertions(+), 90 deletions(-) diff --git a/packages-exp/auth-exp/src/core/providers/facebook.test.ts b/packages-exp/auth-exp/src/core/providers/facebook.test.ts index 7f71d04cc94..7b6ba70cbbf 100644 --- a/packages-exp/auth-exp/src/core/providers/facebook.test.ts +++ b/packages-exp/auth-exp/src/core/providers/facebook.test.ts @@ -17,7 +17,11 @@ import { expect } from 'chai'; -import { OperationType, ProviderId, SignInMethod } from '../../model/enums'; +import { + OperationType, + ProviderId, + SignInMethod +} from '../../model/enums'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; diff --git a/packages-exp/auth-exp/src/core/providers/github.test.ts b/packages-exp/auth-exp/src/core/providers/github.test.ts index 9e7d0d73de8..56d8bfcf64c 100644 --- a/packages-exp/auth-exp/src/core/providers/github.test.ts +++ b/packages-exp/auth-exp/src/core/providers/github.test.ts @@ -17,7 +17,11 @@ import { expect } from 'chai'; -import { OperationType, ProviderId, SignInMethod } from '../../model/enums'; +import { + OperationType, + ProviderId, + SignInMethod +} from '../../model/enums'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; diff --git a/packages-exp/auth-exp/src/core/providers/google.test.ts b/packages-exp/auth-exp/src/core/providers/google.test.ts index d600cb2c17c..544860711f6 100644 --- a/packages-exp/auth-exp/src/core/providers/google.test.ts +++ b/packages-exp/auth-exp/src/core/providers/google.test.ts @@ -17,7 +17,11 @@ import { expect } from 'chai'; -import { OperationType, ProviderId, SignInMethod } from '../../model/enums'; +import { + OperationType, + ProviderId, + SignInMethod +} from '../../model/enums'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; diff --git a/packages-exp/auth-exp/src/core/providers/oauth.test.ts b/packages-exp/auth-exp/src/core/providers/oauth.test.ts index c9f50b3866f..5c831e8a5fc 100644 --- a/packages-exp/auth-exp/src/core/providers/oauth.test.ts +++ b/packages-exp/auth-exp/src/core/providers/oauth.test.ts @@ -17,7 +17,11 @@ import { expect } from 'chai'; -import { OperationType, ProviderId, SignInMethod } from '../../model/enums'; +import { + OperationType, + ProviderId, + SignInMethod +} from '../../model/enums'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; diff --git a/packages-exp/auth-exp/src/core/providers/twitter.test.ts b/packages-exp/auth-exp/src/core/providers/twitter.test.ts index b1d4a05fcd3..31d1f382cf3 100644 --- a/packages-exp/auth-exp/src/core/providers/twitter.test.ts +++ b/packages-exp/auth-exp/src/core/providers/twitter.test.ts @@ -34,7 +34,11 @@ import { expect } from 'chai'; -import { OperationType, ProviderId, SignInMethod } from '../../model/enums'; +import { + OperationType, + ProviderId, + SignInMethod +} from '../../model/enums'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; diff --git a/packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.test.ts b/packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.test.ts index f3500130a2a..ba818c0b9dd 100644 --- a/packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.test.ts @@ -175,7 +175,7 @@ describe('core/strategies/abstract_popup_redirect_operation', () => { context('idp tasks', () => { function updateFilter(type: AuthEventType): void { - (operation as unknown as Record).filter = type; + ((operation as unknown) as Record).filter = type; } function expectedIdpTaskParams(): idp.IdpTaskParams { diff --git a/packages-exp/auth-exp/src/core/strategies/credential.test.ts b/packages-exp/auth-exp/src/core/strategies/credential.test.ts index 654004c0a13..c0604f69d9a 100644 --- a/packages-exp/auth-exp/src/core/strategies/credential.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/credential.test.ts @@ -19,7 +19,11 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { stub } from 'sinon'; -import { OperationType, ProviderId, SignInMethod } from '../../model/enums'; +import { + OperationType, + ProviderId, + SignInMethod +} from '../../model/enums'; import { FirebaseError } from '@firebase/util'; import { mockEndpoint } from '../../../test/helpers/api/helper'; diff --git a/packages-exp/auth-exp/src/core/strategies/custom_token.test.ts b/packages-exp/auth-exp/src/core/strategies/custom_token.test.ts index cf83673a714..5c046ec4d5c 100644 --- a/packages-exp/auth-exp/src/core/strategies/custom_token.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/custom_token.test.ts @@ -68,11 +68,14 @@ describe('core/strategies/signInWithCustomToken', () => { afterEach(mockFetch.tearDown); it('should return a valid user credential', async () => { - const { user, operationType, _tokenResponse } = - (await signInWithCustomToken( - auth, - 'look-at-me-im-a-jwt' - )) as UserCredentialInternal; + const { + user, + operationType, + _tokenResponse + } = (await signInWithCustomToken( + auth, + 'look-at-me-im-a-jwt' + )) as UserCredentialInternal; expect(_tokenResponse).to.eql(idTokenResponse); expect(user.uid).to.eq('local-id'); expect(user.displayName).to.eq('display-name'); diff --git a/packages-exp/auth-exp/src/core/strategies/email_and_password.test.ts b/packages-exp/auth-exp/src/core/strategies/email_and_password.test.ts index 75440f6067c..af941c31760 100644 --- a/packages-exp/auth-exp/src/core/strategies/email_and_password.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/email_and_password.test.ts @@ -384,12 +384,15 @@ describe('core/strategies/email_and_password/createUserWithEmailAndPassword', () afterEach(mockFetch.tearDown); it('should sign in the user', async () => { - const { _tokenResponse, user, operationType } = - (await createUserWithEmailAndPassword( - auth, - 'some-email', - 'some-password' - )) as UserCredentialInternal; + const { + _tokenResponse, + user, + operationType + } = (await createUserWithEmailAndPassword( + auth, + 'some-email', + 'some-password' + )) as UserCredentialInternal; expect(_tokenResponse).to.eql({ idToken: 'id-token', refreshToken: 'refresh-token', @@ -424,12 +427,15 @@ describe('core/strategies/email_and_password/signInWithEmailAndPassword', () => afterEach(mockFetch.tearDown); it('should sign in the user', async () => { - const { _tokenResponse, user, operationType } = - (await signInWithEmailAndPassword( - auth, - 'some-email', - 'some-password' - )) as UserCredentialInternal; + const { + _tokenResponse, + user, + operationType + } = (await signInWithEmailAndPassword( + auth, + 'some-email', + 'some-password' + )) as UserCredentialInternal; expect(_tokenResponse).to.eql({ idToken: 'id-token', refreshToken: 'refresh-token', diff --git a/packages-exp/auth-exp/src/core/strategies/idp.test.ts b/packages-exp/auth-exp/src/core/strategies/idp.test.ts index d918bbcf724..0fcef68c246 100644 --- a/packages-exp/auth-exp/src/core/strategies/idp.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/idp.test.ts @@ -104,7 +104,7 @@ describe('core/strategies/idb', () => { it('passes through the bypassAuthState flag', async () => { const stub = sinon .stub(credential, '_signInWithCredential') - .returns(Promise.resolve({} as unknown as UserCredentialImpl)); + .returns(Promise.resolve(({} as unknown) as UserCredentialImpl)); await idpTasks._signIn({ auth, user, @@ -160,7 +160,7 @@ describe('core/strategies/idb', () => { it('passes through the bypassAuthState flag', async () => { const stub = sinon .stub(reauthenticate, '_reauthenticate') - .returns(Promise.resolve({} as unknown as UserCredentialImpl)); + .returns(Promise.resolve(({} as unknown) as UserCredentialImpl)); await idpTasks._reauth({ auth, user, @@ -218,7 +218,7 @@ describe('core/strategies/idb', () => { it('passes through the bypassAuthState flag', async () => { const stub = sinon .stub(linkUnlink, '_link') - .returns(Promise.resolve({} as unknown as UserCredentialImpl)); + .returns(Promise.resolve(({} as unknown) as UserCredentialImpl)); await idpTasks._link({ auth, user, diff --git a/packages-exp/auth-exp/src/core/strategies/redirect.test.ts b/packages-exp/auth-exp/src/core/strategies/redirect.test.ts index b79d19eb3b6..0b94ae8bfd0 100644 --- a/packages-exp/auth-exp/src/core/strategies/redirect.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/redirect.test.ts @@ -15,7 +15,10 @@ * limitations under the License. */ -import { AuthError, PopupRedirectResolver } from '../../model/public_types'; +import { + AuthError, + PopupRedirectResolver +} from '../../model/public_types'; import { OperationType, ProviderId } from '../../model/enums'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; @@ -56,11 +59,12 @@ describe('core/strategies/redirect', () => { let redirectPersistence: RedirectPersistence; beforeEach(async () => { - eventManager = new AuthEventManager({} as unknown as TestAuth); + eventManager = new AuthEventManager(({} as unknown) as TestAuth); idpStubs = sinon.stub(idpTasks); resolver = makeMockPopupRedirectResolver(eventManager); - _getInstance(resolver)._redirectPersistence = - RedirectPersistence; + _getInstance( + resolver + )._redirectPersistence = RedirectPersistence; auth = await testAuth(); redirectAction = new RedirectAction(auth, _getInstance(resolver), false); redirectPersistence = _getInstance(RedirectPersistence); @@ -193,8 +197,9 @@ describe('core/strategies/redirect', () => { it('bypasses initialization if no key set', async () => { await reInitAuthWithRedirectUser(MATCHING_EVENT_ID); - const resolverInstance = - _getInstance(resolver); + const resolverInstance = _getInstance( + resolver + ); sinon.spy(resolverInstance, '_initialize'); redirectPersistence.hasPendingRedirect = false; diff --git a/packages-exp/auth-exp/src/core/user/additional_user_info.test.ts b/packages-exp/auth-exp/src/core/user/additional_user_info.test.ts index 8e701c3cb45..3e89ecefacb 100644 --- a/packages-exp/auth-exp/src/core/user/additional_user_info.test.ts +++ b/packages-exp/auth-exp/src/core/user/additional_user_info.test.ts @@ -47,8 +47,12 @@ describe('core/user/additional_user_info', () => { providerId: ProviderId.FACEBOOK, rawUserInfo: rawUserInfoWithLogin }); - const { isNewUser, providerId, username, profile } = - _fromIdTokenResponse(idResponse)!; + const { + isNewUser, + providerId, + username, + profile + } = _fromIdTokenResponse(idResponse)!; expect(isNewUser).to.be.false; expect(providerId).to.eq(ProviderId.FACEBOOK); expect(username).to.be.undefined; @@ -60,8 +64,12 @@ describe('core/user/additional_user_info', () => { providerId: ProviderId.GITHUB, rawUserInfo: rawUserInfoWithLogin }); - const { isNewUser, providerId, username, profile } = - _fromIdTokenResponse(idResponse)!; + const { + isNewUser, + providerId, + username, + profile + } = _fromIdTokenResponse(idResponse)!; expect(isNewUser).to.be.false; expect(providerId).to.eq(ProviderId.GITHUB); expect(username).to.eq('scott'); @@ -73,8 +81,12 @@ describe('core/user/additional_user_info', () => { providerId: ProviderId.GOOGLE, rawUserInfo: rawUserInfoWithLogin }); - const { isNewUser, providerId, username, profile } = - _fromIdTokenResponse(idResponse)!; + const { + isNewUser, + providerId, + username, + profile + } = _fromIdTokenResponse(idResponse)!; expect(isNewUser).to.be.false; expect(providerId).to.eq(ProviderId.GOOGLE); expect(username).to.be.undefined; @@ -87,8 +99,12 @@ describe('core/user/additional_user_info', () => { rawUserInfo: rawUserInfoNoLogin, screenName: 'scott' }); - const { isNewUser, providerId, username, profile } = - _fromIdTokenResponse(idResponse)!; + const { + isNewUser, + providerId, + username, + profile + } = _fromIdTokenResponse(idResponse)!; expect(isNewUser).to.be.false; expect(providerId).to.eq(ProviderId.TWITTER); expect(username).to.eq('scott'); @@ -146,8 +162,12 @@ describe('core/user/additional_user_info', () => { } }) }); - const { isNewUser, providerId, username, profile } = - _fromIdTokenResponse(idResponse)!; + const { + isNewUser, + providerId, + username, + profile + } = _fromIdTokenResponse(idResponse)!; expect(isNewUser).to.be.false; expect(providerId).to.be.null; expect(username).to.be.undefined; @@ -163,8 +183,12 @@ describe('core/user/additional_user_info', () => { } }) }); - const { isNewUser, providerId, username, profile } = - _fromIdTokenResponse(idResponse)!; + const { + isNewUser, + providerId, + username, + profile + } = _fromIdTokenResponse(idResponse)!; expect(isNewUser).to.be.false; expect(providerId).to.be.null; expect(username).to.be.undefined; @@ -180,10 +204,14 @@ describe('core/user/additional_user_info', () => { }) ) + '.signature'; - const { isNewUser, providerId, username, profile } = - _fromIdTokenResponse( - idTokenResponse({ rawUserInfo: rawUserInfoWithLogin, idToken }) - )!; + const { + isNewUser, + providerId, + username, + profile + } = _fromIdTokenResponse( + idTokenResponse({ rawUserInfo: rawUserInfoWithLogin, idToken }) + )!; expect(isNewUser).to.be.false; expect(providerId).to.eq(ProviderId.FACEBOOK); expect(username).to.be.undefined; @@ -218,8 +246,12 @@ describe('core/user/additional_user_info', () => { providerId: ProviderId.ANONYMOUS, rawUserInfo: rawUserInfoWithLogin }); - const { isNewUser, providerId, username, profile } = - getAdditionalUserInfo(cred)!; + const { + isNewUser, + providerId, + username, + profile + } = getAdditionalUserInfo(cred)!; expect(isNewUser).to.be.false; expect(providerId).to.be.null; expect(username).to.be.undefined; @@ -232,8 +264,12 @@ describe('core/user/additional_user_info', () => { rawUserInfo: rawUserInfoWithLogin, isNewUser: true }); - const { isNewUser, providerId, username, profile } = - getAdditionalUserInfo(cred)!; + const { + isNewUser, + providerId, + username, + profile + } = getAdditionalUserInfo(cred)!; expect(isNewUser).to.be.true; expect(providerId).to.be.null; expect(username).to.be.undefined; @@ -242,7 +278,7 @@ describe('core/user/additional_user_info', () => { it('returns bespoke info if existing anonymous user', () => { // Note that _tokenResponse is not set on cred - (user as unknown as Record).isAnonymous = true; + ((user as unknown) as Record).isAnonymous = true; const { isNewUser, providerId, profile } = getAdditionalUserInfo(cred)!; expect(isNewUser).to.be.false; expect(providerId).to.be.null; diff --git a/packages-exp/auth-exp/src/core/user/reauthenticate.test.ts b/packages-exp/auth-exp/src/core/user/reauthenticate.test.ts index 793c0b57882..d75f20dbb74 100644 --- a/packages-exp/auth-exp/src/core/user/reauthenticate.test.ts +++ b/packages-exp/auth-exp/src/core/user/reauthenticate.test.ts @@ -19,7 +19,11 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { stub } from 'sinon'; -import { OperationType, ProviderId, SignInMethod } from '../../model/enums'; +import { + OperationType, + ProviderId, + SignInMethod +} from '../../model/enums'; import { FirebaseError } from '@firebase/util'; import { mockEndpoint } from '../../../test/helpers/api/helper'; @@ -59,10 +63,10 @@ describe('core/user/reauthenticate', () => { it('should error if the idToken is missing', async () => { stub(credential, '_getReauthenticationResolver').returns( - Promise.resolve({ + Promise.resolve(({ ...TEST_ID_TOKEN_RESPONSE, idToken: undefined - } as unknown as IdTokenResponse) + } as unknown) as IdTokenResponse) ); await expect(_reauthenticate(user, credential)).to.be.rejectedWith( diff --git a/packages-exp/auth-exp/src/core/user/reload.test.ts b/packages-exp/auth-exp/src/core/user/reload.test.ts index b426df6a209..29c9cd1ce8a 100644 --- a/packages-exp/auth-exp/src/core/user/reload.test.ts +++ b/packages-exp/auth-exp/src/core/user/reload.test.ts @@ -23,6 +23,7 @@ import * as sinonChai from 'sinon-chai'; import { UserInfo } from '../../model/public_types'; import { ProviderId } from '../../model/enums'; + import { mockEndpoint } from '../../../test/helpers/api/helper'; import { testAuth, TestAuth, testUser } from '../../../test/helpers/mock_auth'; import * as fetch from '../../../test/helpers/mock_fetch'; @@ -181,7 +182,7 @@ describe('core/user/reload', () => { providerData: Array<{ providerId: string }> ): void { // Get around readonly property - const mutUser = user as unknown as Record; + const mutUser = (user as unknown) as Record; mutUser.isAnonymous = isAnonStart; mutUser.email = emailStart; diff --git a/packages-exp/auth-exp/src/model/public_types.ts b/packages-exp/auth-exp/src/model/public_types.ts index e8ea9f3693b..4c31ae75317 100644 --- a/packages-exp/auth-exp/src/model/public_types.ts +++ b/packages-exp/auth-exp/src/model/public_types.ts @@ -67,8 +67,8 @@ export interface Config { /** * Interface representing reCAPTCHA parameters. * - * See the [reCAPTCHA docs](https://developers.google.com/recaptcha/docs/display#render_param) - * for the list of accepted parameters. All parameters are accepted except for `sitekey`: Firebase Auth + * See the [reCAPTCHA docs](https://developers.google.com/recaptcha/docs/display#render_param) + * for the list of accepted parameters. All parameters are accepted except for `sitekey`: Firebase Auth * provisions a reCAPTCHA for each project and will configure the site key upon rendering. * * For an invisible reCAPTCHA, set the `size` key to `invisible`. diff --git a/packages-exp/auth-exp/src/platform_browser/popup_redirect.test.ts b/packages-exp/auth-exp/src/platform_browser/popup_redirect.test.ts index 529c5169a6b..4f394572354 100644 --- a/packages-exp/auth-exp/src/platform_browser/popup_redirect.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/popup_redirect.test.ts @@ -57,14 +57,13 @@ describe('platform_browser/popup_redirect', () => { beforeEach(async () => { auth = await testAuth(); - resolver = - new (browserPopupRedirectResolver as SingletonInstantiator)(); + resolver = new (browserPopupRedirectResolver as SingletonInstantiator)(); sinon.stub(validateOrigin, '_validateOrigin').returns(Promise.resolve()); iframeSendStub = sinon.stub(); sinon.stub(gapiLoader, '_loadGapi').returns( - Promise.resolve({ + Promise.resolve(({ open: () => Promise.resolve({ register: ( @@ -73,7 +72,7 @@ describe('platform_browser/popup_redirect', () => { ) => (onIframeMessage = cb), send: iframeSendStub }) - } as unknown as gapi.iframes.Context) + } as unknown) as gapi.iframes.Context) ); sinon.stub(authWindow._window(), 'gapi').value({ @@ -265,7 +264,7 @@ describe('platform_browser/popup_redirect', () => { expect(() => onIframeMessage({ type: 'authEvent', - authEvent: null as unknown as AuthEvent + authEvent: (null as unknown) as AuthEvent }) ).to.throw(FirebaseError, 'auth/invalid-auth-event'); }); @@ -273,10 +272,9 @@ describe('platform_browser/popup_redirect', () => { it('errors with invalid event if everything is null', async () => { const manager = (await resolver._initialize(auth)) as AuthEventManager; sinon.stub(manager, 'onEvent').returns(true); - expect(() => onIframeMessage(null as unknown as GapiAuthEvent)).to.throw( - FirebaseError, - 'auth/invalid-auth-event' - ); + expect(() => + onIframeMessage((null as unknown) as GapiAuthEvent) + ).to.throw(FirebaseError, 'auth/invalid-auth-event'); }); it('returns error to the iframe if the event was not handled', async () => { diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts index ebd48bb02b2..59e299b627d 100644 --- a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts @@ -67,7 +67,7 @@ describe('platform_browser/recaptcha/recaptcha_verifier', () => { context('#render', () => { it('caches the promise if not completed and returns if called multiple times', () => { // This will force the loader to never return so the render promise never completes - sinon.stub(recaptchaLoader, 'load').returns(new Promise(() => {})); + sinon.stub(recaptchaLoader, 'load').returns(new Promise(() => { })); const renderPromise = verifier.render(); expect(verifier.render()).to.eq(renderPromise); }); diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts b/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts index 06c2afb7c5e..c722a231ceb 100644 --- a/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts @@ -20,7 +20,10 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { AuthError, PopupRedirectResolver } from '../../model/public_types'; +import { + AuthError, + PopupRedirectResolver +} from '../../model/public_types'; import { OperationType, ProviderId } from '../../model/enums'; import { delay } from '../../../test/helpers/delay'; @@ -71,15 +74,17 @@ describe('platform_browser/strategies/redirect', () => { let idpStubs: sinon.SinonStubbedInstance; beforeEach(async () => { - eventManager = new AuthEventManager({} as unknown as TestAuth); + eventManager = new AuthEventManager(({} as unknown) as TestAuth); provider = new OAuthProvider(ProviderId.GOOGLE); resolver = makeMockPopupRedirectResolver(eventManager); - _getInstance(resolver)._redirectPersistence = - RedirectPersistence; + _getInstance( + resolver + )._redirectPersistence = RedirectPersistence; auth = await testAuth(resolver); idpStubs = sinon.stub(idpTasks); - _getInstance(RedirectPersistence).hasPendingRedirect = - true; + _getInstance( + RedirectPersistence + ).hasPendingRedirect = true; }); afterEach(() => { @@ -168,8 +173,9 @@ describe('platform_browser/strategies/redirect', () => { }); it('persists the redirect user and current user', async () => { - const redirectPersistence: PersistenceInternal = - _getInstance(RedirectPersistence); + const redirectPersistence: PersistenceInternal = _getInstance( + RedirectPersistence + ); sinon.spy(redirectPersistence, '_set'); sinon.spy(auth.persistenceLayer, '_set'); @@ -187,8 +193,9 @@ describe('platform_browser/strategies/redirect', () => { it('persists the redirect user but not current user if diff currentUser', async () => { await auth._updateCurrentUser(testUser(auth, 'not-uid', 'email', true)); - const redirectPersistence: PersistenceInternal = - _getInstance(RedirectPersistence); + const redirectPersistence: PersistenceInternal = _getInstance( + RedirectPersistence + ); sinon.spy(redirectPersistence, '_set'); sinon.spy(auth.persistenceLayer, '_set'); @@ -244,8 +251,9 @@ describe('platform_browser/strategies/redirect', () => { }); it('persists the redirect user and current user', async () => { - const redirectPersistence: PersistenceInternal = - _getInstance(RedirectPersistence); + const redirectPersistence: PersistenceInternal = _getInstance( + RedirectPersistence + ); sinon.spy(redirectPersistence, '_set'); sinon.spy(auth.persistenceLayer, '_set'); @@ -263,8 +271,9 @@ describe('platform_browser/strategies/redirect', () => { it('persists the redirect user but not current user if diff currentUser', async () => { await auth._updateCurrentUser(testUser(auth, 'not-uid', 'email', true)); - const redirectPersistence: PersistenceInternal = - _getInstance(RedirectPersistence); + const redirectPersistence: PersistenceInternal = _getInstance( + RedirectPersistence + ); sinon.spy(redirectPersistence, '_set'); sinon.spy(auth.persistenceLayer, '_set'); @@ -291,8 +300,9 @@ describe('platform_browser/strategies/redirect', () => { } async function reInitAuthWithRedirectUser(eventId: string): Promise { - const redirectPersistence: RedirectPersistence = - _getInstance(RedirectPersistence); + const redirectPersistence: RedirectPersistence = _getInstance( + RedirectPersistence + ); const mainPersistence = new MockPersistenceLayer(); const oldAuth = await testAuth(); const user = testUser(oldAuth, 'uid'); @@ -400,8 +410,9 @@ describe('platform_browser/strategies/redirect', () => { it('removes the redirect user and clears eventId from currentuser', async () => { await reInitAuthWithRedirectUser(MATCHING_EVENT_ID); - const redirectPersistence: PersistenceInternal = - _getInstance(RedirectPersistence); + const redirectPersistence: PersistenceInternal = _getInstance( + RedirectPersistence + ); sinon.spy(redirectPersistence, '_remove'); const cred = new UserCredentialImpl({ @@ -425,8 +436,9 @@ describe('platform_browser/strategies/redirect', () => { it('does not mutate authstate if bypassAuthState is true', async () => { await reInitAuthWithRedirectUser(MATCHING_EVENT_ID); - const redirectPersistence: PersistenceInternal = - _getInstance(RedirectPersistence); + const redirectPersistence: PersistenceInternal = _getInstance( + RedirectPersistence + ); sinon.spy(redirectPersistence, '_remove'); const cred = new UserCredentialImpl({ From da10a156041a638dc79d0ae5fdb0a624ade29390 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Tue, 17 Aug 2021 13:16:20 -0500 Subject: [PATCH 15/22] resolve comments --- common/api-review/firestore-lite.api.md | 18 ++-- common/api-review/firestore.api.md | 18 ++-- packages/firestore/exp/api.ts | 2 +- packages/firestore/lite/index.ts | 2 +- packages/firestore/src/api/database.ts | 16 +-- packages/firestore/src/exp/reference.ts | 2 +- packages/firestore/src/exp/reference_impl.ts | 6 +- packages/firestore/src/exp/snapshot.ts | 19 +++- packages/firestore/src/lite/reference.ts | 12 +-- packages/firestore/src/lite/reference_impl.ts | 15 +-- packages/firestore/src/lite/snapshot.ts | 17 ++- packages/firestore/src/lite/transaction.ts | 6 +- .../firestore/src/lite/user_data_reader.ts | 11 +- packages/firestore/src/lite/write_batch.ts | 6 +- packages/firestore/test/lite/helpers.ts | 7 +- .../firestore/test/lite/integration.test.ts | 101 ++++++++---------- 16 files changed, 145 insertions(+), 113 deletions(-) diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 0837b8397ce..1fa00b2e43a 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -138,7 +138,7 @@ export class Firestore { export interface FirestoreDataConverter { fromFirestore(snapshot: QueryDocumentSnapshot): T; toFirestore(modelObject: WithFieldValue): DocumentData; - toFirestore(modelObject: NestedPartial, options: SetOptions): DocumentData; + toFirestore(modelObject: PartialWithFieldValue, options: SetOptions): DocumentData; } // @public @@ -187,11 +187,6 @@ export function limitToLast(limit: number): QueryConstraint; export { LogLevel } -// @public -export type NestedPartial = T extends Primitive ? T : T extends Map ? Map, NestedPartial> : T extends {} ? { - [K in keyof T]?: NestedPartial | FieldValue; -} : Partial; - // @public export type NestedUpdateFields> = UnionToIntersection<{ [K in keyof T & string]: T[K] extends Record ? AddPrefixToKeys> : never; @@ -203,6 +198,11 @@ export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDir // @public export type OrderByDirection = 'desc' | 'asc'; +// @public +export type PartialWithFieldValue = T extends Primitive ? T : T extends Map ? Map, PartialWithFieldValue> : T extends {} ? { + [K in keyof T]?: PartialWithFieldValue | FieldValue; +} : Partial; + // @public export type Primitive = string | number | boolean | undefined | null; @@ -258,7 +258,7 @@ export function serverTimestamp(): FieldValue; export function setDoc(reference: DocumentReference, data: WithFieldValue): Promise; // @public -export function setDoc(reference: DocumentReference, data: NestedPartial, options: SetOptions): Promise; +export function setDoc(reference: DocumentReference, data: PartialWithFieldValue, options: SetOptions): Promise; // @public export function setLogLevel(logLevel: LogLevel): void; @@ -321,7 +321,7 @@ export class Transaction { delete(documentRef: DocumentReference): this; get(documentRef: DocumentReference): Promise>; set(documentRef: DocumentReference, data: WithFieldValue): this; - set(documentRef: DocumentReference, data: NestedPartial, options: SetOptions): this; + set(documentRef: DocumentReference, data: PartialWithFieldValue, options: SetOptions): this; update(documentRef: DocumentReference, data: UpdateData): this; update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): this; } @@ -356,7 +356,7 @@ export class WriteBatch { commit(): Promise; delete(documentRef: DocumentReference): WriteBatch; set(documentRef: DocumentReference, data: WithFieldValue): WriteBatch; - set(documentRef: DocumentReference, data: NestedPartial, options: SetOptions): WriteBatch; + set(documentRef: DocumentReference, data: PartialWithFieldValue, options: SetOptions): WriteBatch; update(documentRef: DocumentReference, data: UpdateData): WriteBatch; update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): WriteBatch; } diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index d6ec1ff695e..8e0adf56864 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -168,7 +168,7 @@ export class Firestore { export interface FirestoreDataConverter { fromFirestore(snapshot: QueryDocumentSnapshot, options?: SnapshotOptions): T; toFirestore(modelObject: WithFieldValue): DocumentData; - toFirestore(modelObject: NestedPartial, options: SetOptions): DocumentData; + toFirestore(modelObject: PartialWithFieldValue, options: SetOptions): DocumentData; } // @public @@ -261,11 +261,6 @@ export { LogLevel } // @public export function namedQuery(firestore: Firestore, name: string): Promise; -// @public -export type NestedPartial = T extends Primitive ? T : T extends Map ? Map, NestedPartial> : T extends {} ? { - [K in keyof T]?: NestedPartial | FieldValue; -} : Partial; - // @public export type NestedUpdateFields> = UnionToIntersection<{ [K in keyof T & string]: T[K] extends Record ? AddPrefixToKeys> : never; @@ -327,6 +322,11 @@ export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDir // @public export type OrderByDirection = 'desc' | 'asc'; +// @public +export type PartialWithFieldValue = T extends Primitive ? T : T extends Map ? Map, PartialWithFieldValue> : T extends {} ? { + [K in keyof T]?: PartialWithFieldValue | FieldValue; +} : Partial; + // @public export interface PersistenceSettings { forceOwnership?: boolean; @@ -389,7 +389,7 @@ export function serverTimestamp(): FieldValue; export function setDoc(reference: DocumentReference, data: WithFieldValue): Promise; // @public -export function setDoc(reference: DocumentReference, data: NestedPartial, options: SetOptions): Promise; +export function setDoc(reference: DocumentReference, data: PartialWithFieldValue, options: SetOptions): Promise; // @public export function setLogLevel(logLevel: LogLevel): void; @@ -465,7 +465,7 @@ export class Transaction { delete(documentRef: DocumentReference): this; get(documentRef: DocumentReference): Promise>; set(documentRef: DocumentReference, data: WithFieldValue): this; - set(documentRef: DocumentReference, data: NestedPartial, options: SetOptions): this; + set(documentRef: DocumentReference, data: PartialWithFieldValue, options: SetOptions): this; update(documentRef: DocumentReference, data: UpdateData): this; update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): this; } @@ -508,7 +508,7 @@ export class WriteBatch { commit(): Promise; delete(documentRef: DocumentReference): WriteBatch; set(documentRef: DocumentReference, data: WithFieldValue): WriteBatch; - set(documentRef: DocumentReference, data: NestedPartial, options: SetOptions): WriteBatch; + set(documentRef: DocumentReference, data: PartialWithFieldValue, options: SetOptions): WriteBatch; update(documentRef: DocumentReference, data: UpdateData): WriteBatch; update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): WriteBatch; } diff --git a/packages/firestore/exp/api.ts b/packages/firestore/exp/api.ts index 5b40554c73c..8e0d4d74129 100644 --- a/packages/firestore/exp/api.ts +++ b/packages/firestore/exp/api.ts @@ -68,7 +68,7 @@ export { WithFieldValue, NestedUpdateFields, AddPrefixToKeys, - NestedPartial, + PartialWithFieldValue, UnionToIntersection, refEqual, queryEqual diff --git a/packages/firestore/lite/index.ts b/packages/firestore/lite/index.ts index 39e34eca406..3862f252c64 100644 --- a/packages/firestore/lite/index.ts +++ b/packages/firestore/lite/index.ts @@ -44,7 +44,7 @@ export { WithFieldValue, NestedUpdateFields, AddPrefixToKeys, - NestedPartial, + PartialWithFieldValue, UnionToIntersection, SetOptions, DocumentReference, diff --git a/packages/firestore/src/api/database.ts b/packages/firestore/src/api/database.ts index 0a52163ed3b..d8e4847233e 100644 --- a/packages/firestore/src/api/database.ts +++ b/packages/firestore/src/api/database.ts @@ -106,7 +106,7 @@ import { AbstractUserDataWriter } from '../../exp/index'; // import from the exp public API import { DatabaseId } from '../core/database_info'; -import { NestedPartial, WithFieldValue } from '../lite/reference'; +import { PartialWithFieldValue, WithFieldValue } from '../lite/reference'; import { UntypedFirestoreDataConverter } from '../lite/user_data_reader'; import { DocumentKey } from '../model/document_key'; import { FieldPath, ResourcePath } from '../model/path'; @@ -453,7 +453,7 @@ export class Transaction implements PublicTransaction, Compat { const ref = castReference(documentRef); if (options) { validateSetOptions('Transaction.set', options); - this._delegate.set(ref, data as NestedPartial, options); + this._delegate.set(ref, data as PartialWithFieldValue, options); } else { this._delegate.set(ref, data as WithFieldValue); } @@ -514,7 +514,7 @@ export class WriteBatch implements PublicWriteBatch, Compat { const ref = castReference(documentRef); if (options) { validateSetOptions('WriteBatch.set', options); - this._delegate.set(ref, data as NestedPartial, options); + this._delegate.set(ref, data as PartialWithFieldValue, options); } else { this._delegate.set(ref, data as WithFieldValue); } @@ -600,11 +600,11 @@ class FirestoreDataConverter toFirestore(modelObject: WithFieldValue): PublicDocumentData; toFirestore( - modelObject: NestedPartial, + modelObject: PartialWithFieldValue, options: PublicSetOptions ): PublicDocumentData; toFirestore( - modelObject: WithFieldValue | NestedPartial, + modelObject: WithFieldValue | PartialWithFieldValue, options?: PublicSetOptions ): PublicDocumentData { if (!options) { @@ -735,7 +735,11 @@ export class DocumentReference options = validateSetOptions('DocumentReference.set', options); try { if (options) { - return setDoc(this._delegate, value as NestedPartial, options); + return setDoc( + this._delegate, + value as PartialWithFieldValue, + options + ); } else { return setDoc(this._delegate, value as WithFieldValue); } diff --git a/packages/firestore/src/exp/reference.ts b/packages/firestore/src/exp/reference.ts index 4230ed473a0..f6424bf9bc8 100644 --- a/packages/firestore/src/exp/reference.ts +++ b/packages/firestore/src/exp/reference.ts @@ -30,7 +30,7 @@ export { WithFieldValue, NestedUpdateFields, AddPrefixToKeys, - NestedPartial, + PartialWithFieldValue, UnionToIntersection, refEqual } from '../lite/reference'; diff --git a/packages/firestore/src/exp/reference_impl.ts b/packages/firestore/src/exp/reference_impl.ts index 84623bd9f8f..15822c4d553 100644 --- a/packages/firestore/src/exp/reference_impl.ts +++ b/packages/firestore/src/exp/reference_impl.ts @@ -42,7 +42,7 @@ import { CollectionReference, doc, DocumentReference, - NestedPartial, + PartialWithFieldValue, Query, SetOptions, UpdateData, @@ -260,12 +260,12 @@ export function setDoc( */ export function setDoc( reference: DocumentReference, - data: NestedPartial, + data: PartialWithFieldValue, options: SetOptions ): Promise; export function setDoc( reference: DocumentReference, - data: WithFieldValue | NestedPartial, + data: PartialWithFieldValue, options?: SetOptions ): Promise { reference = cast>(reference, DocumentReference); diff --git a/packages/firestore/src/exp/snapshot.ts b/packages/firestore/src/exp/snapshot.ts index a1c7ab32071..17529d233dc 100644 --- a/packages/firestore/src/exp/snapshot.ts +++ b/packages/firestore/src/exp/snapshot.ts @@ -20,7 +20,7 @@ import { ChangeType, ViewSnapshot } from '../core/view_snapshot'; import { FieldPath } from '../lite/field_path'; import { DocumentData, - NestedPartial, + PartialWithFieldValue, Query, queryEqual, SetOptions, @@ -59,7 +59,7 @@ import { SnapshotListenOptions } from './reference_impl'; * } * * const postConverter = { - * toFirestore(post: Post): firebase.firestore.DocumentData { + * toFirestore(post: WithFieldValue): firebase.firestore.DocumentData { * return {title: post.title, author: post.author}; * }, * fromFirestore( @@ -89,7 +89,10 @@ export interface FirestoreDataConverter * Called by the Firestore SDK to convert a custom model object of type `T` * into a plain JavaScript object (suitable for writing directly to the * Firestore database). To use `set()` with `merge` and `mergeFields`, - * `toFirestore()` must be defined with `Partial`. + * `toFirestore()` must be defined with `PartialWithFieldValue`. + * + * The `WithFieldValue` type extends `T` to also allow FieldValues such + * {@link (deleteField:1)} to be used as property values. */ toFirestore(modelObject: WithFieldValue): DocumentData; @@ -98,8 +101,16 @@ export interface FirestoreDataConverter * into a plain JavaScript object (suitable for writing directly to the * Firestore database). Used with {@link (setDoc:1)}, {@link (WriteBatch.set:1)} * and {@link (Transaction.set:1)} with `merge:true` or `mergeFields`. + * + * The `PartialWithFieldValue` type extends `Partial` to allow + * FieldValues such {@link (arrayUnion:1)} to be used as property values. + * It also supports nested `Partial` by allowing nested fields to be + * omitted. */ - toFirestore(modelObject: NestedPartial, options: SetOptions): DocumentData; + toFirestore( + modelObject: PartialWithFieldValue, + options: SetOptions + ): DocumentData; /** * Called by the Firestore SDK to convert Firestore data into an object of diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite/reference.ts index 89ed441ed27..f474975880b 100644 --- a/packages/firestore/src/lite/reference.ts +++ b/packages/firestore/src/lite/reference.ts @@ -56,12 +56,12 @@ export type Primitive = string | number | boolean | undefined | null; * Similar to Typescript's `Partial`, but allows nested fields to be * omitted and FieldValues to be passed in as property values. */ -export type NestedPartial = T extends Primitive +export type PartialWithFieldValue = T extends Primitive ? T : T extends Map - ? Map, NestedPartial> + ? Map, PartialWithFieldValue> : T extends {} - ? { [K in keyof T]?: NestedPartial | FieldValue } + ? { [K in keyof T]?: PartialWithFieldValue | FieldValue } : Partial; /** @@ -75,9 +75,9 @@ export type WithFieldValue = T extends Primitive : Partial; /** - * Update data (for use with {@link @firebase/firestore/lite#(updateDoc:1)}) - * that consists of field paths (e.g. 'foo' or 'foo.baz') mapped to values. - * Fields that contain dots reference nested fields within the document. + * Update data (for use with {@link (setDoc:1)}) that consists of field paths + * (e.g. 'foo' or 'foo.baz') mapped to values. Fields that contain dots + * reference nested fields within the document. */ export type UpdateData = T extends Primitive ? T diff --git a/packages/firestore/src/lite/reference_impl.ts b/packages/firestore/src/lite/reference_impl.ts index 663ee0551d7..bba1eeb900c 100644 --- a/packages/firestore/src/lite/reference_impl.ts +++ b/packages/firestore/src/lite/reference_impl.ts @@ -41,7 +41,7 @@ import { CollectionReference, doc, DocumentReference, - NestedPartial, + PartialWithFieldValue, Query, SetOptions, UpdateData, @@ -73,7 +73,7 @@ import { AbstractUserDataWriter } from './user_data_writer'; */ export function applyFirestoreDataConverter( converter: UntypedFirestoreDataConverter | null, - value: WithFieldValue | NestedPartial, + value: WithFieldValue | PartialWithFieldValue, options?: PublicSetOptions ): PublicDocumentData { let convertedValue; @@ -219,18 +219,18 @@ export function setDoc( */ export function setDoc( reference: DocumentReference, - data: NestedPartial, + data: PartialWithFieldValue, options: SetOptions ): Promise; export function setDoc( reference: DocumentReference, - data: WithFieldValue | NestedPartial, + data: PartialWithFieldValue, options?: SetOptions ): Promise { reference = cast>(reference, DocumentReference); const convertedValue = applyFirestoreDataConverter( reference.converter, - data as WithFieldValue, + data, options ); const dataReader = newUserDataReader(reference.firestore); @@ -380,7 +380,10 @@ export function addDoc( reference = cast>(reference, CollectionReference); const docRef = doc(reference); - const convertedValue = applyFirestoreDataConverter(reference.converter, data); + const convertedValue = applyFirestoreDataConverter( + reference.converter, + data as PartialWithFieldValue + ); const dataReader = newUserDataReader(reference.firestore); const parsed = parseSetData( diff --git a/packages/firestore/src/lite/snapshot.ts b/packages/firestore/src/lite/snapshot.ts index 22bc4e499dd..70e98c43f4c 100644 --- a/packages/firestore/src/lite/snapshot.ts +++ b/packages/firestore/src/lite/snapshot.ts @@ -27,7 +27,7 @@ import { FieldPath } from './field_path'; import { DocumentData, DocumentReference, - NestedPartial, + PartialWithFieldValue, Query, queryEqual, SetOptions, @@ -57,7 +57,7 @@ import { AbstractUserDataWriter } from './user_data_writer'; * } * * const postConverter = { - * toFirestore(post: Post): firebase.firestore.DocumentData { + * toFirestore(post: WithFieldValue): firebase.firestore.DocumentData { * return {title: post.title, author: post.author}; * }, * fromFirestore(snapshot: firebase.firestore.QueryDocumentSnapshot): Post { @@ -84,6 +84,9 @@ export interface FirestoreDataConverter { * into a plain Javascript object (suitable for writing directly to the * Firestore database). Used with {@link @firebase/firestore/lite#(setDoc:1)}, {@link @firebase/firestore/lite#(WriteBatch.set:1)} * and {@link @firebase/firestore/lite#(Transaction.set:1)}. + * + * The `WithFieldValue` type extends `T` to also allow FieldValues such + * {@link (deleteField:1)} to be used as property values. */ toFirestore(modelObject: WithFieldValue): DocumentData; @@ -92,8 +95,16 @@ export interface FirestoreDataConverter { * into a plain Javascript object (suitable for writing directly to the * Firestore database). Used with {@link @firebase/firestore/lite#(setDoc:1)}, {@link @firebase/firestore/lite#(WriteBatch.set:1)} * and {@link @firebase/firestore/lite#(Transaction.set:1)} with `merge:true` or `mergeFields`. + * + * The `PartialWithFieldValue` type extends `Partial` to allow + * FieldValues such {@link (arrayUnion:1)} to be used as property values. + * It also supports nested `Partial` by allowing nested fields to be + * omitted. */ - toFirestore(modelObject: NestedPartial, options: SetOptions): DocumentData; + toFirestore( + modelObject: PartialWithFieldValue, + options: SetOptions + ): DocumentData; /** * Called by the Firestore SDK to convert Firestore data into an object of diff --git a/packages/firestore/src/lite/transaction.ts b/packages/firestore/src/lite/transaction.ts index 738d1bd3523..aadba43dc63 100644 --- a/packages/firestore/src/lite/transaction.ts +++ b/packages/firestore/src/lite/transaction.ts @@ -29,7 +29,7 @@ import { Firestore } from './database'; import { FieldPath } from './field_path'; import { DocumentReference, - NestedPartial, + PartialWithFieldValue, SetOptions, UpdateData, WithFieldValue @@ -134,12 +134,12 @@ export class Transaction { */ set( documentRef: DocumentReference, - data: NestedPartial, + data: PartialWithFieldValue, options: SetOptions ): this; set( documentRef: DocumentReference, - value: WithFieldValue | NestedPartial, + value: PartialWithFieldValue, options?: SetOptions ): this { const ref = validateReference(documentRef, this._firestore); diff --git a/packages/firestore/src/lite/user_data_reader.ts b/packages/firestore/src/lite/user_data_reader.ts index 1091ef15e8e..c5fbb152872 100644 --- a/packages/firestore/src/lite/user_data_reader.ts +++ b/packages/firestore/src/lite/user_data_reader.ts @@ -63,7 +63,11 @@ import { Firestore } from './database'; import { FieldPath } from './field_path'; import { FieldValue } from './field_value'; import { GeoPoint } from './geo_point'; -import { DocumentReference, NestedPartial, WithFieldValue } from './reference'; +import { + DocumentReference, + PartialWithFieldValue, + WithFieldValue +} from './reference'; import { Timestamp } from './timestamp'; const RESERVED_FIELD_REGEX = /^__.*__$/; @@ -74,7 +78,10 @@ const RESERVED_FIELD_REGEX = /^__.*__$/; */ export interface UntypedFirestoreDataConverter { toFirestore(modelObject: WithFieldValue): DocumentData; - toFirestore(modelObject: NestedPartial, options: SetOptions): DocumentData; + toFirestore( + modelObject: PartialWithFieldValue, + options: SetOptions + ): DocumentData; fromFirestore(snapshot: unknown, options?: unknown): T; } diff --git a/packages/firestore/src/lite/write_batch.ts b/packages/firestore/src/lite/write_batch.ts index 088fe7da1bf..0f3d9a2c5f6 100644 --- a/packages/firestore/src/lite/write_batch.ts +++ b/packages/firestore/src/lite/write_batch.ts @@ -27,7 +27,7 @@ import { Firestore } from './database'; import { FieldPath } from './field_path'; import { DocumentReference, - NestedPartial, + PartialWithFieldValue, SetOptions, UpdateData, WithFieldValue @@ -90,12 +90,12 @@ export class WriteBatch { */ set( documentRef: DocumentReference, - data: NestedPartial, + data: PartialWithFieldValue, options: SetOptions ): WriteBatch; set( documentRef: DocumentReference, - data: WithFieldValue | NestedPartial, + data: WithFieldValue | PartialWithFieldValue, options?: SetOptions ): WriteBatch { this._verifyNotCommitted(); diff --git a/packages/firestore/test/lite/helpers.ts b/packages/firestore/test/lite/helpers.ts index a41a1412078..964592f5621 100644 --- a/packages/firestore/test/lite/helpers.ts +++ b/packages/firestore/test/lite/helpers.ts @@ -27,7 +27,7 @@ import { CollectionReference, DocumentReference, SetOptions, - NestedPartial + PartialWithFieldValue } from '../../src/lite/reference'; import { setDoc } from '../../src/lite/reference_impl'; import { FirestoreSettings } from '../../src/lite/settings'; @@ -124,7 +124,10 @@ export const postConverter = { }; export const postConverterMerge = { - toFirestore(post: NestedPartial, options?: SetOptions): DocumentData { + toFirestore( + post: PartialWithFieldValue, + options?: SetOptions + ): DocumentData { if ( options && ((options as { merge: true }).merge || diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 9420501030a..7ba28a0f21c 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -58,7 +58,7 @@ import { SetOptions, DocumentData, WithFieldValue, - NestedPartial, + PartialWithFieldValue, UpdateData } from '../../src/lite/reference'; import { @@ -349,7 +349,7 @@ interface MutationTester { ): Promise; set( documentRef: DocumentReference, - data: NestedPartial, + data: PartialWithFieldValue, options: SetOptions ): Promise; update( @@ -381,7 +381,7 @@ describe('WriteBatch', () => { set( ref: DocumentReference, - data: WithFieldValue | NestedPartial, + data: PartialWithFieldValue, options?: SetOptions ): Promise { const batch = writeBatch(ref.firestore); @@ -444,12 +444,12 @@ describe('Transaction', () => { set( ref: DocumentReference, - data: WithFieldValue | NestedPartial, + data: PartialWithFieldValue, options?: SetOptions ): Promise { return runTransaction(ref.firestore, async transaction => { if (options) { - transaction.set(ref, data as NestedPartial, options); + transaction.set(ref, data, options); } else { transaction.set(ref, data as WithFieldValue); } @@ -1258,23 +1258,24 @@ describe('withConverter() support', () => { } }; - function setBaseDoc(ref: DocumentReference): Promise { - return setDoc(ref, { - outerString: 'foo', - outerArr: [], - nested: { - innerNested: { - innerNestedNum: 2 - }, - innerArr: arrayUnion(2), - timestamp: serverTimestamp() - } - }); - } + const initialData = { + outerString: 'foo', + outerArr: [], + nested: { + innerNested: { + innerNestedNum: 2 + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }; describe('NestedPartial', () => { const testConverterMerge = { - toFirestore(testObj: NestedPartial, options?: SetOptions) { + toFirestore( + testObj: PartialWithFieldValue, + options?: SetOptions + ) { return { ...testObj }; }, fromFirestore(snapshot: QueryDocumentSnapshot): TestObject { @@ -1496,17 +1497,9 @@ describe('withConverter() support', () => { }); describe('UpdateData', () => { - // Skip test. This is just here to just make sure things compile. - // eslint-disable-next-line no-restricted-properties - it('supports FieldValues', () => { - return withTestDb(async db => { - const ref = doc(collection(db, 'testobj')).withConverter( - testConverter - ); - await setBaseDoc(ref); - - await updateDoc(ref, { + return withTestDocAndInitialData(initialData, async docRef => { + await updateDoc(docRef.withConverter(testConverter), { outerString: deleteField(), nested: { innerNested: { @@ -1519,13 +1512,8 @@ describe('withConverter() support', () => { }); it('validates inner and outer fields', async () => { - return withTestDb(async db => { - const ref = doc(collection(db, 'testobj')).withConverter( - testConverter - ); - await setBaseDoc(ref); - - await updateDoc(ref, { + return withTestDocAndInitialData(initialData, async docRef => { + await updateDoc(docRef.withConverter(testConverter), { // @ts-expect-error outerString: 3, nested: { @@ -1541,13 +1529,10 @@ describe('withConverter() support', () => { }); it('supports string-separated fields', () => { - return withTestDb(async db => { - const ref = doc(collection(db, 'testobj')).withConverter( - testConverter - ); - await setBaseDoc(ref); - - await updateDoc(ref, { + return withTestDocAndInitialData(initialData, async docRef => { + const testDocRef: DocumentReference = + docRef.withConverter(testConverter); + await updateDoc(testDocRef, { // @ts-expect-error outerString: 3, // @ts-expect-error @@ -1558,7 +1543,7 @@ describe('withConverter() support', () => { }); // String comprehension works in nested fields. - await updateDoc(ref, { + await updateDoc(testDocRef, { nested: { innerNested: { // @ts-expect-error @@ -1572,20 +1557,18 @@ describe('withConverter() support', () => { }); it('checks for nonexistent fields', () => { - return withTestDb(async db => { - const ref = doc(collection(db, 'testobj')).withConverter( - testConverter - ); - await setBaseDoc(ref); + return withTestDocAndInitialData(initialData, async docRef => { + const testDocRef: DocumentReference = + docRef.withConverter(testConverter); // Top-level fields. - await updateDoc(ref, { + await updateDoc(testDocRef, { // @ts-expect-error nonexistent: 'foo' }); // Nested Fields. - await updateDoc(ref, { + await updateDoc(testDocRef, { nested: { // @ts-expect-error nonexistent: 'foo' @@ -1593,11 +1576,11 @@ describe('withConverter() support', () => { }); // String fields. - await updateDoc(ref, { + await updateDoc(testDocRef, { // @ts-expect-error 'nonexistent': 'foo' }); - await updateDoc(ref, { + await updateDoc(testDocRef, { // @ts-expect-error 'nested.nonexistent': 'foo' }); @@ -1723,7 +1706,17 @@ describe('withConverter() support', () => { const ref = doc(collection(db, 'testobj')).withConverter( testConverter ); - await setBaseDoc(ref); + await setDoc(ref, { + outerString: 'foo', + outerArr: [], + nested: { + innerNested: { + innerNestedNum: 2 + }, + innerArr: arrayUnion(2), + timestamp: serverTimestamp() + } + }); return runTransaction(db, async tx => { tx.update(ref, { From cf41539f6b5f6c6aa9c1c14cbd2b8a9b9c179893 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Tue, 17 Aug 2021 14:10:18 -0500 Subject: [PATCH 16/22] move helpers types to separate file --- common/api-review/firestore-lite.api.md | 8 +-- common/api-review/firestore.api.md | 8 +-- packages/firestore/exp/api.ts | 11 ++-- packages/firestore/lite/index.ts | 11 ++-- packages/firestore/src/exp/reference.ts | 4 -- packages/firestore/src/lite/reference.ts | 57 +------------------- packages/firestore/src/lite/types.ts | 66 ++++++++++++++++++++++++ 7 files changed, 89 insertions(+), 76 deletions(-) create mode 100644 packages/firestore/src/lite/types.ts diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 1fa00b2e43a..69f0c0d5e13 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -12,7 +12,7 @@ import { LogLevelString as LogLevel } from '@firebase/logger'; export function addDoc(reference: CollectionReference, data: WithFieldValue): Promise>; // @public -export type AddPrefixToKeys> = { +export type AddPrefixToKeys> = { [K in keyof T & string as `${Prefix}.${K}`]+?: T[K]; }; @@ -188,8 +188,8 @@ export function limitToLast(limit: number): QueryConstraint; export { LogLevel } // @public -export type NestedUpdateFields> = UnionToIntersection<{ - [K in keyof T & string]: T[K] extends Record ? AddPrefixToKeys> : never; +export type NestedUpdateFields> = UnionToIntersection<{ + [K in keyof T & string]: T[K] extends Record ? AddPrefixToKeys> : never; }[keyof T & string]>; // @public @@ -327,7 +327,7 @@ export class Transaction { } // @public -export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; +export type UnionToIntersection = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; // @public export type UpdateData = T extends Primitive ? T : T extends Map ? Map, UpdateData> : T extends {} ? { diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 8e0adf56864..fe04d3ef4df 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -12,7 +12,7 @@ import { LogLevelString as LogLevel } from '@firebase/logger'; export function addDoc(reference: CollectionReference, data: WithFieldValue): Promise>; // @public -export type AddPrefixToKeys> = { +export type AddPrefixToKeys> = { [K in keyof T & string as `${Prefix}.${K}`]+?: T[K]; }; @@ -262,8 +262,8 @@ export { LogLevel } export function namedQuery(firestore: Firestore, name: string): Promise; // @public -export type NestedUpdateFields> = UnionToIntersection<{ - [K in keyof T & string]: T[K] extends Record ? AddPrefixToKeys> : never; +export type NestedUpdateFields> = UnionToIntersection<{ + [K in keyof T & string]: T[K] extends Record ? AddPrefixToKeys> : never; }[keyof T & string]>; // @public @@ -471,7 +471,7 @@ export class Transaction { } // @public -export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; +export type UnionToIntersection = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; // @public export interface Unsubscribe { diff --git a/packages/firestore/exp/api.ts b/packages/firestore/exp/api.ts index 8e0d4d74129..f5a0873609e 100644 --- a/packages/firestore/exp/api.ts +++ b/packages/firestore/exp/api.ts @@ -64,12 +64,8 @@ export { SetOptions, DocumentData, UpdateData, - Primitive, WithFieldValue, - NestedUpdateFields, - AddPrefixToKeys, PartialWithFieldValue, - UnionToIntersection, refEqual, queryEqual } from '../src/exp/reference'; @@ -135,3 +131,10 @@ export { CACHE_SIZE_UNLIMITED } from '../src/exp/database'; export { FirestoreErrorCode, FirestoreError } from '../src/util/error'; export { AbstractUserDataWriter } from '../src/lite/user_data_writer'; + +export { + Primitive, + NestedUpdateFields, + AddPrefixToKeys, + UnionToIntersection +} from '../src/lite/types'; diff --git a/packages/firestore/lite/index.ts b/packages/firestore/lite/index.ts index 3862f252c64..eb4ceef313a 100644 --- a/packages/firestore/lite/index.ts +++ b/packages/firestore/lite/index.ts @@ -40,12 +40,8 @@ export { export { DocumentData, UpdateData, - Primitive, WithFieldValue, - NestedUpdateFields, - AddPrefixToKeys, PartialWithFieldValue, - UnionToIntersection, SetOptions, DocumentReference, Query, @@ -82,6 +78,13 @@ export { getDocs } from '../src/lite/reference_impl'; +export { + Primitive, + NestedUpdateFields, + AddPrefixToKeys, + UnionToIntersection +} from '../src/lite/types'; + // TOOD(firestorelite): Add tests when Queries are usable export { FieldPath, documentId } from '../src/lite/field_path'; diff --git a/packages/firestore/src/exp/reference.ts b/packages/firestore/src/exp/reference.ts index f6424bf9bc8..735075fae76 100644 --- a/packages/firestore/src/exp/reference.ts +++ b/packages/firestore/src/exp/reference.ts @@ -26,11 +26,7 @@ export { SetOptions, DocumentData, UpdateData, - Primitive, WithFieldValue, - NestedUpdateFields, - AddPrefixToKeys, PartialWithFieldValue, - UnionToIntersection, refEqual } from '../lite/reference'; diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite/reference.ts index f474975880b..4081600fd1c 100644 --- a/packages/firestore/src/lite/reference.ts +++ b/packages/firestore/src/lite/reference.ts @@ -38,6 +38,7 @@ import { Firestore } from './database'; import { FieldPath } from './field_path'; import { FieldValue } from './field_value'; import { FirestoreDataConverter } from './snapshot'; +import { NestedUpdateFields, Primitive } from './types'; /** * Document data (for use with {@link @firebase/firestore/lite#(setDoc:1)}) consists of fields mapped to @@ -49,9 +50,6 @@ export interface DocumentData { [field: string]: any; } -/** Primitive types. */ -export type Primitive = string | number | boolean | undefined | null; - /** * Similar to Typescript's `Partial`, but allows nested fields to be * omitted and FieldValues to be passed in as property values. @@ -87,59 +85,6 @@ export type UpdateData = T extends Primitive ? { [K in keyof T]?: UpdateData | FieldValue } & NestedUpdateFields : Partial; -/** - * For each field (e.g. 'bar'), find all nested keys (e.g. {'bar.baz': T1, - * 'bar.qux': T2}). Intersect them together to make a single map containing - * all possible keys that are all marked as optional - */ -// Mapping between a field and its value. -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type NestedUpdateFields> = - UnionToIntersection< - { - // Check that T[K] extends Record to only allow nesting for map values. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [K in keyof T & string]: T[K] extends Record - ? // Recurse into the map and add the prefix in front of each key - // (e.g. Prefix 'bar.' to create: 'bar.baz' and 'bar.qux'. - AddPrefixToKeys> - : // TypedUpdateData is always a map of values. - never; - }[keyof T & string] // Also include the generated prefix-string keys. - >; - -/** - * Returns a new map where every key is prefixed with the outer key appended - * to a dot. - */ -// Mapping between a field and its value. -export type AddPrefixToKeys< - Prefix extends string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - T extends Record -> = - // Remap K => Prefix.K. See https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#key-remapping-via-as - { [K in keyof T & string as `${Prefix}.${K}`]+?: T[K] }; - -/** - * Given a union type `U = T1 | T2 | ...`, returns an intersected type - * `(T1 & T2 & ...)`. - * - * Uses distributive conditional types and inference from conditional types. - * This works because multiple candidates for the same type variable in - * contra-variant positions causes an intersection type to be inferred. - * https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-inference-in-conditional-types - * https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type - */ -export type UnionToIntersection = ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - U extends any - ? (k: U) => void - : never -) extends (k: infer I) => void - ? I - : never; - /** * An options object that configures the behavior of {@link @firebase/firestore/lite#(setDoc:1)}, {@link * @firebase/firestore/lite#(WriteBatch.set:1)} and {@link @firebase/firestore/lite#(Transaction.set:1)} calls. These calls can be diff --git a/packages/firestore/src/lite/types.ts b/packages/firestore/src/lite/types.ts new file mode 100644 index 00000000000..7f565b1382b --- /dev/null +++ b/packages/firestore/src/lite/types.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { UpdateData } from './reference'; + +/** Primitive types. */ +export type Primitive = string | number | boolean | undefined | null; + +/** + * For each field (e.g. 'bar'), find all nested keys (e.g. {'bar.baz': T1, + * 'bar.qux': T2}). Intersect them together to make a single map containing + * all possible keys that are all marked as optional + */ +export type NestedUpdateFields> = + UnionToIntersection< + { + // Check that T[K] extends Record to only allow nesting for map values. + [K in keyof T & string]: T[K] extends Record + ? // Recurse into the map and add the prefix in front of each key + // (e.g. Prefix 'bar.' to create: 'bar.baz' and 'bar.qux'. + AddPrefixToKeys> + : // TypedUpdateData is always a map of values. + never; + }[keyof T & string] // Also include the generated prefix-string keys. + >; + +/** + * Returns a new map where every key is prefixed with the outer key appended + * to a dot. + */ +export type AddPrefixToKeys< + Prefix extends string, + T extends Record +> = + // Remap K => Prefix.K. See https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#key-remapping-via-as + { [K in keyof T & string as `${Prefix}.${K}`]+?: T[K] }; + +/** + * Given a union type `U = T1 | T2 | ...`, returns an intersected type + * `(T1 & T2 & ...)`. + * + * Uses distributive conditional types and inference from conditional types. + * This works because multiple candidates for the same type variable in + * contra-variant positions causes an intersection type to be inferred. + * https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-inference-in-conditional-types + * https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type + */ +export type UnionToIntersection = ( + U extends unknown ? (k: U) => void : never +) extends (k: infer I) => void + ? I + : never; From a2df21bb852fce517f65dc66790071766f21461a Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Tue, 17 Aug 2021 14:13:13 -0500 Subject: [PATCH 17/22] add changeset --- .changeset/dirty-pandas-pay.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/dirty-pandas-pay.md diff --git a/.changeset/dirty-pandas-pay.md b/.changeset/dirty-pandas-pay.md new file mode 100644 index 00000000000..583cb38b78d --- /dev/null +++ b/.changeset/dirty-pandas-pay.md @@ -0,0 +1,6 @@ +--- +'firebase': major +'@firebase/firestore': major +--- + +Added support for FieldValues when using a FirestoreDataConverter and support for update() calls with a converter From c11e067b0bb60df5124399537789d12822dfbb59 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Tue, 17 Aug 2021 16:58:16 -0500 Subject: [PATCH 18/22] update comments and changeset --- .changeset/dirty-pandas-pay.md | 6 ++- packages/firestore/src/lite/reference.ts | 3 +- packages/firestore/src/lite/types.ts | 6 +++ .../firestore/test/lite/integration.test.ts | 46 +++++++------------ 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/.changeset/dirty-pandas-pay.md b/.changeset/dirty-pandas-pay.md index 583cb38b78d..50ac32da963 100644 --- a/.changeset/dirty-pandas-pay.md +++ b/.changeset/dirty-pandas-pay.md @@ -3,4 +3,8 @@ '@firebase/firestore': major --- -Added support for FieldValues when using a FirestoreDataConverter and support for update() calls with a converter +This change contains multiple quality-of-life improvements when using the `FirestoreDataConverter` in `@firebase/firestore/lite` and `@firebase/firestore/exp`: +- Support for passing in `FieldValue` property values when using a converter (via `WithFieldValue` and `PartialWithFieldValue`). +- Support for omitting properties in nested fields when performing a set operation with `{merge: true}` with a converter (via `PartialWithFieldValue`). +- [breaking] Support for typed update operations when using a converter (via the newly typed `UpdateData`). Improperly typed fields in +update operations on typed document references will no longer compile. diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite/reference.ts index 4081600fd1c..39cdd6e5e6d 100644 --- a/packages/firestore/src/lite/reference.ts +++ b/packages/firestore/src/lite/reference.ts @@ -75,7 +75,8 @@ export type WithFieldValue = T extends Primitive /** * Update data (for use with {@link (setDoc:1)}) that consists of field paths * (e.g. 'foo' or 'foo.baz') mapped to values. Fields that contain dots - * reference nested fields within the document. + * reference nested fields within the document. FieldValues can be passed in + * as property values. */ export type UpdateData = T extends Primitive ? T diff --git a/packages/firestore/src/lite/types.ts b/packages/firestore/src/lite/types.ts index 7f565b1382b..63d1b1cc752 100644 --- a/packages/firestore/src/lite/types.ts +++ b/packages/firestore/src/lite/types.ts @@ -17,6 +17,12 @@ import { UpdateData } from './reference'; +/** + * These types primarily exist to support the `UpdateData`, + * `WithFieldValue`, and `PartialWithFieldValue` types and are not consumed + * directly by the end developer. + */ + /** Primitive types. */ export type Primitive = string | number | boolean | undefined | null; diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 7ba28a0f21c..3feafc719ae 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -1285,10 +1285,8 @@ describe('withConverter() support', () => { }; it('supports FieldValues', async () => { - return withTestDb(async db => { - const ref = doc(collection(db, 'testobj')).withConverter( - testConverterMerge - ); + return withTestDoc(async doc => { + const ref = doc.withConverter(testConverter); // Allow Field Values in nested partials. await setDoc( @@ -1318,10 +1316,8 @@ describe('withConverter() support', () => { }); it('validates types in outer and inner fields', async () => { - return withTestDb(async db => { - const ref = doc(collection(db, 'testobj')).withConverter( - testConverterMerge - ); + return withTestDoc(async doc => { + const ref = doc.withConverter(testConverter); // Check top-level fields. await setDoc( @@ -1362,10 +1358,8 @@ describe('withConverter() support', () => { }); it('checks for nonexistent properties', async () => { - return withTestDb(async db => { - const ref = doc(collection(db, 'testobj')).withConverter( - testConverterMerge - ); + return withTestDoc(async doc => { + const ref = doc.withConverter(testConverter); // Top-level property. await setDoc( ref, @@ -1393,10 +1387,8 @@ describe('withConverter() support', () => { describe('WithFieldValue', () => { it('supports FieldValues', async () => { - return withTestDb(async db => { - const ref = doc(collection(db, 'testobj')).withConverter( - testConverter - ); + return withTestDoc(async doc => { + const ref = doc.withConverter(testConverter); // Allow Field Values and nested partials. await setDoc(ref, { @@ -1414,10 +1406,8 @@ describe('withConverter() support', () => { }); it('requires all fields to be present', async () => { - return withTestDb(async db => { - const ref = doc(collection(db, 'testobj')).withConverter( - testConverter - ); + return withTestDoc(async doc => { + const ref = doc.withConverter(testConverter); // Allow Field Values and nested partials. // @ts-expect-error @@ -1435,10 +1425,8 @@ describe('withConverter() support', () => { }); it('validates inner and outer fields', async () => { - return withTestDb(async db => { - const ref = doc(collection(db, 'testobj')).withConverter( - testConverter - ); + return withTestDoc(async doc => { + const ref = doc.withConverter(testConverter); await setDoc(ref, { outerString: 'foo', @@ -1457,10 +1445,8 @@ describe('withConverter() support', () => { }); it('checks for nonexistent properties', async () => { - return withTestDb(async db => { - const ref = doc(collection(db, 'testobj')).withConverter( - testConverter - ); + return withTestDoc(async doc => { + const ref = doc.withConverter(testConverter); // Top-level nonexistent fields should error await setDoc(ref, { @@ -1590,8 +1576,8 @@ describe('withConverter() support', () => { describe('methods', () => { it('addDoc()', () => { - return withTestDb(async db => { - const ref = collection(db, 'testobj').withConverter(testConverter); + return withTestDoc(async doc => { + const ref = doc.withConverter(testConverter); // Requires all fields to be present // @ts-expect-error From 706c0608d88e3a314556c10dec373586e7e4fbaf Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Tue, 17 Aug 2021 18:42:43 -0500 Subject: [PATCH 19/22] some comment changes --- .changeset/dirty-pandas-pay.md | 2 +- packages/firestore/src/lite/reference.ts | 2 -- packages/firestore/test/lite/integration.test.ts | 6 +++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.changeset/dirty-pandas-pay.md b/.changeset/dirty-pandas-pay.md index 50ac32da963..78688258b88 100644 --- a/.changeset/dirty-pandas-pay.md +++ b/.changeset/dirty-pandas-pay.md @@ -3,7 +3,7 @@ '@firebase/firestore': major --- -This change contains multiple quality-of-life improvements when using the `FirestoreDataConverter` in `@firebase/firestore/lite` and `@firebase/firestore/exp`: +This change contains multiple quality-of-life improvements when using the `FirestoreDataConverter` in `@firebase/firestore/lite` and `@firebase/firestore`: - Support for passing in `FieldValue` property values when using a converter (via `WithFieldValue` and `PartialWithFieldValue`). - Support for omitting properties in nested fields when performing a set operation with `{merge: true}` with a converter (via `PartialWithFieldValue`). - [breaking] Support for typed update operations when using a converter (via the newly typed `UpdateData`). Improperly typed fields in diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite/reference.ts index 39cdd6e5e6d..04584437735 100644 --- a/packages/firestore/src/lite/reference.ts +++ b/packages/firestore/src/lite/reference.ts @@ -56,8 +56,6 @@ export interface DocumentData { */ export type PartialWithFieldValue = T extends Primitive ? T - : T extends Map - ? Map, PartialWithFieldValue> : T extends {} ? { [K in keyof T]?: PartialWithFieldValue | FieldValue } : Partial; diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 3feafc719ae..13e4927fe54 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -1286,7 +1286,7 @@ describe('withConverter() support', () => { it('supports FieldValues', async () => { return withTestDoc(async doc => { - const ref = doc.withConverter(testConverter); + const ref = doc.withConverter(testConverterMerge); // Allow Field Values in nested partials. await setDoc( @@ -1317,7 +1317,7 @@ describe('withConverter() support', () => { it('validates types in outer and inner fields', async () => { return withTestDoc(async doc => { - const ref = doc.withConverter(testConverter); + const ref = doc.withConverter(testConverterMerge); // Check top-level fields. await setDoc( @@ -1359,7 +1359,7 @@ describe('withConverter() support', () => { it('checks for nonexistent properties', async () => { return withTestDoc(async doc => { - const ref = doc.withConverter(testConverter); + const ref = doc.withConverter(testConverterMerge); // Top-level property. await setDoc( ref, From a8123d865fb2120f63bad8dbbe1a32e91d75b878 Mon Sep 17 00:00:00 2001 From: thebrianchen Date: Tue, 17 Aug 2021 23:52:30 +0000 Subject: [PATCH 20/22] Update API reports --- common/api-review/firestore-lite.api.md | 2 +- common/api-review/firestore.api.md | 1040 +++++++++++------------ 2 files changed, 521 insertions(+), 521 deletions(-) diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 69f0c0d5e13..0d3d3c0ea18 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -199,7 +199,7 @@ export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDir export type OrderByDirection = 'desc' | 'asc'; // @public -export type PartialWithFieldValue = T extends Primitive ? T : T extends Map ? Map, PartialWithFieldValue> : T extends {} ? { +export type PartialWithFieldValue = T extends Primitive ? T : T extends {} ? { [K in keyof T]?: PartialWithFieldValue | FieldValue; } : Partial; diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index fe04d3ef4df..8d9ba3034ae 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -1,520 +1,520 @@ -## API Report File for "@firebase/firestore" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { EmulatorMockTokenOptions } from '@firebase/util'; -import { FirebaseApp } from '@firebase/app-exp'; -import { LogLevelString as LogLevel } from '@firebase/logger'; - -// @public -export function addDoc(reference: CollectionReference, data: WithFieldValue): Promise>; - -// @public -export type AddPrefixToKeys> = { - [K in keyof T & string as `${Prefix}.${K}`]+?: T[K]; -}; - -// @public -export function arrayRemove(...elements: unknown[]): FieldValue; - -// @public -export function arrayUnion(...elements: unknown[]): FieldValue; - -// @public -export class Bytes { - static fromBase64String(base64: string): Bytes; - static fromUint8Array(array: Uint8Array): Bytes; - isEqual(other: Bytes): boolean; - toBase64(): string; - toString(): string; - toUint8Array(): Uint8Array; -} - -// @public -export const CACHE_SIZE_UNLIMITED = -1; - -// @public -export function clearIndexedDbPersistence(firestore: Firestore): Promise; - -// @public -export function collection(firestore: Firestore, path: string, ...pathSegments: string[]): CollectionReference; - -// @public -export function collection(reference: CollectionReference, path: string, ...pathSegments: string[]): CollectionReference; - -// @public -export function collection(reference: DocumentReference, path: string, ...pathSegments: string[]): CollectionReference; - -// @public -export function collectionGroup(firestore: Firestore, collectionId: string): Query; - -// @public -export class CollectionReference extends Query { - get id(): string; - get parent(): DocumentReference | null; - get path(): string; - readonly type = "collection"; - withConverter(converter: FirestoreDataConverter): CollectionReference; - withConverter(converter: null): CollectionReference; -} - -// @public -export function connectFirestoreEmulator(firestore: Firestore, host: string, port: number, options?: { - mockUserToken?: EmulatorMockTokenOptions | string; -}): void; - -// @public -export function deleteDoc(reference: DocumentReference): Promise; - -// @public -export function deleteField(): FieldValue; - -// @public -export function disableNetwork(firestore: Firestore): Promise; - -// @public -export function doc(firestore: Firestore, path: string, ...pathSegments: string[]): DocumentReference; - -// @public -export function doc(reference: CollectionReference, path?: string, ...pathSegments: string[]): DocumentReference; - -// @public -export function doc(reference: DocumentReference, path: string, ...pathSegments: string[]): DocumentReference; - -// @public -export interface DocumentChange { - readonly doc: QueryDocumentSnapshot; - readonly newIndex: number; - readonly oldIndex: number; - readonly type: DocumentChangeType; -} - -// @public -export type DocumentChangeType = 'added' | 'removed' | 'modified'; - -// @public -export interface DocumentData { - [field: string]: any; -} - -// @public -export function documentId(): FieldPath; - -// @public -export class DocumentReference { - readonly converter: FirestoreDataConverter | null; - readonly firestore: Firestore; - get id(): string; - get parent(): CollectionReference; - get path(): string; - readonly type = "document"; - withConverter(converter: FirestoreDataConverter): DocumentReference; - withConverter(converter: null): DocumentReference; -} - -// @public -export class DocumentSnapshot { - protected constructor(); - data(options?: SnapshotOptions): T | undefined; - exists(): this is QueryDocumentSnapshot; - get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; - get id(): string; - readonly metadata: SnapshotMetadata; - get ref(): DocumentReference; -} - -// @public -export function enableIndexedDbPersistence(firestore: Firestore, persistenceSettings?: PersistenceSettings): Promise; - -// @public -export function enableMultiTabIndexedDbPersistence(firestore: Firestore): Promise; - -// @public -export function enableNetwork(firestore: Firestore): Promise; - -// @public -export function endAt(snapshot: DocumentSnapshot): QueryConstraint; - -// @public -export function endAt(...fieldValues: unknown[]): QueryConstraint; - -// @public -export function endBefore(snapshot: DocumentSnapshot): QueryConstraint; - -// @public -export function endBefore(...fieldValues: unknown[]): QueryConstraint; - -// @public -export class FieldPath { - constructor(...fieldNames: string[]); - isEqual(other: FieldPath): boolean; -} - -// @public -export abstract class FieldValue { - abstract isEqual(other: FieldValue): boolean; -} - -// @public -export class Firestore { - get app(): FirebaseApp; - toJSON(): object; - type: 'firestore-lite' | 'firestore'; -} - -// @public -export interface FirestoreDataConverter { - fromFirestore(snapshot: QueryDocumentSnapshot, options?: SnapshotOptions): T; - toFirestore(modelObject: WithFieldValue): DocumentData; - toFirestore(modelObject: PartialWithFieldValue, options: SetOptions): DocumentData; -} - -// @public -export class FirestoreError extends Error { - readonly code: FirestoreErrorCode; - readonly message: string; - readonly name: string; - readonly stack?: string; -} - -// @public -export type FirestoreErrorCode = 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' | 'data-loss' | 'unauthenticated'; - -// @public -export interface FirestoreSettings { - cacheSizeBytes?: number; - experimentalAutoDetectLongPolling?: boolean; - experimentalForceLongPolling?: boolean; - host?: string; - ignoreUndefinedProperties?: boolean; - ssl?: boolean; -} - -// @public -export class GeoPoint { - constructor(latitude: number, longitude: number); - isEqual(other: GeoPoint): boolean; - get latitude(): number; - get longitude(): number; - toJSON(): { - latitude: number; - longitude: number; - }; -} - -// @public -export function getDoc(reference: DocumentReference): Promise>; - -// @public -export function getDocFromCache(reference: DocumentReference): Promise>; - -// @public -export function getDocFromServer(reference: DocumentReference): Promise>; - -// @public -export function getDocs(query: Query): Promise>; - -// @public -export function getDocsFromCache(query: Query): Promise>; - -// @public -export function getDocsFromServer(query: Query): Promise>; - -// @public -export function getFirestore(app?: FirebaseApp): Firestore; - -// @public -export function increment(n: number): FieldValue; - -// @public -export function initializeFirestore(app: FirebaseApp, settings: FirestoreSettings): Firestore; - -// @public -export function limit(limit: number): QueryConstraint; - -// @public -export function limitToLast(limit: number): QueryConstraint; - -// @public -export function loadBundle(firestore: Firestore, bundleData: ReadableStream | ArrayBuffer | string): LoadBundleTask; - -// @public -export class LoadBundleTask implements PromiseLike { - catch(onRejected: (a: Error) => R | PromiseLike): Promise; - onProgress(next?: (progress: LoadBundleTaskProgress) => unknown, error?: (err: Error) => unknown, complete?: () => void): void; - then(onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike, onRejected?: (a: Error) => R | PromiseLike): Promise; -} - -// @public -export interface LoadBundleTaskProgress { - bytesLoaded: number; - documentsLoaded: number; - taskState: TaskState; - totalBytes: number; - totalDocuments: number; -} - -export { LogLevel } - -// @public -export function namedQuery(firestore: Firestore, name: string): Promise; - -// @public -export type NestedUpdateFields> = UnionToIntersection<{ - [K in keyof T & string]: T[K] extends Record ? AddPrefixToKeys> : never; -}[keyof T & string]>; - -// @public -export function onSnapshot(reference: DocumentReference, observer: { - next?: (snapshot: DocumentSnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; -}): Unsubscribe; - -// @public -export function onSnapshot(reference: DocumentReference, options: SnapshotListenOptions, observer: { - next?: (snapshot: DocumentSnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; -}): Unsubscribe; - -// @public -export function onSnapshot(reference: DocumentReference, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; - -// @public -export function onSnapshot(reference: DocumentReference, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; - -// @public -export function onSnapshot(query: Query, observer: { - next?: (snapshot: QuerySnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; -}): Unsubscribe; - -// @public -export function onSnapshot(query: Query, options: SnapshotListenOptions, observer: { - next?: (snapshot: QuerySnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; -}): Unsubscribe; - -// @public -export function onSnapshot(query: Query, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; - -// @public -export function onSnapshot(query: Query, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; - -// @public -export function onSnapshotsInSync(firestore: Firestore, observer: { - next?: (value: void) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; -}): Unsubscribe; - -// @public -export function onSnapshotsInSync(firestore: Firestore, onSync: () => void): Unsubscribe; - -// @public -export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDirection): QueryConstraint; - -// @public -export type OrderByDirection = 'desc' | 'asc'; - -// @public -export type PartialWithFieldValue = T extends Primitive ? T : T extends Map ? Map, PartialWithFieldValue> : T extends {} ? { - [K in keyof T]?: PartialWithFieldValue | FieldValue; -} : Partial; - -// @public -export interface PersistenceSettings { - forceOwnership?: boolean; -} - -// @public -export type Primitive = string | number | boolean | undefined | null; - -// @public -export class Query { - protected constructor(); - readonly converter: FirestoreDataConverter | null; - readonly firestore: Firestore; - readonly type: 'query' | 'collection'; - withConverter(converter: null): Query; - withConverter(converter: FirestoreDataConverter): Query; -} - -// @public -export function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; - -// @public -export abstract class QueryConstraint { - abstract readonly type: QueryConstraintType; -} - -// @public -export type QueryConstraintType = 'where' | 'orderBy' | 'limit' | 'limitToLast' | 'startAt' | 'startAfter' | 'endAt' | 'endBefore'; - -// @public -export class QueryDocumentSnapshot extends DocumentSnapshot { - // @override - data(options?: SnapshotOptions): T; -} - -// @public -export function queryEqual(left: Query, right: Query): boolean; - -// @public -export class QuerySnapshot { - docChanges(options?: SnapshotListenOptions): Array>; - get docs(): Array>; - get empty(): boolean; - forEach(callback: (result: QueryDocumentSnapshot) => void, thisArg?: unknown): void; - readonly metadata: SnapshotMetadata; - readonly query: Query; - get size(): number; -} - -// @public -export function refEqual(left: DocumentReference | CollectionReference, right: DocumentReference | CollectionReference): boolean; - -// @public -export function runTransaction(firestore: Firestore, updateFunction: (transaction: Transaction) => Promise): Promise; - -// @public -export function serverTimestamp(): FieldValue; - -// @public -export function setDoc(reference: DocumentReference, data: WithFieldValue): Promise; - -// @public -export function setDoc(reference: DocumentReference, data: PartialWithFieldValue, options: SetOptions): Promise; - -// @public -export function setLogLevel(logLevel: LogLevel): void; - -// @public -export type SetOptions = { - readonly merge?: boolean; -} | { - readonly mergeFields?: Array; -}; - -// @public -export function snapshotEqual(left: DocumentSnapshot | QuerySnapshot, right: DocumentSnapshot | QuerySnapshot): boolean; - -// @public -export interface SnapshotListenOptions { - readonly includeMetadataChanges?: boolean; -} - -// @public -export class SnapshotMetadata { - readonly fromCache: boolean; - readonly hasPendingWrites: boolean; - isEqual(other: SnapshotMetadata): boolean; -} - -// @public -export interface SnapshotOptions { - readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; -} - -// @public -export function startAfter(snapshot: DocumentSnapshot): QueryConstraint; - -// @public -export function startAfter(...fieldValues: unknown[]): QueryConstraint; - -// @public -export function startAt(snapshot: DocumentSnapshot): QueryConstraint; - -// @public -export function startAt(...fieldValues: unknown[]): QueryConstraint; - -// @public -export type TaskState = 'Error' | 'Running' | 'Success'; - -// @public -export function terminate(firestore: Firestore): Promise; - -// @public -export class Timestamp { - constructor( - seconds: number, - nanoseconds: number); - static fromDate(date: Date): Timestamp; - static fromMillis(milliseconds: number): Timestamp; - isEqual(other: Timestamp): boolean; - readonly nanoseconds: number; - static now(): Timestamp; - readonly seconds: number; - toDate(): Date; - toJSON(): { - seconds: number; - nanoseconds: number; - }; - toMillis(): number; - toString(): string; - valueOf(): string; -} - -// @public -export class Transaction { - delete(documentRef: DocumentReference): this; - get(documentRef: DocumentReference): Promise>; - set(documentRef: DocumentReference, data: WithFieldValue): this; - set(documentRef: DocumentReference, data: PartialWithFieldValue, options: SetOptions): this; - update(documentRef: DocumentReference, data: UpdateData): this; - update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): this; -} - -// @public -export type UnionToIntersection = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; - -// @public -export interface Unsubscribe { - (): void; -} - -// @public -export type UpdateData = T extends Primitive ? T : T extends Map ? Map, UpdateData> : T extends {} ? { - [K in keyof T]?: UpdateData | FieldValue; -} & NestedUpdateFields : Partial; - -// @public -export function updateDoc(reference: DocumentReference, data: UpdateData): Promise; - -// @public -export function updateDoc(reference: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise; - -// @public -export function waitForPendingWrites(firestore: Firestore): Promise; - -// @public -export function where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value: unknown): QueryConstraint; - -// @public -export type WhereFilterOp = '<' | '<=' | '==' | '!=' | '>=' | '>' | 'array-contains' | 'in' | 'array-contains-any' | 'not-in'; - -// @public -export type WithFieldValue = T extends Primitive ? T : T extends {} ? { - [K in keyof T]: WithFieldValue | FieldValue; -} : Partial; - -// @public -export class WriteBatch { - commit(): Promise; - delete(documentRef: DocumentReference): WriteBatch; - set(documentRef: DocumentReference, data: WithFieldValue): WriteBatch; - set(documentRef: DocumentReference, data: PartialWithFieldValue, options: SetOptions): WriteBatch; - update(documentRef: DocumentReference, data: UpdateData): WriteBatch; - update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): WriteBatch; -} - -// @public -export function writeBatch(firestore: Firestore): WriteBatch; - - -``` +## API Report File for "@firebase/firestore" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { EmulatorMockTokenOptions } from '@firebase/util'; +import { FirebaseApp } from '@firebase/app-exp'; +import { LogLevelString as LogLevel } from '@firebase/logger'; + +// @public +export function addDoc(reference: CollectionReference, data: WithFieldValue): Promise>; + +// @public +export type AddPrefixToKeys> = { + [K in keyof T & string as `${Prefix}.${K}`]+?: T[K]; +}; + +// @public +export function arrayRemove(...elements: unknown[]): FieldValue; + +// @public +export function arrayUnion(...elements: unknown[]): FieldValue; + +// @public +export class Bytes { + static fromBase64String(base64: string): Bytes; + static fromUint8Array(array: Uint8Array): Bytes; + isEqual(other: Bytes): boolean; + toBase64(): string; + toString(): string; + toUint8Array(): Uint8Array; +} + +// @public +export const CACHE_SIZE_UNLIMITED = -1; + +// @public +export function clearIndexedDbPersistence(firestore: Firestore): Promise; + +// @public +export function collection(firestore: Firestore, path: string, ...pathSegments: string[]): CollectionReference; + +// @public +export function collection(reference: CollectionReference, path: string, ...pathSegments: string[]): CollectionReference; + +// @public +export function collection(reference: DocumentReference, path: string, ...pathSegments: string[]): CollectionReference; + +// @public +export function collectionGroup(firestore: Firestore, collectionId: string): Query; + +// @public +export class CollectionReference extends Query { + get id(): string; + get parent(): DocumentReference | null; + get path(): string; + readonly type = "collection"; + withConverter(converter: FirestoreDataConverter): CollectionReference; + withConverter(converter: null): CollectionReference; +} + +// @public +export function connectFirestoreEmulator(firestore: Firestore, host: string, port: number, options?: { + mockUserToken?: EmulatorMockTokenOptions | string; +}): void; + +// @public +export function deleteDoc(reference: DocumentReference): Promise; + +// @public +export function deleteField(): FieldValue; + +// @public +export function disableNetwork(firestore: Firestore): Promise; + +// @public +export function doc(firestore: Firestore, path: string, ...pathSegments: string[]): DocumentReference; + +// @public +export function doc(reference: CollectionReference, path?: string, ...pathSegments: string[]): DocumentReference; + +// @public +export function doc(reference: DocumentReference, path: string, ...pathSegments: string[]): DocumentReference; + +// @public +export interface DocumentChange { + readonly doc: QueryDocumentSnapshot; + readonly newIndex: number; + readonly oldIndex: number; + readonly type: DocumentChangeType; +} + +// @public +export type DocumentChangeType = 'added' | 'removed' | 'modified'; + +// @public +export interface DocumentData { + [field: string]: any; +} + +// @public +export function documentId(): FieldPath; + +// @public +export class DocumentReference { + readonly converter: FirestoreDataConverter | null; + readonly firestore: Firestore; + get id(): string; + get parent(): CollectionReference; + get path(): string; + readonly type = "document"; + withConverter(converter: FirestoreDataConverter): DocumentReference; + withConverter(converter: null): DocumentReference; +} + +// @public +export class DocumentSnapshot { + protected constructor(); + data(options?: SnapshotOptions): T | undefined; + exists(): this is QueryDocumentSnapshot; + get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; + get id(): string; + readonly metadata: SnapshotMetadata; + get ref(): DocumentReference; +} + +// @public +export function enableIndexedDbPersistence(firestore: Firestore, persistenceSettings?: PersistenceSettings): Promise; + +// @public +export function enableMultiTabIndexedDbPersistence(firestore: Firestore): Promise; + +// @public +export function enableNetwork(firestore: Firestore): Promise; + +// @public +export function endAt(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function endAt(...fieldValues: unknown[]): QueryConstraint; + +// @public +export function endBefore(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function endBefore(...fieldValues: unknown[]): QueryConstraint; + +// @public +export class FieldPath { + constructor(...fieldNames: string[]); + isEqual(other: FieldPath): boolean; +} + +// @public +export abstract class FieldValue { + abstract isEqual(other: FieldValue): boolean; +} + +// @public +export class Firestore { + get app(): FirebaseApp; + toJSON(): object; + type: 'firestore-lite' | 'firestore'; +} + +// @public +export interface FirestoreDataConverter { + fromFirestore(snapshot: QueryDocumentSnapshot, options?: SnapshotOptions): T; + toFirestore(modelObject: WithFieldValue): DocumentData; + toFirestore(modelObject: PartialWithFieldValue, options: SetOptions): DocumentData; +} + +// @public +export class FirestoreError extends Error { + readonly code: FirestoreErrorCode; + readonly message: string; + readonly name: string; + readonly stack?: string; +} + +// @public +export type FirestoreErrorCode = 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' | 'data-loss' | 'unauthenticated'; + +// @public +export interface FirestoreSettings { + cacheSizeBytes?: number; + experimentalAutoDetectLongPolling?: boolean; + experimentalForceLongPolling?: boolean; + host?: string; + ignoreUndefinedProperties?: boolean; + ssl?: boolean; +} + +// @public +export class GeoPoint { + constructor(latitude: number, longitude: number); + isEqual(other: GeoPoint): boolean; + get latitude(): number; + get longitude(): number; + toJSON(): { + latitude: number; + longitude: number; + }; +} + +// @public +export function getDoc(reference: DocumentReference): Promise>; + +// @public +export function getDocFromCache(reference: DocumentReference): Promise>; + +// @public +export function getDocFromServer(reference: DocumentReference): Promise>; + +// @public +export function getDocs(query: Query): Promise>; + +// @public +export function getDocsFromCache(query: Query): Promise>; + +// @public +export function getDocsFromServer(query: Query): Promise>; + +// @public +export function getFirestore(app?: FirebaseApp): Firestore; + +// @public +export function increment(n: number): FieldValue; + +// @public +export function initializeFirestore(app: FirebaseApp, settings: FirestoreSettings): Firestore; + +// @public +export function limit(limit: number): QueryConstraint; + +// @public +export function limitToLast(limit: number): QueryConstraint; + +// @public +export function loadBundle(firestore: Firestore, bundleData: ReadableStream | ArrayBuffer | string): LoadBundleTask; + +// @public +export class LoadBundleTask implements PromiseLike { + catch(onRejected: (a: Error) => R | PromiseLike): Promise; + onProgress(next?: (progress: LoadBundleTaskProgress) => unknown, error?: (err: Error) => unknown, complete?: () => void): void; + then(onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike, onRejected?: (a: Error) => R | PromiseLike): Promise; +} + +// @public +export interface LoadBundleTaskProgress { + bytesLoaded: number; + documentsLoaded: number; + taskState: TaskState; + totalBytes: number; + totalDocuments: number; +} + +export { LogLevel } + +// @public +export function namedQuery(firestore: Firestore, name: string): Promise; + +// @public +export type NestedUpdateFields> = UnionToIntersection<{ + [K in keyof T & string]: T[K] extends Record ? AddPrefixToKeys> : never; +}[keyof T & string]>; + +// @public +export function onSnapshot(reference: DocumentReference, observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(reference: DocumentReference, options: SnapshotListenOptions, observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(reference: DocumentReference, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshot(reference: DocumentReference, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshot(query: Query, observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(query: Query, options: SnapshotListenOptions, observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(query: Query, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshot(query: Query, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshotsInSync(firestore: Firestore, observer: { + next?: (value: void) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshotsInSync(firestore: Firestore, onSync: () => void): Unsubscribe; + +// @public +export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDirection): QueryConstraint; + +// @public +export type OrderByDirection = 'desc' | 'asc'; + +// @public +export type PartialWithFieldValue = T extends Primitive ? T : T extends {} ? { + [K in keyof T]?: PartialWithFieldValue | FieldValue; +} : Partial; + +// @public +export interface PersistenceSettings { + forceOwnership?: boolean; +} + +// @public +export type Primitive = string | number | boolean | undefined | null; + +// @public +export class Query { + protected constructor(); + readonly converter: FirestoreDataConverter | null; + readonly firestore: Firestore; + readonly type: 'query' | 'collection'; + withConverter(converter: null): Query; + withConverter(converter: FirestoreDataConverter): Query; +} + +// @public +export function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; + +// @public +export abstract class QueryConstraint { + abstract readonly type: QueryConstraintType; +} + +// @public +export type QueryConstraintType = 'where' | 'orderBy' | 'limit' | 'limitToLast' | 'startAt' | 'startAfter' | 'endAt' | 'endBefore'; + +// @public +export class QueryDocumentSnapshot extends DocumentSnapshot { + // @override + data(options?: SnapshotOptions): T; +} + +// @public +export function queryEqual(left: Query, right: Query): boolean; + +// @public +export class QuerySnapshot { + docChanges(options?: SnapshotListenOptions): Array>; + get docs(): Array>; + get empty(): boolean; + forEach(callback: (result: QueryDocumentSnapshot) => void, thisArg?: unknown): void; + readonly metadata: SnapshotMetadata; + readonly query: Query; + get size(): number; +} + +// @public +export function refEqual(left: DocumentReference | CollectionReference, right: DocumentReference | CollectionReference): boolean; + +// @public +export function runTransaction(firestore: Firestore, updateFunction: (transaction: Transaction) => Promise): Promise; + +// @public +export function serverTimestamp(): FieldValue; + +// @public +export function setDoc(reference: DocumentReference, data: WithFieldValue): Promise; + +// @public +export function setDoc(reference: DocumentReference, data: PartialWithFieldValue, options: SetOptions): Promise; + +// @public +export function setLogLevel(logLevel: LogLevel): void; + +// @public +export type SetOptions = { + readonly merge?: boolean; +} | { + readonly mergeFields?: Array; +}; + +// @public +export function snapshotEqual(left: DocumentSnapshot | QuerySnapshot, right: DocumentSnapshot | QuerySnapshot): boolean; + +// @public +export interface SnapshotListenOptions { + readonly includeMetadataChanges?: boolean; +} + +// @public +export class SnapshotMetadata { + readonly fromCache: boolean; + readonly hasPendingWrites: boolean; + isEqual(other: SnapshotMetadata): boolean; +} + +// @public +export interface SnapshotOptions { + readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; +} + +// @public +export function startAfter(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function startAfter(...fieldValues: unknown[]): QueryConstraint; + +// @public +export function startAt(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function startAt(...fieldValues: unknown[]): QueryConstraint; + +// @public +export type TaskState = 'Error' | 'Running' | 'Success'; + +// @public +export function terminate(firestore: Firestore): Promise; + +// @public +export class Timestamp { + constructor( + seconds: number, + nanoseconds: number); + static fromDate(date: Date): Timestamp; + static fromMillis(milliseconds: number): Timestamp; + isEqual(other: Timestamp): boolean; + readonly nanoseconds: number; + static now(): Timestamp; + readonly seconds: number; + toDate(): Date; + toJSON(): { + seconds: number; + nanoseconds: number; + }; + toMillis(): number; + toString(): string; + valueOf(): string; +} + +// @public +export class Transaction { + delete(documentRef: DocumentReference): this; + get(documentRef: DocumentReference): Promise>; + set(documentRef: DocumentReference, data: WithFieldValue): this; + set(documentRef: DocumentReference, data: PartialWithFieldValue, options: SetOptions): this; + update(documentRef: DocumentReference, data: UpdateData): this; + update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): this; +} + +// @public +export type UnionToIntersection = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; + +// @public +export interface Unsubscribe { + (): void; +} + +// @public +export type UpdateData = T extends Primitive ? T : T extends Map ? Map, UpdateData> : T extends {} ? { + [K in keyof T]?: UpdateData | FieldValue; +} & NestedUpdateFields : Partial; + +// @public +export function updateDoc(reference: DocumentReference, data: UpdateData): Promise; + +// @public +export function updateDoc(reference: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise; + +// @public +export function waitForPendingWrites(firestore: Firestore): Promise; + +// @public +export function where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value: unknown): QueryConstraint; + +// @public +export type WhereFilterOp = '<' | '<=' | '==' | '!=' | '>=' | '>' | 'array-contains' | 'in' | 'array-contains-any' | 'not-in'; + +// @public +export type WithFieldValue = T extends Primitive ? T : T extends {} ? { + [K in keyof T]: WithFieldValue | FieldValue; +} : Partial; + +// @public +export class WriteBatch { + commit(): Promise; + delete(documentRef: DocumentReference): WriteBatch; + set(documentRef: DocumentReference, data: WithFieldValue): WriteBatch; + set(documentRef: DocumentReference, data: PartialWithFieldValue, options: SetOptions): WriteBatch; + update(documentRef: DocumentReference, data: UpdateData): WriteBatch; + update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): WriteBatch; +} + +// @public +export function writeBatch(firestore: Firestore): WriteBatch; + + +``` From f9d4b3eda9472f1a625c339fe7d005781508af98 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Wed, 18 Aug 2021 10:06:49 -0500 Subject: [PATCH 21/22] fix addDoc test --- packages/firestore/test/lite/integration.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 13e4927fe54..a0a803d6b13 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -1576,8 +1576,8 @@ describe('withConverter() support', () => { describe('methods', () => { it('addDoc()', () => { - return withTestDoc(async doc => { - const ref = doc.withConverter(testConverter); + return withTestDb(async db => { + const ref = collection(db, 'testobj').withConverter(testConverter); // Requires all fields to be present // @ts-expect-error From 5a175a42b7a51a6458e34157b6b04fa7510d2cf8 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Wed, 18 Aug 2021 11:07:11 -0500 Subject: [PATCH 22/22] update comments --- .changeset/dirty-pandas-pay.md | 2 +- packages/firestore/src/exp/snapshot.ts | 4 ++-- packages/firestore/src/lite/reference.ts | 2 +- packages/firestore/src/lite/snapshot.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.changeset/dirty-pandas-pay.md b/.changeset/dirty-pandas-pay.md index 78688258b88..a582ac41378 100644 --- a/.changeset/dirty-pandas-pay.md +++ b/.changeset/dirty-pandas-pay.md @@ -6,5 +6,5 @@ This change contains multiple quality-of-life improvements when using the `FirestoreDataConverter` in `@firebase/firestore/lite` and `@firebase/firestore`: - Support for passing in `FieldValue` property values when using a converter (via `WithFieldValue` and `PartialWithFieldValue`). - Support for omitting properties in nested fields when performing a set operation with `{merge: true}` with a converter (via `PartialWithFieldValue`). -- [breaking] Support for typed update operations when using a converter (via the newly typed `UpdateData`). Improperly typed fields in +- Support for typed update operations when using a converter (via the newly typed `UpdateData`). Improperly typed fields in update operations on typed document references will no longer compile. diff --git a/packages/firestore/src/exp/snapshot.ts b/packages/firestore/src/exp/snapshot.ts index 17529d233dc..4246c03d3b2 100644 --- a/packages/firestore/src/exp/snapshot.ts +++ b/packages/firestore/src/exp/snapshot.ts @@ -91,7 +91,7 @@ export interface FirestoreDataConverter * Firestore database). To use `set()` with `merge` and `mergeFields`, * `toFirestore()` must be defined with `PartialWithFieldValue`. * - * The `WithFieldValue` type extends `T` to also allow FieldValues such + * The `WithFieldValue` type extends `T` to also allow FieldValues such as * {@link (deleteField:1)} to be used as property values. */ toFirestore(modelObject: WithFieldValue): DocumentData; @@ -103,7 +103,7 @@ export interface FirestoreDataConverter * and {@link (Transaction.set:1)} with `merge:true` or `mergeFields`. * * The `PartialWithFieldValue` type extends `Partial` to allow - * FieldValues such {@link (arrayUnion:1)} to be used as property values. + * FieldValues such as {@link (arrayUnion:1)} to be used as property values. * It also supports nested `Partial` by allowing nested fields to be * omitted. */ diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite/reference.ts index 04584437735..2e02e2bd673 100644 --- a/packages/firestore/src/lite/reference.ts +++ b/packages/firestore/src/lite/reference.ts @@ -71,7 +71,7 @@ export type WithFieldValue = T extends Primitive : Partial; /** - * Update data (for use with {@link (setDoc:1)}) that consists of field paths + * Update data (for use with {@link (updateDoc:1)}) that consists of field paths * (e.g. 'foo' or 'foo.baz') mapped to values. Fields that contain dots * reference nested fields within the document. FieldValues can be passed in * as property values. diff --git a/packages/firestore/src/lite/snapshot.ts b/packages/firestore/src/lite/snapshot.ts index 70e98c43f4c..8b0fde84a78 100644 --- a/packages/firestore/src/lite/snapshot.ts +++ b/packages/firestore/src/lite/snapshot.ts @@ -85,7 +85,7 @@ export interface FirestoreDataConverter { * Firestore database). Used with {@link @firebase/firestore/lite#(setDoc:1)}, {@link @firebase/firestore/lite#(WriteBatch.set:1)} * and {@link @firebase/firestore/lite#(Transaction.set:1)}. * - * The `WithFieldValue` type extends `T` to also allow FieldValues such + * The `WithFieldValue` type extends `T` to also allow FieldValues such as * {@link (deleteField:1)} to be used as property values. */ toFirestore(modelObject: WithFieldValue): DocumentData; @@ -97,7 +97,7 @@ export interface FirestoreDataConverter { * and {@link @firebase/firestore/lite#(Transaction.set:1)} with `merge:true` or `mergeFields`. * * The `PartialWithFieldValue` type extends `Partial` to allow - * FieldValues such {@link (arrayUnion:1)} to be used as property values. + * FieldValues such as {@link (arrayUnion:1)} to be used as property values. * It also supports nested `Partial` by allowing nested fields to be * omitted. */