From 7a8431b18e989186fcabbe7637fa5b265ef6ab03 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Fri, 27 Aug 2021 11:25:11 -0400 Subject: [PATCH 01/10] fix updatedata --- packages/firestore/src/lite/reference.ts | 6 +-- packages/firestore/src/lite/types.ts | 2 +- .../firestore/test/lite/integration.test.ts | 43 +++++++++++++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite/reference.ts index 50e6d9a652c..4cab8e6b72a 100644 --- a/packages/firestore/src/lite/reference.ts +++ b/packages/firestore/src/lite/reference.ts @@ -76,10 +76,10 @@ export type WithFieldValue = T extends Primitive * reference nested fields within the document. FieldValues can be passed in * as property values. */ -export type UpdateData = T extends Primitive +export type UpdateData = T extends undefined + ? never + : T extends Primitive ? T - : T extends Map - ? Map, UpdateData> : T extends {} ? { [K in keyof T]?: UpdateData | FieldValue } & NestedUpdateFields : Partial; diff --git a/packages/firestore/src/lite/types.ts b/packages/firestore/src/lite/types.ts index 63d1b1cc752..fa63e176595 100644 --- a/packages/firestore/src/lite/types.ts +++ b/packages/firestore/src/lite/types.ts @@ -35,7 +35,7 @@ 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 + [K in keyof T & string]: T[K] extends Record | undefined ? // 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> diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index a0a803d6b13..233ea189adc 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -1542,6 +1542,49 @@ describe('withConverter() support', () => { }); }); + it('supports optional fields', () => { + interface TestObjectOptional { + optionalStr?: string; + nested?: { + requiredStr: string; + }; + } + + const testConverterOptional = { + toFirestore(testObj: WithFieldValue) { + return { ...testObj }; + }, + fromFirestore(snapshot: QueryDocumentSnapshot): TestObjectOptional { + const data = snapshot.data(); + return { + optionalStr: data.optionalStr, + nested: data.nested + }; + } + }; + + return withTestDocAndInitialData(initialData, async docRef => { + const testDocRef: DocumentReference = + docRef.withConverter(testConverterOptional); + + await updateDoc(testDocRef, { + optionalStr: 'foo' + }); + await updateDoc(testDocRef, { + 'optionalStr': 'foo' + }); + + await updateDoc(testDocRef, { + nested: { + requiredStr: 'foo' + } + }); + await updateDoc(testDocRef, { + 'nested.requiredStr': 'foo' + }); + }); + }); + it('checks for nonexistent fields', () => { return withTestDocAndInitialData(initialData, async docRef => { const testDocRef: DocumentReference = From 83fee2c1d67ef78322a478a8bf945b0fa7259a04 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Fri, 27 Aug 2021 11:27:06 -0400 Subject: [PATCH 02/10] changeset --- .changeset/clean-cameras-check.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/clean-cameras-check.md diff --git a/.changeset/clean-cameras-check.md b/.changeset/clean-cameras-check.md new file mode 100644 index 00000000000..aef371d632b --- /dev/null +++ b/.changeset/clean-cameras-check.md @@ -0,0 +1,5 @@ +--- +'@firebase/firestore': minor +--- + +Fixed a bug where UpdateData did not recognize optional, dot-separated string fields From 881ee447e8cbb3b1771abcfbe7daf24d5344402a Mon Sep 17 00:00:00 2001 From: thebrianchen Date: Fri, 27 Aug 2021 15:46:23 +0000 Subject: [PATCH 03/10] Update API reports --- common/api-review/firestore-lite.api.md | 4 +- common/api-review/firestore.api.md | 1044 +++++++++++------------ 2 files changed, 524 insertions(+), 524 deletions(-) diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 70f9a27dba6..e261637bdd8 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -191,7 +191,7 @@ export { LogLevel } // @public export type NestedUpdateFields> = UnionToIntersection<{ - [K in keyof T & string]: T[K] extends Record ? AddPrefixToKeys> : never; + [K in keyof T & string]: T[K] extends Record | undefined ? AddPrefixToKeys> : never; }[keyof T & string]>; // @public @@ -332,7 +332,7 @@ export class Transaction { 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 {} ? { +export type UpdateData = T extends undefined ? never : T extends Primitive ? T : T extends {} ? { [K in keyof T]?: UpdateData | FieldValue; } & NestedUpdateFields : Partial; diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 56ee1729a4e..c6690a88b1d 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -1,522 +1,522 @@ -## 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'; -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; -} - -export { EmulatorMockTokenOptions } - -// @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; - - -``` +## 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'; +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; +} + +export { EmulatorMockTokenOptions } + +// @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 | undefined ? 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 undefined ? never : T extends Primitive ? T : 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 be8a42ffb462d2bc2ec03fb9a49d6498ff8a5503 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Thu, 9 Sep 2021 15:25:40 -0500 Subject: [PATCH 04/10] add support for union fields --- .changeset/clean-cameras-check.md | 2 +- common/api-review/firestore-lite.api.md | 7 ++- common/api-review/firestore.api.md | 7 ++- packages/firestore/lite/index.ts | 1 + packages/firestore/src/api.ts | 1 + packages/firestore/src/lite-api/reference.ts | 5 +- packages/firestore/src/lite-api/types.ts | 23 ++++++-- .../firestore/test/lite/integration.test.ts | 57 +++++++++++++++++++ 8 files changed, 88 insertions(+), 15 deletions(-) diff --git a/.changeset/clean-cameras-check.md b/.changeset/clean-cameras-check.md index aef371d632b..1ce7101e355 100644 --- a/.changeset/clean-cameras-check.md +++ b/.changeset/clean-cameras-check.md @@ -2,4 +2,4 @@ '@firebase/firestore': minor --- -Fixed a bug where UpdateData did not recognize optional, dot-separated string fields +Fixed a bug where `UpdateData` did not recognize optional, dot-separated string fields. diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 70f9a27dba6..1bff3183d55 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -32,6 +32,9 @@ export class Bytes { toUint8Array(): Uint8Array; } +// @public +export type ChildUpdateFields = T extends Record ? AddPrefixToKeys> : never; + // @public export function collection(firestore: Firestore, path: string, ...pathSegments: string[]): CollectionReference; @@ -191,7 +194,7 @@ export { LogLevel } // @public export type NestedUpdateFields> = UnionToIntersection<{ - [K in keyof T & string]: T[K] extends Record ? AddPrefixToKeys> : never; + [K in keyof T & string]: ChildUpdateFields; }[keyof T & string]>; // @public @@ -332,7 +335,7 @@ export class Transaction { 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 {} ? { +export type UpdateData = T extends Primitive ? T : T extends {} ? { [K in keyof T]?: UpdateData | FieldValue; } & NestedUpdateFields : Partial; diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 56ee1729a4e..894308c49d5 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -35,6 +35,9 @@ export class Bytes { // @public export const CACHE_SIZE_UNLIMITED = -1; +// @public +export type ChildUpdateFields = T extends Record ? AddPrefixToKeys> : never; + // @public export function clearIndexedDbPersistence(firestore: Firestore): Promise; @@ -265,7 +268,7 @@ export function namedQuery(firestore: Firestore, name: string): Promise> = UnionToIntersection<{ - [K in keyof T & string]: T[K] extends Record ? AddPrefixToKeys> : never; + [K in keyof T & string]: ChildUpdateFields; }[keyof T & string]>; // @public @@ -481,7 +484,7 @@ export interface Unsubscribe { } // @public -export type UpdateData = T extends Primitive ? T : T extends Map ? Map, UpdateData> : T extends {} ? { +export type UpdateData = T extends Primitive ? T : T extends {} ? { [K in keyof T]?: UpdateData | FieldValue; } & NestedUpdateFields : Partial; diff --git a/packages/firestore/lite/index.ts b/packages/firestore/lite/index.ts index 81f7318ec2a..42e1554c92e 100644 --- a/packages/firestore/lite/index.ts +++ b/packages/firestore/lite/index.ts @@ -82,6 +82,7 @@ export { export { Primitive, NestedUpdateFields, + ChildUpdateFields, AddPrefixToKeys, UnionToIntersection } from '../src/lite-api/types'; diff --git a/packages/firestore/src/api.ts b/packages/firestore/src/api.ts index 96af005ad48..b20bf1b9ae6 100644 --- a/packages/firestore/src/api.ts +++ b/packages/firestore/src/api.ts @@ -136,6 +136,7 @@ export { AbstractUserDataWriter } from './lite-api/user_data_writer'; export { Primitive, NestedUpdateFields, + ChildUpdateFields, AddPrefixToKeys, UnionToIntersection } from '../src/lite-api/types'; diff --git a/packages/firestore/src/lite-api/reference.ts b/packages/firestore/src/lite-api/reference.ts index 4cab8e6b72a..0b63d5fe2a3 100644 --- a/packages/firestore/src/lite-api/reference.ts +++ b/packages/firestore/src/lite-api/reference.ts @@ -76,14 +76,11 @@ export type WithFieldValue = T extends Primitive * reference nested fields within the document. FieldValues can be passed in * as property values. */ -export type UpdateData = T extends undefined - ? never - : T extends Primitive +export type UpdateData = T extends Primitive ? T : T extends {} ? { [K in keyof T]?: UpdateData | FieldValue } & NestedUpdateFields : Partial; - /** * 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-api/types.ts b/packages/firestore/src/lite-api/types.ts index fa63e176595..3a1749e93aa 100644 --- a/packages/firestore/src/lite-api/types.ts +++ b/packages/firestore/src/lite-api/types.ts @@ -35,15 +35,26 @@ 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 | undefined - ? // 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; + // Union with `undefined` to allow for optional nested fields + [K in keyof T & string]: ChildUpdateFields; }[keyof T & string] // Also include the generated prefix-string keys. >; +/** + * Helper for calculating the nested fields for a given type T. This is needed + * to distribute union types such as `undefined | {...}` (happens for optional + * props) or `{a: A} | {b: B}`. + * https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types + */ +export type ChildUpdateFields = + // Only allow nesting for map values + T 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> + : // UpdateData is always a map of values. + never; + /** * Returns a new map where every key is prefixed with the outer key appended * to a dot. diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index cf944b6a058..e2916be36a4 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -1585,6 +1585,63 @@ describe('withConverter() support', () => { }); }); + it('supports union fields', () => { + interface TestObjectUnion { + optionalStr?: string; + nested?: + | { + requiredStr: string; + } + | { requiredStrObject: string }; + } + + const testConverterUnion = { + toFirestore(testObj: WithFieldValue) { + return { ...testObj }; + }, + fromFirestore(snapshot: QueryDocumentSnapshot): TestObjectUnion { + const data = snapshot.data(); + return { + optionalStr: data.optionalStr, + nested: data.nested + }; + } + }; + + return withTestDocAndInitialData(initialData, async docRef => { + const testDocRef: DocumentReference = + docRef.withConverter(testConverterUnion); + + await updateDoc(testDocRef, { + optionalStr: 'foo' + }); + await updateDoc(testDocRef, { + 'optionalStr': 'foo' + }); + + await updateDoc(testDocRef, { + nested: { + requiredStr: 'foo' + } + }); + await updateDoc(testDocRef, { + 'nested.requiredStr': 'foo' + }); + await updateDoc(testDocRef, { + // @ts-expect-error + 'nested.requiredStr': 1 + }); + await updateDoc(testDocRef, { + 'nested.requiredStrObject': 'foo' + }); + + await updateDoc(testDocRef, { + // @ts-expect-error + 'nested.requiredStringObject': 1 + }); + }); + }); + it('checks for nonexistent fields', () => { return withTestDocAndInitialData(initialData, async docRef => { const testDocRef: DocumentReference = From 8865d092d1db048faae0a1181181de64677e21da Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Thu, 9 Sep 2021 16:04:10 -0500 Subject: [PATCH 05/10] fix lint --- packages/firestore/src/lite-api/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firestore/src/lite-api/types.ts b/packages/firestore/src/lite-api/types.ts index 3a1749e93aa..a6b6c7b648e 100644 --- a/packages/firestore/src/lite-api/types.ts +++ b/packages/firestore/src/lite-api/types.ts @@ -48,7 +48,7 @@ export type NestedUpdateFields> = */ export type ChildUpdateFields = // Only allow nesting for map values - T extends Record + T 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> From 7633ff5c958e0a8a0887fadb8fafb08684c171f4 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Thu, 9 Sep 2021 16:39:35 -0500 Subject: [PATCH 06/10] update comments --- packages/firestore/src/lite-api/types.ts | 16 +++++++++------- packages/firestore/test/lite/integration.test.ts | 8 +++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/firestore/src/lite-api/types.ts b/packages/firestore/src/lite-api/types.ts index a6b6c7b648e..df2dfd29df0 100644 --- a/packages/firestore/src/lite-api/types.ts +++ b/packages/firestore/src/lite-api/types.ts @@ -34,24 +34,26 @@ export type Primitive = string | number | boolean | undefined | null; export type NestedUpdateFields> = UnionToIntersection< { - // Check that T[K] extends Record to only allow nesting for map values. - // Union with `undefined` to allow for optional nested fields [K in keyof T & string]: ChildUpdateFields; }[keyof T & string] // Also include the generated prefix-string keys. >; /** - * Helper for calculating the nested fields for a given type T. This is needed + * Helper for calculating the nested fields for a given type T1. This is needed * to distribute union types such as `undefined | {...}` (happens for optional * props) or `{a: A} | {b: B}`. - * https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types + * + * In this use case, `T1` is used to distribute the union types of `T[K]` on + * `Record`, since `T[K]` is evaluated as an expression and not distributed. + * + * See https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types */ -export type ChildUpdateFields = +export type ChildUpdateFields = // Only allow nesting for map values - T extends Record + T1 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> + AddPrefixToKeys> : // UpdateData is always a map of values. never; diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index e2916be36a4..fc8a56003f5 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -1592,7 +1592,7 @@ describe('withConverter() support', () => { | { requiredStr: string; } - | { requiredStrObject: string }; + | { requiredNumber: number }; } const testConverterUnion = { @@ -1624,6 +1624,7 @@ describe('withConverter() support', () => { requiredStr: 'foo' } }); + await updateDoc(testDocRef, { 'nested.requiredStr': 'foo' }); @@ -1631,13 +1632,14 @@ describe('withConverter() support', () => { // @ts-expect-error 'nested.requiredStr': 1 }); + await updateDoc(testDocRef, { - 'nested.requiredStrObject': 'foo' + 'nested.requiredNumber': 1 }); await updateDoc(testDocRef, { // @ts-expect-error - 'nested.requiredStringObject': 1 + 'nested.requiredNumber': 'foo' }); }); }); From 160423ad9fdb7baf5898658265ea9a8f200deb0f Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Thu, 9 Sep 2021 16:48:33 -0500 Subject: [PATCH 07/10] fix API reports --- common/api-review/firestore-lite.api.md | 2 +- common/api-review/firestore.api.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 1bff3183d55..63f6193c94f 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -33,7 +33,7 @@ export class Bytes { } // @public -export type ChildUpdateFields = T extends Record ? AddPrefixToKeys> : never; +export type ChildUpdateFields = T1 extends Record ? AddPrefixToKeys> : never; // @public export function collection(firestore: Firestore, path: string, ...pathSegments: string[]): CollectionReference; diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 894308c49d5..9d3c3b23a66 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -36,7 +36,7 @@ export class Bytes { export const CACHE_SIZE_UNLIMITED = -1; // @public -export type ChildUpdateFields = T extends Record ? AddPrefixToKeys> : never; +export type ChildUpdateFields = T1 extends Record ? AddPrefixToKeys> : never; // @public export function clearIndexedDbPersistence(firestore: Firestore): Promise; From cb69ae1a826f6daf4d226ead0e54143b203389d6 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Thu, 9 Sep 2021 16:57:50 -0500 Subject: [PATCH 08/10] Update clean-cameras-check.md --- .changeset/clean-cameras-check.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/clean-cameras-check.md b/.changeset/clean-cameras-check.md index 1ce7101e355..572ce9b3e3d 100644 --- a/.changeset/clean-cameras-check.md +++ b/.changeset/clean-cameras-check.md @@ -2,4 +2,4 @@ '@firebase/firestore': minor --- -Fixed a bug where `UpdateData` did not recognize optional, dot-separated string fields. +Fixed a bug where `UpdateData` did not recognize union types or optional, dot-separated string fields. From 98bd1ad68162d3b2082b39ec6db82eebe8ac5982 Mon Sep 17 00:00:00 2001 From: Brian Chen Date: Fri, 10 Sep 2021 13:01:02 -0500 Subject: [PATCH 09/10] resolve sebastian comments --- packages/firestore/src/lite-api/types.ts | 10 ++-- .../firestore/test/lite/integration.test.ts | 47 ++++++++++++++++--- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/packages/firestore/src/lite-api/types.ts b/packages/firestore/src/lite-api/types.ts index df2dfd29df0..4801e587836 100644 --- a/packages/firestore/src/lite-api/types.ts +++ b/packages/firestore/src/lite-api/types.ts @@ -34,7 +34,7 @@ export type Primitive = string | number | boolean | undefined | null; export type NestedUpdateFields> = UnionToIntersection< { - [K in keyof T & string]: ChildUpdateFields; + [K in keyof T & string]: ChildUpdateFields; }[keyof T & string] // Also include the generated prefix-string keys. >; @@ -43,17 +43,17 @@ export type NestedUpdateFields> = * to distribute union types such as `undefined | {...}` (happens for optional * props) or `{a: A} | {b: B}`. * - * In this use case, `T1` is used to distribute the union types of `T[K]` on + * In this use case, `V` is used to distribute the union types of `T[K]` on * `Record`, since `T[K]` is evaluated as an expression and not distributed. * * See https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types */ -export type ChildUpdateFields = +export type ChildUpdateFields = // Only allow nesting for map values - T1 extends Record + V 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> + AddPrefixToKeys> : // UpdateData is always a map of values. never; diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index fc8a56003f5..a9ebcbcf50b 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -1585,6 +1585,42 @@ describe('withConverter() support', () => { }); }); + it('supports null fields', () => { + interface TestObjectOptional { + optionalStr?: string; + nested?: { + strOrNull: string | null; + }; + } + + const testConverterOptional = { + toFirestore(testObj: WithFieldValue) { + return { ...testObj }; + }, + fromFirestore(snapshot: QueryDocumentSnapshot): TestObjectOptional { + const data = snapshot.data(); + return { + optionalStr: data.optionalStr, + nested: data.nested + }; + } + }; + + return withTestDocAndInitialData(initialData, async docRef => { + const testDocRef: DocumentReference = + docRef.withConverter(testConverterOptional); + + await updateDoc(testDocRef, { + nested: { + strOrNull: null + } + }); + await updateDoc(testDocRef, { + 'nested.strOrNull': null + }); + }); + }); + it('supports union fields', () => { interface TestObjectUnion { optionalStr?: string; @@ -1612,13 +1648,6 @@ describe('withConverter() support', () => { const testDocRef: DocumentReference = docRef.withConverter(testConverterUnion); - await updateDoc(testDocRef, { - optionalStr: 'foo' - }); - await updateDoc(testDocRef, { - 'optionalStr': 'foo' - }); - await updateDoc(testDocRef, { nested: { requiredStr: 'foo' @@ -1641,6 +1670,10 @@ describe('withConverter() support', () => { // @ts-expect-error 'nested.requiredNumber': 'foo' }); + await updateDoc(testDocRef, { + // @ts-expect-error + 'nested.requiredNumber': null + }); }); }); From 073ab9c60e9118388e1d52cb8de15ab15586e3d7 Mon Sep 17 00:00:00 2001 From: thebrianchen Date: Fri, 10 Sep 2021 18:22:21 +0000 Subject: [PATCH 10/10] Update API reports --- common/api-review/firestore-lite.api.md | 4 +- common/api-review/firestore.api.md | 1050 +++++++++++------------ 2 files changed, 527 insertions(+), 527 deletions(-) diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 63f6193c94f..83eebeb488d 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -33,7 +33,7 @@ export class Bytes { } // @public -export type ChildUpdateFields = T1 extends Record ? AddPrefixToKeys> : never; +export type ChildUpdateFields = V extends Record ? AddPrefixToKeys> : never; // @public export function collection(firestore: Firestore, path: string, ...pathSegments: string[]): CollectionReference; @@ -194,7 +194,7 @@ export { LogLevel } // @public export type NestedUpdateFields> = UnionToIntersection<{ - [K in keyof T & string]: ChildUpdateFields; + [K in keyof T & string]: ChildUpdateFields; }[keyof T & string]>; // @public diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 9d3c3b23a66..2d3fee14117 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -1,525 +1,525 @@ -## 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'; -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 type ChildUpdateFields = T1 extends Record ? AddPrefixToKeys> : never; - -// @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; -} - -export { EmulatorMockTokenOptions } - -// @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]: ChildUpdateFields; -}[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 {} ? { - [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'; +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 type ChildUpdateFields = V extends Record ? AddPrefixToKeys> : never; + +// @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; +} + +export { EmulatorMockTokenOptions } + +// @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]: ChildUpdateFields; +}[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 {} ? { + [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; + + +```