From 990c066a5173c51d536f3cd4c644eda58ec87d79 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Fri, 4 Apr 2025 10:24:01 -0400 Subject: [PATCH 01/29] WIP, QuerySnapshot works in protoype function --- common/api-review/firestore.api.md | 7 ++ packages/firestore/src/api.ts | 1 + packages/firestore/src/api/reference_impl.ts | 64 ++++++++++++++++++- packages/firestore/src/api/snapshot.ts | 7 +- .../firestore/src/util/bundle_builder_impl.ts | 10 +-- 5 files changed, 79 insertions(+), 10 deletions(-) diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 3f17e415e52..8640fab03a5 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -461,6 +461,13 @@ export function onSnapshot(query // @public export function onSnapshot(query: Query, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; +// @public (undocumented) +export function onSnapshotBundle(db: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; + // @public export function onSnapshotsInSync(firestore: Firestore, observer: { next?: (value: void) => void; diff --git a/packages/firestore/src/api.ts b/packages/firestore/src/api.ts index ea969c6b94c..bd4087eb042 100644 --- a/packages/firestore/src/api.ts +++ b/packages/firestore/src/api.ts @@ -160,6 +160,7 @@ export { getDocsFromCache, getDocsFromServer, onSnapshot, + onSnapshotBundle, onSnapshotsInSync, setDoc, updateDoc diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index e730fb40da7..f7c225a8461 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -66,7 +66,8 @@ import { FirestoreError } from '../util/error'; import { cast } from '../util/input_validation'; import { ensureFirestoreConfigured, Firestore } from './database'; -import { DocumentSnapshot, QuerySnapshot, SnapshotMetadata } from './snapshot'; +import { DocumentSnapshot, FirestoreDataConverter, QuerySnapshot, SnapshotMetadata } from './snapshot'; +import { loadBundle, namedQuery } from '../api/database'; /** * An options object that can be passed to {@link (onSnapshot:1)} and {@link @@ -680,6 +681,7 @@ export function onSnapshot( | DocumentReference, ...args: unknown[] ): Unsubscribe { + console.log("DEDB Real onSnapshot"); reference = getModularInstance(reference); let options: SnapshotListenOptions = { @@ -732,6 +734,7 @@ export function onSnapshot( complete: args[currArg + 2] as CompleteFn }; } else { + console.log("DEDB real onSnapshot query"); const query = cast>(reference, Query); firestore = cast(query.firestore, Firestore); internalQuery = query._query; @@ -739,6 +742,7 @@ export function onSnapshot( observer = { next: snapshot => { + console.log("DEDB onSnapshot callback next invoked"); if (args[currArg]) { (args[currArg] as NextFn>)( new QuerySnapshot(firestore, userDataWriter, query, snapshot) @@ -761,6 +765,60 @@ export function onSnapshot( ); } +export function onSnapshotBundle( + db: Firestore, + json: { bundle: string, bundleName: string, bundleSource: string }, + onNext: (snapshot: QuerySnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void, + converter?: FirestoreDataConverter +): Unsubscribe { + let unsubscribed: boolean = false; + let internalUnsubscribe: Unsubscribe | undefined; + const bundle = json.bundle; + + console.log("DEDB bundle: ", json); + console.log("DEDB bundle Type: ", json.bundleSource); + + const loadTask = loadBundle(db, bundle); + if (json.bundleSource == 'QuerySnapshot') { + loadTask + .then(() => namedQuery(db, json.bundleName)) + .then((query) => { + console.log("DEDB load Bundle task built query!"); + console.log("DEDB query: ", query); + console.log("DEDB unsubscribed: ", unsubscribed) + if (query && !unsubscribed) { + let realQuery: Query = (query as Query)!; + + if (converter) { + realQuery.withConverter(converter); + } + console.log("DEDB invoking real on Snapshot"); + internalUnsubscribe = onSnapshot( + query as Query, + onNext, + onError, + onCompletion); + } + }); + } else { + // doc type + } + + return () => { + if (unsubscribed) { + return; + } + + unsubscribed = true; + + if (internalUnsubscribe) { + internalUnsubscribe(); + } + } +} + // TODO(firestorexp): Make sure these overloads are tested via the Firestore // integration tests @@ -816,8 +874,8 @@ export function onSnapshotsInSync( const observer = isPartialObserver(arg) ? (arg as PartialObserver) : { - next: arg as () => void - }; + next: arg as () => void + }; return firestoreClientAddSnapshotsInSyncListener(client, observer); } diff --git a/packages/firestore/src/api/snapshot.ts b/packages/firestore/src/api/snapshot.ts index 9d2ddf41a7e..5a7b6108d40 100644 --- a/packages/firestore/src/api/snapshot.ts +++ b/packages/firestore/src/api/snapshot.ts @@ -701,10 +701,12 @@ export class QuerySnapshot< toJSON(): object { // eslint-disable-next-line @typescript-eslint/no-explicit-any const result: any = {}; - result['source'] = 'QuerySnapshot'; + result['bundleSource'] = 'QuerySnapshot'; + result['bundleName'] = AutoId.newId(); + const builder: BundleBuilder = new BundleBuilder( this._firestore, - AutoId.newId() + result['bundleName'] ); const databaseId = this._firestore._databaseId.database; const projectId = this._firestore._databaseId.projectId; @@ -735,6 +737,7 @@ export class QuerySnapshot< ); }); const bundleData: QuerySnapshotBundleData = { + name: result['bundleName'], query: this.query._query, parent, docBundleDataArray diff --git a/packages/firestore/src/util/bundle_builder_impl.ts b/packages/firestore/src/util/bundle_builder_impl.ts index d516e512db0..ae13b686bfc 100644 --- a/packages/firestore/src/util/bundle_builder_impl.ts +++ b/packages/firestore/src/util/bundle_builder_impl.ts @@ -140,13 +140,12 @@ export class BundleBuilder { * dependency error. */ addBundleQuery(queryBundleData: QuerySnapshotBundleData): void { - const name = AutoId.newId(); - if (this.namedQueries.has(name)) { + if (this.namedQueries.has(queryBundleData.name)) { throw new Error(`Query name conflict: ${name} has already been added.`); } let latestReadTime = new Timestamp(0, 0); for (const docBundleData of queryBundleData.docBundleDataArray) { - this.addBundleDocument(docBundleData, name); + this.addBundleDocument(docBundleData, queryBundleData.name); if (docBundleData.readTime && docBundleData.readTime > latestReadTime) { latestReadTime = docBundleData.readTime; } @@ -159,8 +158,8 @@ export class BundleBuilder { parent: queryBundleData.parent, structuredQuery: queryTarget.queryTarget.structuredQuery }; - this.namedQueries.set(name, { - name, + this.namedQueries.set(queryBundleData.name, { + name: queryBundleData.name, bundledQuery, readTime: toTimestamp(this.serializer, latestReadTime) }); @@ -267,6 +266,7 @@ export interface DocumentSnapshotBundleData { * @internal */ export interface QuerySnapshotBundleData { + name: string; query: Query; parent: string; docBundleDataArray: DocumentSnapshotBundleData[]; From 54c89fc64d84506dc4cb4a01d201d53ad205bf62 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Fri, 4 Apr 2025 11:49:27 -0400 Subject: [PATCH 02/29] Bundle snapshot loader part of overload --- common/api-review/firestore.api.md | 7 + packages/firestore/src/api.ts | 1 - packages/firestore/src/api/reference_impl.ts | 265 +++++++++++------- .../firestore/src/util/bundle_builder_impl.ts | 1 - 4 files changed, 170 insertions(+), 104 deletions(-) diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 8640fab03a5..a2596865970 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -461,6 +461,13 @@ export function onSnapshot(query // @public export function onSnapshot(query: Query, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; +// @public (undocumented) +export function onSnapshot(db: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; + // @public (undocumented) export function onSnapshotBundle(db: Firestore, json: { bundle: string; diff --git a/packages/firestore/src/api.ts b/packages/firestore/src/api.ts index bd4087eb042..ea969c6b94c 100644 --- a/packages/firestore/src/api.ts +++ b/packages/firestore/src/api.ts @@ -160,7 +160,6 @@ export { getDocsFromCache, getDocsFromServer, onSnapshot, - onSnapshotBundle, onSnapshotsInSync, setDoc, updateDoc diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index f7c225a8461..118ee78d672 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -17,6 +17,7 @@ import { getModularInstance } from '@firebase/util'; +import { loadBundle, namedQuery } from '../api/database'; import { CompleteFn, ErrorFn, @@ -59,15 +60,20 @@ import { parseUpdateVarargs } from '../lite-api/user_data_reader'; import { AbstractUserDataWriter } from '../lite-api/user_data_writer'; +import { DocumentKey } from '../model/document_key'; import { DeleteMutation, Mutation, Precondition } from '../model/mutation'; import { debugAssert } from '../util/assert'; import { ByteString } from '../util/byte_string'; -import { FirestoreError } from '../util/error'; +import { Code, FirestoreError } from '../util/error'; import { cast } from '../util/input_validation'; import { ensureFirestoreConfigured, Firestore } from './database'; -import { DocumentSnapshot, FirestoreDataConverter, QuerySnapshot, SnapshotMetadata } from './snapshot'; -import { loadBundle, namedQuery } from '../api/database'; +import { + DocumentSnapshot, + FirestoreDataConverter, + QuerySnapshot, + SnapshotMetadata +} from './snapshot'; /** * An options object that can be passed to {@link (onSnapshot:1)} and {@link @@ -675,99 +681,130 @@ export function onSnapshot( onError?: (error: FirestoreError) => void, onCompletion?: () => void ): Unsubscribe; +/// Bundle version +export function onSnapshot( + db: Firestore, + json: { bundle: string; bundleName: string; bundleSource: string }, + onNext: (snapshot: QuerySnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void, + converter?: FirestoreDataConverter +): Unsubscribe; export function onSnapshot( reference: | Query - | DocumentReference, + | DocumentReference + | Firestore, ...args: unknown[] ): Unsubscribe { - console.log("DEDB Real onSnapshot"); - reference = getModularInstance(reference); + if (reference instanceof Firestore) { + // onSnapshot for a bundle + const db = getModularInstance(reference); + const json = args[0] as { + bundle: string; + bundleName: string; + bundleSource: string; + }; + const onNext = args[1] as ( + snapshot: QuerySnapshot + ) => void; + const onError = args[2] as (error: FirestoreError) => void | undefined; + const onCompletion = args[3] as () => void | undefined; + const converter = args[4] as + | FirestoreDataConverter + | undefined; + return onSnapshotBundle(db, json, onNext, onError, onCompletion, converter); + } else { + // onSnapshot for QuerySnapshot or DocumentSnapshot + reference = getModularInstance(reference); + let options: SnapshotListenOptions = { + includeMetadataChanges: false, + source: 'default' + }; + let currArg = 0; + if ( + typeof args[currArg] === 'object' && + !isPartialObserver(args[currArg]) + ) { + options = args[currArg] as SnapshotListenOptions; + currArg++; + } - let options: SnapshotListenOptions = { - includeMetadataChanges: false, - source: 'default' - }; - let currArg = 0; - if (typeof args[currArg] === 'object' && !isPartialObserver(args[currArg])) { - options = args[currArg] as SnapshotListenOptions; - currArg++; - } + const internalOptions = { + includeMetadataChanges: options.includeMetadataChanges, + source: options.source as ListenerDataSource + }; - const internalOptions = { - includeMetadataChanges: options.includeMetadataChanges, - source: options.source as ListenerDataSource - }; + if (isPartialObserver(args[currArg])) { + const userObserver = args[currArg] as PartialObserver< + QuerySnapshot + >; + args[currArg] = userObserver.next?.bind(userObserver); + args[currArg + 1] = userObserver.error?.bind(userObserver); + args[currArg + 2] = userObserver.complete?.bind(userObserver); + } - if (isPartialObserver(args[currArg])) { - const userObserver = args[currArg] as PartialObserver< - QuerySnapshot - >; - args[currArg] = userObserver.next?.bind(userObserver); - args[currArg + 1] = userObserver.error?.bind(userObserver); - args[currArg + 2] = userObserver.complete?.bind(userObserver); - } + let observer: PartialObserver; + let firestore: Firestore; + let internalQuery: InternalQuery; + + if (reference instanceof DocumentReference) { + firestore = cast(reference.firestore, Firestore); + internalQuery = newQueryForPath(reference._key.path); + + observer = { + next: snapshot => { + if (args[currArg]) { + ( + args[currArg] as NextFn< + DocumentSnapshot + > + )( + convertToDocSnapshot( + firestore, + reference as DocumentReference, + snapshot + ) + ); + } + }, + error: args[currArg + 1] as ErrorFn, + complete: args[currArg + 2] as CompleteFn + }; + } else { + const query = cast>(reference, Query); + firestore = cast(query.firestore, Firestore); + internalQuery = query._query; + const userDataWriter = new ExpUserDataWriter(firestore); + + observer = { + next: snapshot => { + if (args[currArg]) { + (args[currArg] as NextFn>)( + new QuerySnapshot(firestore, userDataWriter, query, snapshot) + ); + } + }, + error: args[currArg + 1] as ErrorFn, + complete: args[currArg + 2] as CompleteFn + }; - let observer: PartialObserver; - let firestore: Firestore; - let internalQuery: InternalQuery; - - if (reference instanceof DocumentReference) { - firestore = cast(reference.firestore, Firestore); - internalQuery = newQueryForPath(reference._key.path); - - observer = { - next: snapshot => { - if (args[currArg]) { - ( - args[currArg] as NextFn> - )( - convertToDocSnapshot( - firestore, - reference as DocumentReference, - snapshot - ) - ); - } - }, - error: args[currArg + 1] as ErrorFn, - complete: args[currArg + 2] as CompleteFn - }; - } else { - console.log("DEDB real onSnapshot query"); - const query = cast>(reference, Query); - firestore = cast(query.firestore, Firestore); - internalQuery = query._query; - const userDataWriter = new ExpUserDataWriter(firestore); - - observer = { - next: snapshot => { - console.log("DEDB onSnapshot callback next invoked"); - if (args[currArg]) { - (args[currArg] as NextFn>)( - new QuerySnapshot(firestore, userDataWriter, query, snapshot) - ); - } - }, - error: args[currArg + 1] as ErrorFn, - complete: args[currArg + 2] as CompleteFn - }; + validateHasExplicitOrderByForLimitToLast(reference._query); + } - validateHasExplicitOrderByForLimitToLast(reference._query); + const client = ensureFirestoreConfigured(firestore); + return firestoreClientListen( + client, + internalQuery, + internalOptions, + observer + ); } - - const client = ensureFirestoreConfigured(firestore); - return firestoreClientListen( - client, - internalQuery, - internalOptions, - observer - ); } -export function onSnapshotBundle( +function onSnapshotBundle( db: Firestore, - json: { bundle: string, bundleName: string, bundleSource: string }, + json: { bundle: string; bundleName: string; bundleSource: string }, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, @@ -776,47 +813,71 @@ export function onSnapshotBundle let unsubscribed: boolean = false; let internalUnsubscribe: Unsubscribe | undefined; const bundle = json.bundle; - - console.log("DEDB bundle: ", json); - console.log("DEDB bundle Type: ", json.bundleSource); - const loadTask = loadBundle(db, bundle); - if (json.bundleSource == 'QuerySnapshot') { + if (json.bundleSource === 'QuerySnapshot') { loadTask .then(() => namedQuery(db, json.bundleName)) - .then((query) => { - console.log("DEDB load Bundle task built query!"); - console.log("DEDB query: ", query); - console.log("DEDB unsubscribed: ", unsubscribed) + .then(query => { if (query && !unsubscribed) { - let realQuery: Query = (query as Query)!; - + const realQuery: Query = (query as Query)!; if (converter) { realQuery.withConverter(converter); } - console.log("DEDB invoking real on Snapshot"); internalUnsubscribe = onSnapshot( query as Query, onNext, onError, - onCompletion); + onCompletion + ); + } + }) + .catch(e => { + if (onError) { + onError(e); + } else { + throw e; + } + }); + } else if (json.bundleSource === 'DocumentSnapshot') { + loadTask + .then(() => { + const key = DocumentKey.fromPath(json.bundleName); + const docConverter = converter ? converter : null; + const docReference = new DocumentReference(db, docConverter, key); + internalUnsubscribe = onSnapshot( + docReference as DocumentReference, + onNext, + onError, + onCompletion + ); + }) + .catch(e => { + if (onError) { + onError(e); + } else { + throw e; } }); } else { - // doc type + const error = new FirestoreError( + Code.INVALID_ARGUMENT, + `unsupported bundle source: ${json.bundleSource}` + ); + if (onError) { + onError(error); + } else { + throw error; + } } - return () => { if (unsubscribed) { return; } - unsubscribed = true; - if (internalUnsubscribe) { internalUnsubscribe(); } - } + }; } // TODO(firestorexp): Make sure these overloads are tested via the Firestore @@ -874,8 +935,8 @@ export function onSnapshotsInSync( const observer = isPartialObserver(arg) ? (arg as PartialObserver) : { - next: arg as () => void - }; + next: arg as () => void + }; return firestoreClientAddSnapshotsInSyncListener(client, observer); } diff --git a/packages/firestore/src/util/bundle_builder_impl.ts b/packages/firestore/src/util/bundle_builder_impl.ts index ae13b686bfc..dc94ebca495 100644 --- a/packages/firestore/src/util/bundle_builder_impl.ts +++ b/packages/firestore/src/util/bundle_builder_impl.ts @@ -44,7 +44,6 @@ import { Document as ProtoDocument, Document } from '../protos/firestore_proto_api'; -import { AutoId } from '../util/misc'; const BUNDLE_VERSION = 1; From 593e3cd1455f74c0d49bb15bb091b3d84e4acdc3 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Fri, 4 Apr 2025 15:02:54 -0400 Subject: [PATCH 03/29] Compilation complete --- common/api-review/firestore.api.md | 7 - packages/firestore/src/api/reference_impl.ts | 167 ++++++++++++------- 2 files changed, 111 insertions(+), 63 deletions(-) diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index a2596865970..7124eb73c34 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -468,13 +468,6 @@ export function onSnapshot(db: F bundleSource: string; }, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; -// @public (undocumented) -export function onSnapshotBundle(db: Firestore, json: { - bundle: string; - bundleName: string; - bundleSource: string; -}, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; - // @public export function onSnapshotsInSync(firestore: Firestore, observer: { next?: (value: void) => void; diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index 118ee78d672..a0c293c6e85 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -698,24 +698,60 @@ export function onSnapshot( ...args: unknown[] ): Unsubscribe { if (reference instanceof Firestore) { - // onSnapshot for a bundle + // onSnapshot for a QuerySnapshot or DocumentSnapshot bundle. const db = getModularInstance(reference); const json = args[0] as { bundle: string; bundleName: string; bundleSource: string; }; - const onNext = args[1] as ( - snapshot: QuerySnapshot - ) => void; const onError = args[2] as (error: FirestoreError) => void | undefined; const onCompletion = args[3] as () => void | undefined; const converter = args[4] as | FirestoreDataConverter | undefined; - return onSnapshotBundle(db, json, onNext, onError, onCompletion, converter); + + if (json.bundleSource === 'QuerySnapshot') { + const onNext = args[1] as ( + snapshot: QuerySnapshot + ) => void; + const converter = args[4] as + | FirestoreDataConverter + | undefined; + return onSnapshotQuerySnapshotBundle( + db, + json, + onNext, + onError, + onCompletion, + converter + ); + } else if (json.bundleSource === 'DocumentSnapshot') { + const onNext = args[1] as ( + snapshot: DocumentSnapshot + ) => void; + return onSnapshotDocumentSnapshotBundle( + db, + json, + onNext, + onError, + onCompletion, + converter + ); + } else { + const e = new FirestoreError( + Code.INVALID_ARGUMENT, + `unsupported bundle source: ${json.bundleSource}` + ); + if (onError) { + onError(e); + } else { + throw e; + } + return () => {}; + } } else { - // onSnapshot for QuerySnapshot or DocumentSnapshot + // onSnapshot for Query or Document. reference = getModularInstance(reference); let options: SnapshotListenOptions = { includeMetadataChanges: false, @@ -802,7 +838,58 @@ export function onSnapshot( } } -function onSnapshotBundle( +function onSnapshotDocumentSnapshotBundle< + AppModelType, + DbModelType extends DocumentData +>( + db: Firestore, + json: { bundle: string; bundleName: string; bundleSource: string }, + onNext: (snapshot: DocumentSnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void, + converter?: FirestoreDataConverter +): Unsubscribe { + let unsubscribed: boolean = false; + let internalUnsubscribe: Unsubscribe | undefined; + const bundle = json.bundle; + const loadTask = loadBundle(db, bundle); + loadTask + .then(() => { + const key = DocumentKey.fromPath(json.bundleName); + const docConverter = converter ? converter : null; + const docReference = new DocumentReference(db, docConverter, key); + const observer = { + next: onNext, + error: onError, + complete: onCompletion + }; + internalUnsubscribe = onSnapshot( + docReference as DocumentReference, + observer + ); + }) + .catch(e => { + if (onError) { + onError(e); + } else { + throw e; + } + }); + return () => { + if (unsubscribed) { + return; + } + unsubscribed = true; + if (internalUnsubscribe) { + internalUnsubscribe(); + } + }; +} + +function onSnapshotQuerySnapshotBundle< + AppModelType, + DbModelType extends DocumentData +>( db: Firestore, json: { bundle: string; bundleName: string; bundleSource: string }, onNext: (snapshot: QuerySnapshot) => void, @@ -814,61 +901,29 @@ function onSnapshotBundle( let internalUnsubscribe: Unsubscribe | undefined; const bundle = json.bundle; const loadTask = loadBundle(db, bundle); - if (json.bundleSource === 'QuerySnapshot') { - loadTask - .then(() => namedQuery(db, json.bundleName)) - .then(query => { - if (query && !unsubscribed) { - const realQuery: Query = (query as Query)!; - if (converter) { - realQuery.withConverter(converter); - } - internalUnsubscribe = onSnapshot( - query as Query, - onNext, - onError, - onCompletion - ); + loadTask + .then(() => namedQuery(db, json.bundleName)) + .then(query => { + if (query && !unsubscribed) { + const realQuery: Query = (query as Query)!; + if (converter) { + realQuery.withConverter(converter); } - }) - .catch(e => { - if (onError) { - onError(e); - } else { - throw e; - } - }); - } else if (json.bundleSource === 'DocumentSnapshot') { - loadTask - .then(() => { - const key = DocumentKey.fromPath(json.bundleName); - const docConverter = converter ? converter : null; - const docReference = new DocumentReference(db, docConverter, key); internalUnsubscribe = onSnapshot( - docReference as DocumentReference, + query as Query, onNext, onError, onCompletion ); - }) - .catch(e => { - if (onError) { - onError(e); - } else { - throw e; - } - }); - } else { - const error = new FirestoreError( - Code.INVALID_ARGUMENT, - `unsupported bundle source: ${json.bundleSource}` - ); - if (onError) { - onError(error); - } else { - throw error; - } - } + } + }) + .catch(e => { + if (onError) { + onError(e); + } else { + throw e; + } + }); return () => { if (unsubscribed) { return; From 8a181e1ae7e14d840c70e8d5600115a9de2c079c Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 7 Apr 2025 10:47:25 -0400 Subject: [PATCH 04/29] Overloaded onSnapshot signature impl. --- common/api-review/firestore.api.md | 35 +- packages/firestore/src/api/reference_impl.ts | 627 ++++++++++++------- 2 files changed, 431 insertions(+), 231 deletions(-) diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 7124eb73c34..d077ceae07a 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -456,18 +456,47 @@ export function onSnapshot(query }): Unsubscribe; // @public -export function onSnapshot(query: Query, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; +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 (undocumented) -export function onSnapshot(db: Firestore, json: { +// @public +export function onSnapshot(firestore: Firestore, json: { bundle: string; bundleName: string; bundleSource: string; }, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +// @public +export function onSnapshot(firestore: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; + +// @public +export function onSnapshot(firestore: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, observer: { + next: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}, converter?: FirestoreDataConverter): Unsubscribe; + +// @public +export function onSnapshot(firestore: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, options: SnapshotListenOptions, observer: { + next: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}, converter?: FirestoreDataConverter): Unsubscribe; + // @public export function onSnapshotsInSync(firestore: Firestore, observer: { next?: (value: void) => void; diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index a0c293c6e85..1e5218dddb2 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -650,7 +650,7 @@ export function onSnapshot( */ export function onSnapshot( query: Query, - onNext: (snapshot: QuerySnapshot) => void, + onNext?: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void ): Unsubscribe; @@ -681,15 +681,127 @@ export function onSnapshot( onError?: (error: FirestoreError) => void, onCompletion?: () => void ): Unsubscribe; -/// Bundle version + +/** + * Attaches a listener for `QuerySnapshot` events based on QuerySnapshot data generated by invoking + * {@link QuerySnapshot.toJSON} You may either pass individual `onNext` and `onError` callbacks or + * pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param firestore - The {@link Firestore} instance to enable persistence for. + * @param json - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. + * @param onNext - A callback to be called every time a new `QuerySnapshot` + * is available. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @param converter - An optional object that converts objects from Firestore before the onNext + * listener is invoked. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ export function onSnapshot( - db: Firestore, + firestore: Firestore, json: { bundle: string; bundleName: string; bundleSource: string }, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter ): Unsubscribe; + +/** + * Attaches a listener for `QuerySnapshot` events based on QuerySnapshot data generated by invoking + * {@link QuerySnapshot.toJSON} You may either pass individual `onNext` and `onError` callbacks or + * pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param firestore - The {@link Firestore} instance to enable persistence for. + * @param json - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. + * @param options - Options controlling the listen behavior. + * @param onNext - A callback to be called every time a new `QuerySnapshot` + * is available. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @param converter - An optional object that converts objects from Firestore before the onNext + * listener is invoked. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export function onSnapshot( + firestore: Firestore, + json: { bundle: string; bundleName: string; bundleSource: string }, + options: SnapshotListenOptions, + onNext: (snapshot: QuerySnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void, + converter?: FirestoreDataConverter +): Unsubscribe; + +/** + * Attaches a listener for `QuerySnapshot` events based on QuerySnapshot data generated by invoking + * {@link QuerySnapshot.toJSON} You may either pass individual `onNext` and `onError` callbacks or + * pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param firestore - The {@link Firestore} instance to enable persistence for. + * @param json - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. + * @param observer - A single object containing `next` and `error` callbacks. + * @param converter - An optional object that converts objects from Firestore before the onNext + * listener is invoked. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export function onSnapshot( + firestore: Firestore, + json: { bundle: string; bundleName: string; bundleSource: string }, + observer: { + next: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + }, + converter?: FirestoreDataConverter +): Unsubscribe; +/** + * Attaches a listener for `QuerySnapshot` events based on QuerySnapshot data generated by invoking + * {@link QuerySnapshot.toJSON} You may either pass individual `onNext` and `onError` callbacks or + * pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param firestore - The {@link Firestore} instance to enable persistence for. + * @param json - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. + * @param options - Options controlling the listen behavior. + * @param observer - A single object containing `next` and `error` callbacks. + * @param converter - An optional object that converts objects from Firestore before the onNext + * listener is invoked. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export function onSnapshot( + firestore: Firestore, + json: { bundle: string; bundleName: string; bundleSource: string }, + options: SnapshotListenOptions, + observer: { + next: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + }, + converter?: FirestoreDataConverter +): Unsubscribe; export function onSnapshot( reference: | Query @@ -698,241 +810,88 @@ export function onSnapshot( ...args: unknown[] ): Unsubscribe { if (reference instanceof Firestore) { - // onSnapshot for a QuerySnapshot or DocumentSnapshot bundle. - const db = getModularInstance(reference); - const json = args[0] as { - bundle: string; - bundleName: string; - bundleSource: string; - }; - const onError = args[2] as (error: FirestoreError) => void | undefined; - const onCompletion = args[3] as () => void | undefined; - const converter = args[4] as - | FirestoreDataConverter - | undefined; - - if (json.bundleSource === 'QuerySnapshot') { - const onNext = args[1] as ( - snapshot: QuerySnapshot - ) => void; - const converter = args[4] as - | FirestoreDataConverter - | undefined; - return onSnapshotQuerySnapshotBundle( - db, - json, - onNext, - onError, - onCompletion, - converter - ); - } else if (json.bundleSource === 'DocumentSnapshot') { - const onNext = args[1] as ( - snapshot: DocumentSnapshot - ) => void; - return onSnapshotDocumentSnapshotBundle( - db, - json, - onNext, - onError, - onCompletion, - converter - ); - } else { - const e = new FirestoreError( - Code.INVALID_ARGUMENT, - `unsupported bundle source: ${json.bundleSource}` - ); - if (onError) { - onError(e); - } else { - throw e; - } - return () => {}; - } - } else { - // onSnapshot for Query or Document. - reference = getModularInstance(reference); - let options: SnapshotListenOptions = { - includeMetadataChanges: false, - source: 'default' - }; - let currArg = 0; - if ( - typeof args[currArg] === 'object' && - !isPartialObserver(args[currArg]) - ) { - options = args[currArg] as SnapshotListenOptions; - currArg++; - } + return onSnapshotBundle(reference as Firestore, args); + } - const internalOptions = { - includeMetadataChanges: options.includeMetadataChanges, - source: options.source as ListenerDataSource - }; + // onSnapshot for Query or Document. + reference = getModularInstance(reference); + let options: SnapshotListenOptions = { + includeMetadataChanges: false, + source: 'default' + }; + let currArg = 0; + if (typeof args[currArg] === 'object' && !isPartialObserver(args[currArg])) { + options = args[currArg] as SnapshotListenOptions; + currArg++; + } - if (isPartialObserver(args[currArg])) { - const userObserver = args[currArg] as PartialObserver< - QuerySnapshot - >; - args[currArg] = userObserver.next?.bind(userObserver); - args[currArg + 1] = userObserver.error?.bind(userObserver); - args[currArg + 2] = userObserver.complete?.bind(userObserver); - } + const internalOptions = { + includeMetadataChanges: options.includeMetadataChanges, + source: options.source as ListenerDataSource + }; - let observer: PartialObserver; - let firestore: Firestore; - let internalQuery: InternalQuery; + if (isPartialObserver(args[currArg])) { + const userObserver = args[currArg] as PartialObserver< + QuerySnapshot + >; + args[currArg] = userObserver.next?.bind(userObserver); + args[currArg + 1] = userObserver.error?.bind(userObserver); + args[currArg + 2] = userObserver.complete?.bind(userObserver); + } - if (reference instanceof DocumentReference) { - firestore = cast(reference.firestore, Firestore); - internalQuery = newQueryForPath(reference._key.path); + let observer: PartialObserver; + let firestore: Firestore; + let internalQuery: InternalQuery; - observer = { - next: snapshot => { - if (args[currArg]) { - ( - args[currArg] as NextFn< - DocumentSnapshot - > - )( - convertToDocSnapshot( - firestore, - reference as DocumentReference, - snapshot - ) - ); - } - }, - error: args[currArg + 1] as ErrorFn, - complete: args[currArg + 2] as CompleteFn - }; - } else { - const query = cast>(reference, Query); - firestore = cast(query.firestore, Firestore); - internalQuery = query._query; - const userDataWriter = new ExpUserDataWriter(firestore); + if (reference instanceof DocumentReference) { + firestore = cast(reference.firestore, Firestore); + internalQuery = newQueryForPath(reference._key.path); - observer = { - next: snapshot => { - if (args[currArg]) { - (args[currArg] as NextFn>)( - new QuerySnapshot(firestore, userDataWriter, query, snapshot) - ); - } - }, - error: args[currArg + 1] as ErrorFn, - complete: args[currArg + 2] as CompleteFn - }; + observer = { + next: snapshot => { + if (args[currArg]) { + ( + args[currArg] as NextFn> + )( + convertToDocSnapshot( + firestore, + reference as DocumentReference, + snapshot + ) + ); + } + }, + error: args[currArg + 1] as ErrorFn, + complete: args[currArg + 2] as CompleteFn + }; + } else { + const query = cast>(reference, Query); + firestore = cast(query.firestore, Firestore); + internalQuery = query._query; + const userDataWriter = new ExpUserDataWriter(firestore); - validateHasExplicitOrderByForLimitToLast(reference._query); - } + observer = { + next: snapshot => { + if (args[currArg]) { + (args[currArg] as NextFn>)( + new QuerySnapshot(firestore, userDataWriter, query, snapshot) + ); + } + }, + error: args[currArg + 1] as ErrorFn, + complete: args[currArg + 2] as CompleteFn + }; - const client = ensureFirestoreConfigured(firestore); - return firestoreClientListen( - client, - internalQuery, - internalOptions, - observer - ); + validateHasExplicitOrderByForLimitToLast(reference._query); } -} - -function onSnapshotDocumentSnapshotBundle< - AppModelType, - DbModelType extends DocumentData ->( - db: Firestore, - json: { bundle: string; bundleName: string; bundleSource: string }, - onNext: (snapshot: DocumentSnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void, - converter?: FirestoreDataConverter -): Unsubscribe { - let unsubscribed: boolean = false; - let internalUnsubscribe: Unsubscribe | undefined; - const bundle = json.bundle; - const loadTask = loadBundle(db, bundle); - loadTask - .then(() => { - const key = DocumentKey.fromPath(json.bundleName); - const docConverter = converter ? converter : null; - const docReference = new DocumentReference(db, docConverter, key); - const observer = { - next: onNext, - error: onError, - complete: onCompletion - }; - internalUnsubscribe = onSnapshot( - docReference as DocumentReference, - observer - ); - }) - .catch(e => { - if (onError) { - onError(e); - } else { - throw e; - } - }); - return () => { - if (unsubscribed) { - return; - } - unsubscribed = true; - if (internalUnsubscribe) { - internalUnsubscribe(); - } - }; -} -function onSnapshotQuerySnapshotBundle< - AppModelType, - DbModelType extends DocumentData ->( - db: Firestore, - json: { bundle: string; bundleName: string; bundleSource: string }, - onNext: (snapshot: QuerySnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void, - converter?: FirestoreDataConverter -): Unsubscribe { - let unsubscribed: boolean = false; - let internalUnsubscribe: Unsubscribe | undefined; - const bundle = json.bundle; - const loadTask = loadBundle(db, bundle); - loadTask - .then(() => namedQuery(db, json.bundleName)) - .then(query => { - if (query && !unsubscribed) { - const realQuery: Query = (query as Query)!; - if (converter) { - realQuery.withConverter(converter); - } - internalUnsubscribe = onSnapshot( - query as Query, - onNext, - onError, - onCompletion - ); - } - }) - .catch(e => { - if (onError) { - onError(e); - } else { - throw e; - } - }); - return () => { - if (unsubscribed) { - return; - } - unsubscribed = true; - if (internalUnsubscribe) { - internalUnsubscribe(); - } - }; + const client = ensureFirestoreConfigured(firestore); + return firestoreClientListen( + client, + internalQuery, + internalOptions, + observer + ); } // TODO(firestorexp): Make sure these overloads are tested via the Firestore @@ -1033,3 +992,215 @@ function convertToDocSnapshot( ref.converter ); } + +// onSnapshot for a QuerySnapshot or DocumentSnapshot bundle. Parses the bundle and then +// invokes onSnapshot with a Document or Query reference. +function onSnapshotBundle( + reference: Firestore, + ...args: unknown[] +): Unsubscribe { + const db = getModularInstance(reference); + let curArg = 0; + const json = args[curArg++] as { + bundle: string; + bundleName: string; + bundleSource: string; + }; + + let options: SnapshotListenOptions | undefined = undefined; + if (typeof args[curArg] === 'object' && !isPartialObserver(args[curArg])) { + options = args[curArg++] as SnapshotListenOptions; + } + + let error: undefined | ((error: FirestoreError) => void) = undefined; + let complete: undefined | (() => void) = undefined; + + if (json.bundleSource === 'QuerySnapshot') { + let next: + | undefined + | ((snapshot: QuerySnapshot) => void); + if (typeof args[curArg] === 'object' && isPartialObserver(args[1])) { + const userObserver = args[curArg] as PartialObserver< + QuerySnapshot + >; + next = userObserver.next?.bind(userObserver); + error = userObserver.error?.bind(userObserver); + complete = userObserver.complete?.bind(userObserver); + curArg++; + } else { + next = args[curArg++] as ( + snapshot: QuerySnapshot + ) => void; + error = args[curArg++] as (error: FirestoreError) => void; + complete = args[curArg++] as () => void; + } + const converter = args[curArg] as + | FirestoreDataConverter + | undefined; + return onSnapshotQuerySnapshotBundle( + db, + json, + options, + { + next: next!, + error, + complete + }, + converter + ); + } else if (json.bundleSource === 'DocumentSnapshot') { + let next: + | undefined + | ((snapshot: DocumentSnapshot) => void); + if (typeof args[curArg] === 'object' && isPartialObserver(args[1])) { + const userObserver = args[curArg] as PartialObserver< + DocumentSnapshot + >; + next = userObserver.next?.bind(userObserver); + error = userObserver.error?.bind(userObserver); + complete = userObserver.complete?.bind(userObserver); + curArg++; + } else { + next = args[curArg++] as ( + snapshot: DocumentSnapshot + ) => void; + error = args[curArg++] as (error: FirestoreError) => void; + complete = args[curArg++] as () => void; + } + const converter = args[curArg] as + | FirestoreDataConverter + | undefined; + return onSnapshotDocumentSnapshotBundle( + db, + json, + options, + { + next: next!, + error, + complete + }, + converter + ); + } else { + throw new FirestoreError( + Code.INVALID_ARGUMENT, + `unsupported bundle source: ${json.bundleSource}` + ); + } +} + +// Loads the bundle in a separate task and then invokes onSnapshot with a DocumentReference +// for the document in the bundle. Returns the unsubscribe callback immediately. +function onSnapshotDocumentSnapshotBundle< + AppModelType, + DbModelType extends DocumentData +>( + db: Firestore, + json: { bundle: string; bundleName: string; bundleSource: string }, + options: SnapshotListenOptions | undefined, + observer: { + next: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + }, + converter?: FirestoreDataConverter +): Unsubscribe { + let unsubscribed: boolean = false; + let internalUnsubscribe: Unsubscribe | undefined; + const bundle = json.bundle; + const loadTask = loadBundle(db, bundle); + loadTask + .then(() => { + const key = DocumentKey.fromPath(json.bundleName); + const docConverter = converter ? converter : null; + const docReference = new DocumentReference(db, docConverter, key); + if (options !== undefined) { + internalUnsubscribe = onSnapshot( + docReference as DocumentReference, + options, + observer + ); + } else { + internalUnsubscribe = onSnapshot( + docReference as DocumentReference, + observer + ); + } + }) + .catch(e => { + if (observer.error) { + observer.error(e); + } else { + throw e; + } + }); + return () => { + if (unsubscribed) { + return; + } + unsubscribed = true; + if (internalUnsubscribe) { + internalUnsubscribe(); + } + }; +} + +// Loads the bundle in a separate task and then invokes onSnapshot with a Query +// for the documents in the bundle. Returns the unsubscribe callback immediately. +function onSnapshotQuerySnapshotBundle< + AppModelType, + DbModelType extends DocumentData +>( + db: Firestore, + json: { bundle: string; bundleName: string; bundleSource: string }, + options: SnapshotListenOptions | undefined, + observer: { + next: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + }, + converter?: FirestoreDataConverter +): Unsubscribe { + let unsubscribed: boolean = false; + let internalUnsubscribe: Unsubscribe | undefined; + const bundle = json.bundle; + const loadTask = loadBundle(db, bundle); + loadTask + .then(() => namedQuery(db, json.bundleName)) + .then(query => { + if (query && !unsubscribed) { + const realQuery: Query = (query as Query)!; + if (converter) { + realQuery.withConverter(converter); + } + if (options !== undefined) { + internalUnsubscribe = onSnapshot( + query as Query, + options, + observer + ); + } else { + internalUnsubscribe = onSnapshot( + query as Query, + observer + ); + } + } + }) + .catch(e => { + if (observer.error) { + observer.error(e); + } else { + throw e; + } + }); + return () => { + if (unsubscribed) { + return; + } + unsubscribed = true; + if (internalUnsubscribe) { + internalUnsubscribe(); + } + }; +} From 7588ffd295294cab82144700af2206c596c85bc9 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 7 Apr 2025 14:56:49 -0400 Subject: [PATCH 05/29] cleanup observer marshalling --- common/api-review/firestore.api.md | 2 +- packages/firestore/src/api/reference_impl.ts | 115 +++++++++---------- 2 files changed, 54 insertions(+), 63 deletions(-) diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index d077ceae07a..3600722843e 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -456,7 +456,7 @@ export function onSnapshot(query }): Unsubscribe; // @public -export function onSnapshot(query: Query, onNext?: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; +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; diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index 1e5218dddb2..5692860403c 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -650,7 +650,7 @@ export function onSnapshot( */ export function onSnapshot( query: Query, - onNext?: (snapshot: QuerySnapshot) => void, + onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void ): Unsubscribe; @@ -810,7 +810,7 @@ export function onSnapshot( ...args: unknown[] ): Unsubscribe { if (reference instanceof Firestore) { - return onSnapshotBundle(reference as Firestore, args); + return onSnapshotBundle(reference as Firestore, ...args); } // onSnapshot for Query or Document. @@ -821,8 +821,7 @@ export function onSnapshot( }; let currArg = 0; if (typeof args[currArg] === 'object' && !isPartialObserver(args[currArg])) { - options = args[currArg] as SnapshotListenOptions; - currArg++; + options = args[currArg++] as SnapshotListenOptions; } const internalOptions = { @@ -869,7 +868,6 @@ export function onSnapshot( firestore = cast(query.firestore, Firestore); internalQuery = query._query; const userDataWriter = new ExpUserDataWriter(firestore); - observer = { next: snapshot => { if (args[currArg]) { @@ -1012,75 +1010,68 @@ function onSnapshotBundle( options = args[curArg++] as SnapshotListenOptions; } - let error: undefined | ((error: FirestoreError) => void) = undefined; - let complete: undefined | (() => void) = undefined; - if (json.bundleSource === 'QuerySnapshot') { - let next: - | undefined - | ((snapshot: QuerySnapshot) => void); if (typeof args[curArg] === 'object' && isPartialObserver(args[1])) { - const userObserver = args[curArg] as PartialObserver< + const userObserver = args[curArg++] as PartialObserver< QuerySnapshot >; - next = userObserver.next?.bind(userObserver); - error = userObserver.error?.bind(userObserver); - complete = userObserver.complete?.bind(userObserver); - curArg++; + return onSnapshotQuerySnapshotBundle( + db, + json, + options, + { + next: userObserver.next!, + error: userObserver.error, + complete: userObserver.complete + }, + args[curArg] as FirestoreDataConverter + ); } else { - next = args[curArg++] as ( - snapshot: QuerySnapshot - ) => void; - error = args[curArg++] as (error: FirestoreError) => void; - complete = args[curArg++] as () => void; + return onSnapshotQuerySnapshotBundle( + db, + json, + options, + { + next: args[curArg++] as ( + snapshot: QuerySnapshot + ) => void, + error: args[curArg++] as (error: FirestoreError) => void, + complete: args[curArg++] as () => void + }, + args[curArg] as FirestoreDataConverter + ); } - const converter = args[curArg] as - | FirestoreDataConverter - | undefined; - return onSnapshotQuerySnapshotBundle( - db, - json, - options, - { - next: next!, - error, - complete - }, - converter - ); } else if (json.bundleSource === 'DocumentSnapshot') { - let next: - | undefined - | ((snapshot: DocumentSnapshot) => void); if (typeof args[curArg] === 'object' && isPartialObserver(args[1])) { - const userObserver = args[curArg] as PartialObserver< + const userObserver = args[curArg++] as PartialObserver< DocumentSnapshot >; - next = userObserver.next?.bind(userObserver); - error = userObserver.error?.bind(userObserver); - complete = userObserver.complete?.bind(userObserver); - curArg++; + return onSnapshotDocumentSnapshotBundle( + db, + json, + options, + { + next: userObserver.next!, + error: userObserver.error, + complete: userObserver.complete + }, + args[curArg] as FirestoreDataConverter + ); } else { - next = args[curArg++] as ( - snapshot: DocumentSnapshot - ) => void; - error = args[curArg++] as (error: FirestoreError) => void; - complete = args[curArg++] as () => void; + return onSnapshotDocumentSnapshotBundle( + db, + json, + options, + { + next: args[curArg++] as ( + snapshot: DocumentSnapshot + ) => void, + error: args[curArg++] as (error: FirestoreError) => void, + complete: args[curArg++] as () => void + }, + args[curArg] as FirestoreDataConverter + ); } - const converter = args[curArg] as - | FirestoreDataConverter - | undefined; - return onSnapshotDocumentSnapshotBundle( - db, - json, - options, - { - next: next!, - error, - complete - }, - converter - ); } else { throw new FirestoreError( Code.INVALID_ARGUMENT, From 10bf743e11340a0679238fcb0cc622cfa35ff0b7 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 8 Apr 2025 09:50:48 -0400 Subject: [PATCH 06/29] DocumentSnapshot. bundleSource & name fix --- packages/firestore/src/api/snapshot.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/firestore/src/api/snapshot.ts b/packages/firestore/src/api/snapshot.ts index 5a7b6108d40..4f2f056d720 100644 --- a/packages/firestore/src/api/snapshot.ts +++ b/packages/firestore/src/api/snapshot.ts @@ -508,7 +508,8 @@ export class DocumentSnapshot< // eslint-disable-next-line @typescript-eslint/no-explicit-any const result: any = {}; result['bundle'] = ''; - result['source'] = 'DocumentSnapshot'; + result['bundleSource'] = 'DocumentSnapshot'; + result['bundleName'] = this._key.toString(); if ( !document || From f88748f5c7f7ef44c9fa99be3d0b2bb1e9154428 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 8 Apr 2025 15:32:30 -0400 Subject: [PATCH 07/29] onNext overloads and toJSON type --- common/api-review/firestore.api.md | 48 ++- packages/firestore/src/api/reference_impl.ts | 294 ++++++++++++------- packages/firestore/src/api/snapshot.ts | 4 +- 3 files changed, 237 insertions(+), 109 deletions(-) diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 3600722843e..70986703d5d 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -179,7 +179,11 @@ export class DocumentSnapshot; // (undocumented) - toJSON(): object; + toJSON(): { + bundle: string; + bundleSource: string; + bundleName: string; + }; } export { EmulatorMockTokenOptions } @@ -468,6 +472,13 @@ export function onSnapshot(fires bundleSource: string; }, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +// @public +export function onSnapshot(firestore: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; + // @public export function onSnapshot(firestore: Firestore, json: { bundle: string; @@ -475,6 +486,13 @@ export function onSnapshot(fires bundleSource: string; }, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +// @public +export function onSnapshot(firestore: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; + // @public export function onSnapshot(firestore: Firestore, json: { bundle: string; @@ -486,6 +504,17 @@ export function onSnapshot(fires complete?: () => void; }, converter?: FirestoreDataConverter): Unsubscribe; +// @public +export function onSnapshot(firestore: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, observer: { + next: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}, converter?: FirestoreDataConverter): Unsubscribe; + // @public export function onSnapshot(firestore: Firestore, json: { bundle: string; @@ -497,6 +526,17 @@ export function onSnapshot(fires complete?: () => void; }, converter?: FirestoreDataConverter): Unsubscribe; +// @public +export function onSnapshot(firestore: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, options: SnapshotListenOptions, observer: { + next: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}, converter?: FirestoreDataConverter): Unsubscribe; + // @public export function onSnapshotsInSync(firestore: Firestore, observer: { next?: (value: void) => void; @@ -649,7 +689,11 @@ export class QuerySnapshot; get size(): number; // (undocumented) - toJSON(): object; + toJSON(): { + bundle: string; + bundleSource: string; + bundleName: string; + }; } // @public diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index 5692860403c..68c707bc085 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -490,12 +490,11 @@ export interface Unsubscribe { // integration tests /** - * Attaches a listener for `DocumentSnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. + * Attaches a listener for `DocumentSnapshot` events. You may either pass individual `onNext` and + * `onError` callbacks or pass a single observer object with `next` and `error` callbacks. * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. * * @param reference - A reference to the document to listen to. * @param observer - A single object containing `next` and `error` callbacks. @@ -511,12 +510,11 @@ export function onSnapshot( } ): Unsubscribe; /** - * Attaches a listener for `DocumentSnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. + * Attaches a listener for `DocumentSnapshot` events. You may either pass individual `onNext` and + * `onError` callbacks or pass a single observer object with `next` and `error` callbacks. * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. * * @param reference - A reference to the document to listen to. * @param options - Options controlling the listen behavior. @@ -534,22 +532,18 @@ export function onSnapshot( } ): Unsubscribe; /** - * Attaches a listener for `DocumentSnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. + * Attaches a listener for `DocumentSnapshot` events. You may either pass individual `onNext` and + * `onError` callbacks or pass a single observer object with `next` and `error` callbacks. * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. * * @param reference - A reference to the document to listen to. - * @param onNext - A callback to be called every time a new `DocumentSnapshot` - * is available. - * @param onError - A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @param onCompletion - Can be provided, but will not be called since streams are - * never ending. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. + * @param onNext - A callback to be called every time a new `DocumentSnapshot` is available. + * @param onError - A callback to be called if the listen fails or is cancelled. No further + * callbacks will occur. + * @param onCompletion - Can be provided, but will not be called since streams are never ending. + * @returns An unsubscribe function that can be called to cancel the snapshot listener. */ export function onSnapshot( reference: DocumentReference, @@ -558,23 +552,19 @@ export function onSnapshot( onCompletion?: () => void ): Unsubscribe; /** - * Attaches a listener for `DocumentSnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. + * Attaches a listener for `DocumentSnapshot` events. You may either pass individual `onNext` and + * `onError` callbacks or pass a single observer object with `next` and `error` callbacks. * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. * * @param reference - A reference to the document to listen to. * @param options - Options controlling the listen behavior. - * @param onNext - A callback to be called every time a new `DocumentSnapshot` - * is available. - * @param onError - A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @param onCompletion - Can be provided, but will not be called since streams are - * never ending. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. + * @param onNext - A callback to be called every time a new `DocumentSnapshot` is available. + * @param onError - A callback to be called if the listen fails or is cancelled. No further + * callbacks will occur. + * @param onCompletion - Can be provided, but will not be called since streams are never ending. + * @returns An unsubscribe function that can be called to cancel the snapshot listener. */ export function onSnapshot( reference: DocumentReference, @@ -584,18 +574,16 @@ export function onSnapshot( onCompletion?: () => void ): Unsubscribe; /** - * Attaches a listener for `QuerySnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. + * Attaches a listener for `QuerySnapshot` events. You may either pass individual `onNext` and + * `onError` callbacks or pass a single observer object with `next` and `error` callbacks. The + * listener can be cancelled by calling the function that is returned when `onSnapshot` is called. * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. * * @param query - The query to listen to. * @param observer - A single object containing `next` and `error` callbacks. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. + * @returns An unsubscribe function that can be called to cancel the snapshot listener. */ export function onSnapshot( query: Query, @@ -606,19 +594,17 @@ export function onSnapshot( } ): Unsubscribe; /** - * Attaches a listener for `QuerySnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. + * Attaches a listener for `QuerySnapshot` events. You may either pass individual `onNext` and + * `onError` callbacks or pass a single observer object with `next` and `error` callbacks. The + * listener can be cancelled by calling the function that is returned when `onSnapshot` is called. * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. * * @param query - The query to listen to. * @param options - Options controlling the listen behavior. * @param observer - A single object containing `next` and `error` callbacks. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. + * @returns An unsubscribe function that can be called to cancel the snapshot listener. */ export function onSnapshot( query: Query, @@ -630,23 +616,19 @@ export function onSnapshot( } ): Unsubscribe; /** - * Attaches a listener for `QuerySnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. + * Attaches a listener for `QuerySnapshot` events. You may either pass individual `onNext` and + * `onError` callbacks or pass a single observer object with `next` and `error` callbacks. The + * listener can be cancelled by calling the function that is returned when `onSnapshot` is called. * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. * * @param query - The query to listen to. - * @param onNext - A callback to be called every time a new `QuerySnapshot` - * is available. - * @param onCompletion - Can be provided, but will not be called since streams are - * never ending. - * @param onError - A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. + * @param onNext - A callback to be called every time a new `QuerySnapshot` is available. + * @param onCompletion - Can be provided, but will not be called since streams are never ending. + * @param onError - A callback to be called if the listen fails or is cancelled. No further + * callbacks will occur. + * @returns An unsubscribe function that can be called to cancel the snapshot listener. */ export function onSnapshot( query: Query, @@ -655,24 +637,20 @@ export function onSnapshot( onCompletion?: () => void ): Unsubscribe; /** - * Attaches a listener for `QuerySnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. + * Attaches a listener for `QuerySnapshot` events. You may either pass individual `onNext` and + * `onError` callbacks or pass a single observer object with `next` and `error` callbacks. The + * listener can be cancelled by calling the function that is returned when `onSnapshot` is called. * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. * * @param query - The query to listen to. * @param options - Options controlling the listen behavior. - * @param onNext - A callback to be called every time a new `QuerySnapshot` - * is available. - * @param onCompletion - Can be provided, but will not be called since streams are - * never ending. - * @param onError - A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. + * @param onNext - A callback to be called every time a new `QuerySnapshot` is available. + * @param onCompletion - Can be provided, but will not be called since streams are never ending. + * @param onError - A callback to be called if the listen fails or is cancelled. No further + * callbacks will occur. + * @returns An unsubscribe function that can be called to cancel the snapshot listener. */ export function onSnapshot( query: Query, @@ -683,54 +661,105 @@ export function onSnapshot( ): Unsubscribe; /** - * Attaches a listener for `QuerySnapshot` events based on QuerySnapshot data generated by invoking + * Attaches a listener for `QuerySnapshot` events based on data generated by invoking * {@link QuerySnapshot.toJSON} You may either pass individual `onNext` and `onError` callbacks or * pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by * calling the function that is returned when `onSnapshot` is called. * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. * * @param firestore - The {@link Firestore} instance to enable persistence for. * @param json - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. - * @param onNext - A callback to be called every time a new `QuerySnapshot` - * is available. - * @param onError - A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. + * @param onNext - A callback to be called every time a new `QuerySnapshot` is available. + * @param onError - A callback to be called if the listen fails or is cancelled. No further + * callbacks will occur. + * @param onCompletion - Can be provided, but will not be called since streams are never ending. + * @param converter - An optional object that converts objects from Firestore before the onNext + * listener is invoked. + * @returns An unsubscribe function that can be called to cancel the snapshot listener. + */ +export function onSnapshot( + firestore: Firestore, + json: { bundle: string; bundleName: string; bundleSource: string }, + onNext: (snapshot: QuerySnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void, + converter?: FirestoreDataConverter +): Unsubscribe; +/** + * Attaches a listener for `DocumentSnapshot` events based on data generated by invoking + * {@link DocumentSnapshot.toJSON} You may either pass individual `onNext` and `onError` callbacks or + * pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. + * + * @param firestore - The {@link Firestore} instance to enable persistence for. + * @param json - A JSON object generated by invoking {@link DocumentSnapshot.toJSON}. + * @param onNext - A callback to be called every time a new `DocumentSnapshot` is available. + * @param onError - A callback to be called if the listen fails or is cancelled. No fruther + * callbacks will occur. * @param onCompletion - Can be provided, but will not be called since streams are * never ending. * @param converter - An optional object that converts objects from Firestore before the onNext * listener is invoked. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. + * @returns An unsubscribe function that can be called to cancel the snapshot listener. */ export function onSnapshot( firestore: Firestore, json: { bundle: string; bundleName: string; bundleSource: string }, - onNext: (snapshot: QuerySnapshot) => void, + onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter ): Unsubscribe; - /** - * Attaches a listener for `QuerySnapshot` events based on QuerySnapshot data generated by invoking + * Attaches a listener for `QuerySnapshot` events based on data generated by invoking * {@link QuerySnapshot.toJSON} You may either pass individual `onNext` and `onError` callbacks or * pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by * calling the function that is returned when `onSnapshot` is called. * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. * * @param firestore - The {@link Firestore} instance to enable persistence for. * @param json - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. * @param options - Options controlling the listen behavior. - * @param onNext - A callback to be called every time a new `QuerySnapshot` - * is available. - * @param onError - A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @param onCompletion - Can be provided, but will not be called since streams are - * never ending. + * @param onNext - A callback to be called every time a new `QuerySnapshot` is available. + * @param onError - A callback to be called if the listen fails or is cancelled. No further + * callbacks will occur. + * @param onCompletion - Can be provided, but will not be called since streams are never ending. + * @param converter - An optional object that converts objects from Firestore before the onNext + * listener is invoked. + * @returns An unsubscribe function that can be called to cancel the snapshot listener. + */ +export function onSnapshot( + firestore: Firestore, + json: { bundle: string; bundleName: string; bundleSource: string }, + options: SnapshotListenOptions, + onNext: (snapshot: QuerySnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void, + converter?: FirestoreDataConverter +): Unsubscribe; +/** + * Attaches a listener for `DocumentSnapshot` events based on data generated by invoking + * {@link DocumentSnapshot.toJSON} You may either pass individual `onNext` and `onError` callbacks + * or pass a single observer object with `next` and `error` callbacks. The listener can be cancelled + * by calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. + * + * @param firestore - The {@link Firestore} instance to enable persistence for. + * @param json - A JSON object generated by invoking {@link DocumentSnapshot.toJSON}. + * @param options - Options controlling the listen behavior. + * @param onNext - A callback to be called every time a new `DocumentSnapshot` is available. + * @param onError - A callback to be called if the listen fails or is cancelled. No further + * callbacks will occur. + * @param onCompletion - Can be provided, but will not be called since streams are never ending. * @param converter - An optional object that converts objects from Firestore before the onNext * listener is invoked. * @returns An unsubscribe function that can be called to cancel @@ -740,7 +769,7 @@ export function onSnapshot( firestore: Firestore, json: { bundle: string; bundleName: string; bundleSource: string }, options: SnapshotListenOptions, - onNext: (snapshot: QuerySnapshot) => void, + onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter @@ -752,8 +781,8 @@ export function onSnapshot( * pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by * calling the function that is returned when `onSnapshot` is called. * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. * * @param firestore - The {@link Firestore} instance to enable persistence for. * @param json - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. @@ -773,14 +802,41 @@ export function onSnapshot( }, converter?: FirestoreDataConverter ): Unsubscribe; +/** + * Attaches a listener for `DocumentSnapshot` events based on data generated by invoking + * {@link DocumentSnapshot.toJSON} You may either pass individual `onNext` and `onError` callbacks + * or pass a single observer object with `next` and `error` callbacks. The listener can be cancelled + * by calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. + * + * @param firestore - The {@link Firestore} instance to enable persistence for. + * @param json - A JSON object generated by invoking {@link DocumentSnapshot.toJSON}. + * @param observer - A single object containing `next` and `error` callbacks. + * @param converter - An optional object that converts objects from Firestore before the onNext + * listener is invoked. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export function onSnapshot( + firestore: Firestore, + json: { bundle: string; bundleName: string; bundleSource: string }, + observer: { + next: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + }, + converter?: FirestoreDataConverter +): Unsubscribe; /** * Attaches a listener for `QuerySnapshot` events based on QuerySnapshot data generated by invoking * {@link QuerySnapshot.toJSON} You may either pass individual `onNext` and `onError` callbacks or * pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by * calling the function that is returned when `onSnapshot` is called. * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. * * @param firestore - The {@link Firestore} instance to enable persistence for. * @param json - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. @@ -802,6 +858,34 @@ export function onSnapshot( }, converter?: FirestoreDataConverter ): Unsubscribe; +/** + * Attaches a listener for `DocumentSnapshot` events based on QuerySnapshot data generated by + * invoking {@link DocumentSnapshot.toJSON} You may either pass individual `onNext` and `onError` + * callbacks or pass a single observer object with `next` and `error` callbacks. The listener can be + * cancelled by calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will never be called because the + * snapshot stream is never-ending. + * + * @param firestore - The {@link Firestore} instance to enable persistence for. + * @param json - A JSON object generated by invoking {@link DocumentSnapshot.toJSON}. + * @param options - Options controlling the listen behavior. + * @param observer - A single object containing `next` and `error` callbacks. + * @param converter - An optional object that converts objects from Firestore before the onNext + * listener is invoked. + * @returns An unsubscribe function that can be called to cancel the snapshot listener. + */ +export function onSnapshot( + firestore: Firestore, + json: { bundle: string; bundleName: string; bundleSource: string }, + options: SnapshotListenOptions, + observer: { + next: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + }, + converter?: FirestoreDataConverter +): Unsubscribe; export function onSnapshot( reference: | Query diff --git a/packages/firestore/src/api/snapshot.ts b/packages/firestore/src/api/snapshot.ts index 4f2f056d720..01f974c097e 100644 --- a/packages/firestore/src/api/snapshot.ts +++ b/packages/firestore/src/api/snapshot.ts @@ -503,7 +503,7 @@ export class DocumentSnapshot< return undefined; } - toJSON(): object { + toJSON(): { bundle: string; bundleSource: string; bundleName: string } { const document = this._document; // eslint-disable-next-line @typescript-eslint/no-explicit-any const result: any = {}; @@ -699,7 +699,7 @@ export class QuerySnapshot< return this._cachedChanges; } - toJSON(): object { + toJSON(): { bundle: string; bundleSource: string; bundleName: string } { // eslint-disable-next-line @typescript-eslint/no-explicit-any const result: any = {}; result['bundleSource'] = 'QuerySnapshot'; From 19990fc358700135fad3b0b8d492e0f77f15ed35 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 9 Apr 2025 09:15:10 -0400 Subject: [PATCH 08/29] itest for Snapshot created by a DocSnap bundle --- .../test/integration/api/database.test.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 8cbe99b3cd9..d2ec43e3470 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -86,6 +86,8 @@ import { DEFAULT_SETTINGS, DEFAULT_PROJECT_ID } from '../util/settings'; use(chaiAsPromised); +const SNAPSHOT_TEST_TIMEOUT = 5000; + apiDescribe('Database', persistence => { it('can set a document', () => { return withTestDoc(persistence, docRef => { @@ -1184,6 +1186,41 @@ apiDescribe('Database', persistence => { }); }); + it('DocumentSnapshot events for snapshot created by a bundle', function (done) { + this.timeout(SNAPSHOT_TEST_TIMEOUT); + withTestDoc(persistence, async (docRef, db) => { + const secondUpdateFound = new Deferred(); + let count = 0; + await setDoc(docRef, { a: 0 }); + await waitForPendingWrites(db); + const docSnap = await getDoc(docRef); + expect(docSnap.data()).to.deep.equal({ a: 0 }); + const unlisten = onSnapshot( + db, + docSnap.toJSON(), + (doc: DocumentSnapshot) => { + if (doc) { + count++; + if (count === 1) { + expect(doc.data()).to.deep.equal({ a: 1 }); + } else { + expect(doc.data()).to.deep.equal({ b: 1 }); + secondUpdateFound.resolve(); + } + } + } + ); + await setDoc(docRef, { a: 1 }).then(() => { + setDoc(docRef, { b: 1 }); + }); + await secondUpdateFound.promise; + console.error('DEDB done!'); + expect(count).to.equal(2); + unlisten(); + done(); + }); + }); + it('Listen can be called multiple times', () => { return withTestCollection(persistence, {}, coll => { const docA = doc(coll); From 099e53ea4aaac9c4a44f2b62de00fb7812fbe6c8 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 9 Apr 2025 12:23:08 -0400 Subject: [PATCH 09/29] Three DocumentSnap iTests --- .../test/integration/api/database.test.ts | 90 +++++++++++++++---- 1 file changed, 71 insertions(+), 19 deletions(-) diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index d2ec43e3470..1b91aaa85f5 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -1186,15 +1186,84 @@ apiDescribe('Database', persistence => { }); }); + it('Listen can be called multiple times', () => { + return withTestCollection(persistence, {}, coll => { + const docA = doc(coll); + const deferred1 = new Deferred(); + const deferred2 = new Deferred(); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + setDoc(docA, { foo: 'bar' }).then(() => { + onSnapshot(docA, () => { + deferred1.resolve(); + onSnapshot(docA, () => deferred2.resolve()); + }); + }); + return Promise.all([deferred1.promise, deferred2.promise]).then(() => {}); + }); + }); + it('DocumentSnapshot events for snapshot created by a bundle', function (done) { this.timeout(SNAPSHOT_TEST_TIMEOUT); - withTestDoc(persistence, async (docRef, db) => { + void withTestDoc(persistence, async (docRef, db) => { + const updateFound = new Deferred(); + await setDoc(docRef, { a: 0 }); + await waitForPendingWrites(db); + const docSnap = await getDoc(docRef); + expect(docSnap.data()).to.deep.equal({ a: 0 }); + const unlisten = onSnapshot( + db, + docSnap.toJSON(), + (doc: DocumentSnapshot) => { + if (doc) { + expect(doc.data()).to.deep.equal({ a: 0 }); + updateFound.resolve(); + } + } + ); + await updateFound.promise; + unlisten(); + done(); + }); + }); + it('DocumentSnapshot updated doc events in snapshot created by a bundle', function (done) { + this.timeout(SNAPSHOT_TEST_TIMEOUT); + void withTestDoc(persistence, async (docRef, db) => { const secondUpdateFound = new Deferred(); + await setDoc(docRef, { a: 0 }); + await waitForPendingWrites(db); + const docSnap = await getDoc(docRef); + expect(docSnap.data()).to.deep.equal({ a: 0 }); let count = 0; + const unlisten = onSnapshot( + db, + docSnap.toJSON(), + (doc: DocumentSnapshot) => { + if (doc) { + count++; + if (count === 1) { + expect(doc.data()).to.deep.equal({ a: 1 }); + secondUpdateFound.resolve(); + } + } + } + ); + await setDoc(docRef, { a: 1 }); + await secondUpdateFound.promise; + expect(count).to.equal(1); + unlisten(); + done(); + }); + }); + + it('DocumentSnapshot multiple events for snapshot created by a bundle', function (done) { + this.timeout(SNAPSHOT_TEST_TIMEOUT); + void withTestDoc(persistence, async (docRef, db) => { + const secondUpdateFound = new Deferred(); await setDoc(docRef, { a: 0 }); await waitForPendingWrites(db); const docSnap = await getDoc(docRef); expect(docSnap.data()).to.deep.equal({ a: 0 }); + let count = 0; const unlisten = onSnapshot( db, docSnap.toJSON(), @@ -1211,32 +1280,15 @@ apiDescribe('Database', persistence => { } ); await setDoc(docRef, { a: 1 }).then(() => { - setDoc(docRef, { b: 1 }); + void setDoc(docRef, { b: 1 }); }); await secondUpdateFound.promise; - console.error('DEDB done!'); expect(count).to.equal(2); unlisten(); done(); }); }); - it('Listen can be called multiple times', () => { - return withTestCollection(persistence, {}, coll => { - const docA = doc(coll); - const deferred1 = new Deferred(); - const deferred2 = new Deferred(); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - setDoc(docA, { foo: 'bar' }).then(() => { - onSnapshot(docA, () => { - deferred1.resolve(); - onSnapshot(docA, () => deferred2.resolve()); - }); - }); - return Promise.all([deferred1.promise, deferred2.promise]).then(() => {}); - }); - }); - it('Metadata only changes are not fired when no options provided', () => { return withTestDoc(persistence, docRef => { const secondUpdateFound = new Deferred(); From e33c4f9668372a6b690eb68a3220eb68b0229b75 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 9 Apr 2025 15:47:12 -0400 Subject: [PATCH 10/29] QuerySnapshot integration tests --- .../test/integration/api/database.test.ts | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 1b91aaa85f5..0478b06a9c4 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -1225,6 +1225,7 @@ apiDescribe('Database', persistence => { done(); }); }); + it('DocumentSnapshot updated doc events in snapshot created by a bundle', function (done) { this.timeout(SNAPSHOT_TEST_TIMEOUT); void withTestDoc(persistence, async (docRef, db) => { @@ -1289,6 +1290,136 @@ apiDescribe('Database', persistence => { }); }); + it('Querysnapshot events for snapshot created by a bundle', function (done) { + this.timeout(SNAPSHOT_TEST_TIMEOUT); + const testDocs = { + a: { foo: 1 }, + b: { bar: 2 } + }; + void withTestCollection(persistence, testDocs, async (coll, db) => { + const q = query(coll, orderBy(documentId())); + const querySnap = await getDocs(q); + const json = querySnap.toJSON(); + const gotInitialSnapshot = new Deferred(); + const unlisten = onSnapshot(db, json, (querySnap: QuerySnapshot) => { + const docs = querySnap.docs; + expect(docs).to.not.be.null; + if (docs) { + expect(querySnap.docs.length).equals(2); + expect(docs[0].data()).to.deep.equal({ foo: 1 }); + expect(docs[1].data()).to.deep.equal({ bar: 2 }); + } + gotInitialSnapshot.resolve(); + }); + await gotInitialSnapshot.promise; + unlisten(); + done(); + }); + }); + + it('QuerySnapshot updated doc events in snapshot created by a bundle', function (done) { + this.timeout(SNAPSHOT_TEST_TIMEOUT); + const testDocs = { + a: { foo: 1 }, + b: { bar: 2 } + }; + void withTestCollection(persistence, testDocs, async (coll, db) => { + const q = query(coll, orderBy(documentId())); + const querySnap = await getDocs(q); + const json = querySnap.toJSON(); + const updateFound = new Deferred(); + let count = 0; + const unlisten = onSnapshot(db, json, (querySnap: QuerySnapshot) => { + count++; + const docs = querySnap.docs; + expect(docs).to.not.be.null; + if (docs) { + expect(docs[0].data()).to.deep.equal({ foo: 0 }); + updateFound.resolve(); + } + }); + await setDoc(querySnap.docs[0].ref, { foo: 0 }); + await updateFound.promise; + expect(count).to.equal(1); + unlisten(); + done(); + }); + }); + + it('QuerySnapshot multiple events for snapshot created by a bundle', function (done) { + this.timeout(SNAPSHOT_TEST_TIMEOUT); + const testDocs = { + a: { foo: false }, + b: { bar: false } + }; + void withTestCollection(persistence, testDocs, async (coll, db) => { + const q = query(coll, orderBy(documentId())); + const querySnap = await getDocs(q); + const json = querySnap.toJSON(); + const updateFound = new Deferred(); + let count = 0; + const unlisten = onSnapshot(db, json, (querySnap: QuerySnapshot) => { + const docs = querySnap.docs; + expect(docs).to.not.be.null; + if (docs) { + if (count === 0) { + expect(docs[0].data()).to.deep.equal({ foo: true }); + expect(docs[1].data()).to.deep.equal({ bar: false }); + } else { + expect(docs[0].data()).to.deep.equal({ foo: true }); + expect(docs[1].data()).to.deep.equal({ bar: true }); + updateFound.resolve(); + } + count++; + } + }); + await setDoc(querySnap.docs[0].ref, { foo: true }); + await setDoc(querySnap.docs[1].ref, { bar: true }); + await updateFound.promise; + expect(count).to.equal(2); + unlisten(); + done(); + }); + }); + + ////////////////// + it('onSnapshotsInSync fires after listeners are in sync', () => { + const testDocs = { + a: { foo: 1 } + }; + return withTestCollection(persistence, testDocs, async (coll, db) => { + let events: string[] = []; + const gotInitialSnapshot = new Deferred(); + const docA = doc(coll, 'a'); + + onSnapshot(docA, snap => { + events.push('doc'); + gotInitialSnapshot.resolve(); + }); + await gotInitialSnapshot.promise; + events = []; + + const done = new Deferred(); + onSnapshotsInSync(db, () => { + events.push('snapshots-in-sync'); + if (events.length === 3) { + // We should have an initial snapshots-in-sync event, then a snapshot + // event for set(), then another event to indicate we're in sync + // again. + expect(events).to.deep.equal([ + 'snapshots-in-sync', + 'doc', + 'snapshots-in-sync' + ]); + done.resolve(); + } + }); + + await setDoc(docA, { foo: 3 }); + await done.promise; + }); + }); + it('Metadata only changes are not fired when no options provided', () => { return withTestDoc(persistence, docRef => { const secondUpdateFound = new Deferred(); From a5ffd29220741146347204c87278694242f3cf0d Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 9 Apr 2025 15:49:04 -0400 Subject: [PATCH 11/29] Remove extra test, added by mistake. --- .../test/integration/api/database.test.ts | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 0478b06a9c4..fa699b119eb 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -1382,44 +1382,6 @@ apiDescribe('Database', persistence => { }); }); - ////////////////// - it('onSnapshotsInSync fires after listeners are in sync', () => { - const testDocs = { - a: { foo: 1 } - }; - return withTestCollection(persistence, testDocs, async (coll, db) => { - let events: string[] = []; - const gotInitialSnapshot = new Deferred(); - const docA = doc(coll, 'a'); - - onSnapshot(docA, snap => { - events.push('doc'); - gotInitialSnapshot.resolve(); - }); - await gotInitialSnapshot.promise; - events = []; - - const done = new Deferred(); - onSnapshotsInSync(db, () => { - events.push('snapshots-in-sync'); - if (events.length === 3) { - // We should have an initial snapshots-in-sync event, then a snapshot - // event for set(), then another event to indicate we're in sync - // again. - expect(events).to.deep.equal([ - 'snapshots-in-sync', - 'doc', - 'snapshots-in-sync' - ]); - done.resolve(); - } - }); - - await setDoc(docA, { foo: 3 }); - await done.promise; - }); - }); - it('Metadata only changes are not fired when no options provided', () => { return withTestDoc(persistence, docRef => { const secondUpdateFound = new Deferred(); From 740b723c34435c31dc74a25621a0bf6924b21309 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 9 Apr 2025 16:51:34 -0400 Subject: [PATCH 12/29] Update database.test.ts --- packages/firestore/test/integration/api/database.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index fa699b119eb..0dc95317187 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -1365,7 +1365,7 @@ apiDescribe('Database', persistence => { if (count === 0) { expect(docs[0].data()).to.deep.equal({ foo: true }); expect(docs[1].data()).to.deep.equal({ bar: false }); - } else { + } else if(count === 1) { expect(docs[0].data()).to.deep.equal({ foo: true }); expect(docs[1].data()).to.deep.equal({ bar: true }); updateFound.resolve(); From c039ae2d6e52f576fd1e3da7ff22b6271b57d4f2 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 9 Apr 2025 21:08:11 -0400 Subject: [PATCH 13/29] Wait for writes in QuerySnapshot multiple events --- packages/firestore/test/integration/api/database.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 0dc95317187..be1a23e3030 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -1374,6 +1374,7 @@ apiDescribe('Database', persistence => { } }); await setDoc(querySnap.docs[0].ref, { foo: true }); + await waitForPendingWrites(db); await setDoc(querySnap.docs[1].ref, { bar: true }); await updateFound.promise; expect(count).to.equal(2); From 2f27429ba5f7bf91f435d4c845f1caae32b71bc2 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 9 Apr 2025 21:20:09 -0400 Subject: [PATCH 14/29] debug logging --- packages/firestore/test/integration/api/database.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index be1a23e3030..113957dc113 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -1362,6 +1362,7 @@ apiDescribe('Database', persistence => { const docs = querySnap.docs; expect(docs).to.not.be.null; if (docs) { + console.error("DEDB count: ", count, " bar: ", docs[1].data()) if (count === 0) { expect(docs[0].data()).to.deep.equal({ foo: true }); expect(docs[1].data()).to.deep.equal({ bar: false }); From 8fc6c33d75df7170bddc7cf852165287d96de4bd Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Thu, 10 Apr 2025 08:58:31 -0400 Subject: [PATCH 15/29] Timing protection QuerySnapshot tests. --- .../test/integration/api/database.test.ts | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 113957dc113..a0e8a42a840 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -1198,7 +1198,7 @@ apiDescribe('Database', persistence => { onSnapshot(docA, () => deferred2.resolve()); }); }); - return Promise.all([deferred1.promise, deferred2.promise]).then(() => {}); + return Promise.all([deferred1.promise, deferred2.promise]).then(() => { }); }); }); @@ -1349,35 +1349,35 @@ apiDescribe('Database', persistence => { it('QuerySnapshot multiple events for snapshot created by a bundle', function (done) { this.timeout(SNAPSHOT_TEST_TIMEOUT); const testDocs = { - a: { foo: false }, - b: { bar: false } + a: { foo: 0 }, }; void withTestCollection(persistence, testDocs, async (coll, db) => { const q = query(coll, orderBy(documentId())); const querySnap = await getDocs(q); const json = querySnap.toJSON(); - const updateFound = new Deferred(); + const firstUpdateFound = new Deferred(); + const secondUpdateFound = new Deferred(); + const docRef = querySnap.docs[0].ref; let count = 0; const unlisten = onSnapshot(db, json, (querySnap: QuerySnapshot) => { const docs = querySnap.docs; expect(docs).to.not.be.null; if (docs) { - console.error("DEDB count: ", count, " bar: ", docs[1].data()) - if (count === 0) { - expect(docs[0].data()).to.deep.equal({ foo: true }); - expect(docs[1].data()).to.deep.equal({ bar: false }); - } else if(count === 1) { - expect(docs[0].data()).to.deep.equal({ foo: true }); - expect(docs[1].data()).to.deep.equal({ bar: true }); - updateFound.resolve(); - } + expect(docs[0]).to.exist; count++; + if (docs[0] !== undefined && count === 1) { + expect(docs[0].data()).to.deep.equal({ foo: 1 }); + firstUpdateFound.resolve(); + } else if (docs[0] !== undefined && count === 2) { + expect(docs[0].data()).to.deep.equal({ foo: 2 }); + secondUpdateFound.resolve(); + } } }); - await setDoc(querySnap.docs[0].ref, { foo: true }); - await waitForPendingWrites(db); - await setDoc(querySnap.docs[1].ref, { bar: true }); - await updateFound.promise; + await setDoc(docRef, { foo: 1 }); + await firstUpdateFound.promise; + await setDoc(docRef, { foo: 2 }); + await secondUpdateFound.promise; expect(count).to.equal(2); unlisten(); done(); @@ -1421,7 +1421,7 @@ apiDescribe('Database', persistence => { const queryForRejection = collection(db, 'a/__badpath__/b'); onSnapshot( queryForRejection, - () => {}, + () => { }, (err: Error) => { expect(err.name).to.exist; expect(err.message).to.exist; @@ -1438,13 +1438,13 @@ apiDescribe('Database', persistence => { const queryForRejection = collection(db, 'a/__badpath__/b'); onSnapshot( queryForRejection, - () => {}, + () => { }, (err: Error) => { expect(err.name).to.exist; expect(err.message).to.exist; onSnapshot( queryForRejection, - () => {}, + () => { }, (err2: Error) => { expect(err2.name).to.exist; expect(err2.message).to.exist; @@ -1859,7 +1859,7 @@ apiDescribe('Database', persistence => { it('can query after firestore restart', async () => { return withTestDoc(persistence, async (docRef, firestore) => { const deferred: Deferred = new Deferred(); - const unsubscribe = onSnapshot(docRef, snapshot => {}, deferred.resolve); + const unsubscribe = onSnapshot(docRef, snapshot => { }, deferred.resolve); await firestore._restart(); @@ -1879,7 +1879,7 @@ apiDescribe('Database', persistence => { it('query listener throws error on termination', async () => { return withTestDoc(persistence, async (docRef, firestore) => { const deferred: Deferred = new Deferred(); - const unsubscribe = onSnapshot(docRef, snapshot => {}, deferred.resolve); + const unsubscribe = onSnapshot(docRef, snapshot => { }, deferred.resolve); await terminate(firestore); @@ -1926,7 +1926,7 @@ apiDescribe('Database', persistence => { readonly title: string, readonly author: string, readonly ref: DocumentReference | null = null - ) {} + ) { } byline(): string { return this.title + ', by ' + this.author; } @@ -2056,8 +2056,8 @@ apiDescribe('Database', persistence => { batch.set(ref, { title: 'olive' }, { merge: true }) ).to.throw( 'Function WriteBatch.set() called with invalid ' + - 'data (via `toFirestore()`). Unsupported field value: undefined ' + - '(found in field author in document posts/some-post)' + 'data (via `toFirestore()`). Unsupported field value: undefined ' + + '(found in field author in document posts/some-post)' ); }); }); From 4bc9bdd78705d46146cfeb4326d896dd8d935997 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Thu, 10 Apr 2025 09:20:23 -0400 Subject: [PATCH 16/29] format --- .../test/integration/api/database.test.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index a0e8a42a840..3c4dc6cd947 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -1198,7 +1198,7 @@ apiDescribe('Database', persistence => { onSnapshot(docA, () => deferred2.resolve()); }); }); - return Promise.all([deferred1.promise, deferred2.promise]).then(() => { }); + return Promise.all([deferred1.promise, deferred2.promise]).then(() => {}); }); }); @@ -1349,7 +1349,7 @@ apiDescribe('Database', persistence => { it('QuerySnapshot multiple events for snapshot created by a bundle', function (done) { this.timeout(SNAPSHOT_TEST_TIMEOUT); const testDocs = { - a: { foo: 0 }, + a: { foo: 0 } }; void withTestCollection(persistence, testDocs, async (coll, db) => { const q = query(coll, orderBy(documentId())); @@ -1421,7 +1421,7 @@ apiDescribe('Database', persistence => { const queryForRejection = collection(db, 'a/__badpath__/b'); onSnapshot( queryForRejection, - () => { }, + () => {}, (err: Error) => { expect(err.name).to.exist; expect(err.message).to.exist; @@ -1438,13 +1438,13 @@ apiDescribe('Database', persistence => { const queryForRejection = collection(db, 'a/__badpath__/b'); onSnapshot( queryForRejection, - () => { }, + () => {}, (err: Error) => { expect(err.name).to.exist; expect(err.message).to.exist; onSnapshot( queryForRejection, - () => { }, + () => {}, (err2: Error) => { expect(err2.name).to.exist; expect(err2.message).to.exist; @@ -1859,7 +1859,7 @@ apiDescribe('Database', persistence => { it('can query after firestore restart', async () => { return withTestDoc(persistence, async (docRef, firestore) => { const deferred: Deferred = new Deferred(); - const unsubscribe = onSnapshot(docRef, snapshot => { }, deferred.resolve); + const unsubscribe = onSnapshot(docRef, snapshot => {}, deferred.resolve); await firestore._restart(); @@ -1879,7 +1879,7 @@ apiDescribe('Database', persistence => { it('query listener throws error on termination', async () => { return withTestDoc(persistence, async (docRef, firestore) => { const deferred: Deferred = new Deferred(); - const unsubscribe = onSnapshot(docRef, snapshot => { }, deferred.resolve); + const unsubscribe = onSnapshot(docRef, snapshot => {}, deferred.resolve); await terminate(firestore); @@ -1926,7 +1926,7 @@ apiDescribe('Database', persistence => { readonly title: string, readonly author: string, readonly ref: DocumentReference | null = null - ) { } + ) {} byline(): string { return this.title + ', by ' + this.author; } @@ -2056,8 +2056,8 @@ apiDescribe('Database', persistence => { batch.set(ref, { title: 'olive' }, { merge: true }) ).to.throw( 'Function WriteBatch.set() called with invalid ' + - 'data (via `toFirestore()`). Unsupported field value: undefined ' + - '(found in field author in document posts/some-post)' + 'data (via `toFirestore()`). Unsupported field value: undefined ' + + '(found in field author in document posts/some-post)' ); }); }); From 8238ccec7a3d100e6ab92522f38e3393af737bf5 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Thu, 10 Apr 2025 09:39:59 -0400 Subject: [PATCH 17/29] docs --- docs-devsite/firestore_.documentsnapshot.md | 8 +- docs-devsite/firestore_.md | 284 ++++++++++++++++++++ docs-devsite/firestore_.querysnapshot.md | 8 +- 3 files changed, 296 insertions(+), 4 deletions(-) diff --git a/docs-devsite/firestore_.documentsnapshot.md b/docs-devsite/firestore_.documentsnapshot.md index 8c4825593dc..a21cbde871d 100644 --- a/docs-devsite/firestore_.documentsnapshot.md +++ b/docs-devsite/firestore_.documentsnapshot.md @@ -150,9 +150,13 @@ The data at the specified field location or undefined if no such field exists in Signature: ```typescript -toJSON(): object; +toJSON(): { + bundle: string; + bundleSource: string; + bundleName: string; + }; ``` Returns: -object +{ bundle: string; bundleSource: string; bundleName: string; } diff --git a/docs-devsite/firestore_.md b/docs-devsite/firestore_.md index 91d21e32708..19505ce9547 100644 --- a/docs-devsite/firestore_.md +++ b/docs-devsite/firestore_.md @@ -32,6 +32,14 @@ https://github.com/firebase/firebase-js-sdk | [getPersistentCacheIndexManager(firestore)](./firestore_.md#getpersistentcacheindexmanager_231a8e0) | Returns the PersistentCache Index Manager used by the given Firestore object. The PersistentCacheIndexManager instance, or null if local persistent storage is not in use. | | [loadBundle(firestore, bundleData)](./firestore_.md#loadbundle_bec5b75) | Loads a Firestore bundle into the local cache. | | [namedQuery(firestore, name)](./firestore_.md#namedquery_6438876) | Reads a Firestore [Query](./firestore_.query.md#query_class) from local cache, identified by the given name.The named queries are packaged into bundles on the server side (along with resulting documents), and loaded to local cache using loadBundle. Once in local cache, use this method to extract a [Query](./firestore_.query.md#query_class) by name. | +| [onSnapshot(firestore, json, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_ce95e22) | Attaches a listener for DocumentSnapshot events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, json, options, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_0b786a3) | Attaches a listener for QuerySnapshot events based on data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, json, options, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_1f316d3) | Attaches a listener for DocumentSnapshot events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, json, observer, converter)](./firestore_.md#onsnapshot_9b07eb9) | Attaches a listener for QuerySnapshot events based on QuerySnapshot data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, json, observer, converter)](./firestore_.md#onsnapshot_a4eb012) | Attaches a listener for DocumentSnapshot events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, json, options, observer, converter)](./firestore_.md#onsnapshot_11d66d6) | Attaches a listener for QuerySnapshot events based on QuerySnapshot data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, json, options, observer, converter)](./firestore_.md#onsnapshot_7fccf8d) | Attaches a listener for DocumentSnapshot events based on QuerySnapshot data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, json, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_3e4fc0f) | Attaches a listener for QuerySnapshot events based on data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | | [onSnapshotsInSync(firestore, observer)](./firestore_.md#onsnapshotsinsync_2f0dfa4) | Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync event indicates that all listeners affected by a given change have fired, even if a single server-generated change affects multiple listeners.NOTE: The snapshots-in-sync event only indicates that listeners are in sync with each other, but does not relate to whether those snapshots are in sync with the server. Use SnapshotMetadata in the individual listeners to determine if a snapshot is from the cache or the server. | | [onSnapshotsInSync(firestore, onSync)](./firestore_.md#onsnapshotsinsync_1901c06) | Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync event indicates that all listeners affected by a given change have fired, even if a single server-generated change affects multiple listeners.NOTE: The snapshots-in-sync event only indicates that listeners are in sync with each other, but does not relate to whether those snapshots are in sync with the server. Use SnapshotMetadata in the individual listeners to determine if a snapshot is from the cache or the server. | | [runTransaction(firestore, updateFunction, options)](./firestore_.md#runtransaction_6f03ec4) | Executes the given updateFunction and then attempts to commit the changes applied within the transaction. If any document read within the transaction has changed, Cloud Firestore retries the updateFunction. If it fails to commit after 5 attempts, the transaction fails.The maximum number of writes allowed in a single transaction is 500. | @@ -617,6 +625,282 @@ Promise<[Query](./firestore_.query.md#query_class) \| null> A `Promise` that is resolved with the Query or `null`. +### onSnapshot(firestore, json, onNext, onError, onCompletion, converter) {:#onsnapshot_ce95e22} + +Attaches a listener for `DocumentSnapshot` events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual `onNext` and `onError` callbacks or pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by calling the function that is returned when `onSnapshot` is called. + +NOTE: Although an `onCompletion` callback can be provided, it will never be called because the snapshot stream is never-ending. + +Signature: + +```typescript +export declare function onSnapshot(firestore: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firestore | [Firestore](./firestore_.firestore.md#firestore_class) | The [Firestore](./firestore_.firestore.md#firestore_class) instance to enable persistence for. | +| json | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). | +| onNext | (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class)<AppModelType, DbModelType>) => void | A callback to be called every time a new DocumentSnapshot is available. | +| onError | (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void | A callback to be called if the listen fails or is cancelled. No fruther callbacks will occur. | +| onCompletion | () => void | Can be provided, but will not be called since streams are never ending. | +| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | + +Returns: + +[Unsubscribe](./firestore_.unsubscribe.md#unsubscribe_interface) + +An unsubscribe function that can be called to cancel the snapshot listener. + +### onSnapshot(firestore, json, options, onNext, onError, onCompletion, converter) {:#onsnapshot_0b786a3} + +Attaches a listener for `QuerySnapshot` events based on data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual `onNext` and `onError` callbacks or pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by calling the function that is returned when `onSnapshot` is called. + +NOTE: Although an `onCompletion` callback can be provided, it will never be called because the snapshot stream is never-ending. + +Signature: + +```typescript +export declare function onSnapshot(firestore: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firestore | [Firestore](./firestore_.firestore.md#firestore_class) | The [Firestore](./firestore_.firestore.md#firestore_class) instance to enable persistence for. | +| json | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). | +| options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md#snapshotlistenoptions_interface) | Options controlling the listen behavior. | +| onNext | (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class)<AppModelType, DbModelType>) => void | A callback to be called every time a new QuerySnapshot is available. | +| onError | (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void | A callback to be called if the listen fails or is cancelled. No further callbacks will occur. | +| onCompletion | () => void | Can be provided, but will not be called since streams are never ending. | +| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | + +Returns: + +[Unsubscribe](./firestore_.unsubscribe.md#unsubscribe_interface) + +An unsubscribe function that can be called to cancel the snapshot listener. + +### onSnapshot(firestore, json, options, onNext, onError, onCompletion, converter) {:#onsnapshot_1f316d3} + +Attaches a listener for `DocumentSnapshot` events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual `onNext` and `onError` callbacks or pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by calling the function that is returned when `onSnapshot` is called. + +NOTE: Although an `onCompletion` callback can be provided, it will never be called because the snapshot stream is never-ending. + +Signature: + +```typescript +export declare function onSnapshot(firestore: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firestore | [Firestore](./firestore_.firestore.md#firestore_class) | The [Firestore](./firestore_.firestore.md#firestore_class) instance to enable persistence for. | +| json | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). | +| options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md#snapshotlistenoptions_interface) | Options controlling the listen behavior. | +| onNext | (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class)<AppModelType, DbModelType>) => void | A callback to be called every time a new DocumentSnapshot is available. | +| onError | (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void | A callback to be called if the listen fails or is cancelled. No further callbacks will occur. | +| onCompletion | () => void | Can be provided, but will not be called since streams are never ending. | +| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | + +Returns: + +[Unsubscribe](./firestore_.unsubscribe.md#unsubscribe_interface) + +An unsubscribe function that can be called to cancel the snapshot listener. + +### onSnapshot(firestore, json, observer, converter) {:#onsnapshot_9b07eb9} + +Attaches a listener for `QuerySnapshot` events based on QuerySnapshot data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual `onNext` and `onError` callbacks or pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by calling the function that is returned when `onSnapshot` is called. + +NOTE: Although an `onCompletion` callback can be provided, it will never be called because the snapshot stream is never-ending. + +Signature: + +```typescript +export declare function onSnapshot(firestore: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, observer: { + next: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}, converter?: FirestoreDataConverter): Unsubscribe; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firestore | [Firestore](./firestore_.firestore.md#firestore_class) | The [Firestore](./firestore_.firestore.md#firestore_class) instance to enable persistence for. | +| json | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). | +| observer | { next: (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class)<AppModelType, DbModelType>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void; complete?: () => void; } | A single object containing next and error callbacks. | +| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | + +Returns: + +[Unsubscribe](./firestore_.unsubscribe.md#unsubscribe_interface) + +An unsubscribe function that can be called to cancel the snapshot listener. + +### onSnapshot(firestore, json, observer, converter) {:#onsnapshot_a4eb012} + +Attaches a listener for `DocumentSnapshot` events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual `onNext` and `onError` callbacks or pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by calling the function that is returned when `onSnapshot` is called. + +NOTE: Although an `onCompletion` callback can be provided, it will never be called because the snapshot stream is never-ending. + +Signature: + +```typescript +export declare function onSnapshot(firestore: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, observer: { + next: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}, converter?: FirestoreDataConverter): Unsubscribe; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firestore | [Firestore](./firestore_.firestore.md#firestore_class) | The [Firestore](./firestore_.firestore.md#firestore_class) instance to enable persistence for. | +| json | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). | +| observer | { next: (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class)<AppModelType, DbModelType>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void; complete?: () => void; } | A single object containing next and error callbacks. | +| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | + +Returns: + +[Unsubscribe](./firestore_.unsubscribe.md#unsubscribe_interface) + +An unsubscribe function that can be called to cancel the snapshot listener. + +### onSnapshot(firestore, json, options, observer, converter) {:#onsnapshot_11d66d6} + +Attaches a listener for `QuerySnapshot` events based on QuerySnapshot data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual `onNext` and `onError` callbacks or pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by calling the function that is returned when `onSnapshot` is called. + +NOTE: Although an `onCompletion` callback can be provided, it will never be called because the snapshot stream is never-ending. + +Signature: + +```typescript +export declare function onSnapshot(firestore: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, options: SnapshotListenOptions, observer: { + next: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}, converter?: FirestoreDataConverter): Unsubscribe; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firestore | [Firestore](./firestore_.firestore.md#firestore_class) | The [Firestore](./firestore_.firestore.md#firestore_class) instance to enable persistence for. | +| json | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). | +| options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md#snapshotlistenoptions_interface) | Options controlling the listen behavior. | +| observer | { next: (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class)<AppModelType, DbModelType>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void; complete?: () => void; } | A single object containing next and error callbacks. | +| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | + +Returns: + +[Unsubscribe](./firestore_.unsubscribe.md#unsubscribe_interface) + +An unsubscribe function that can be called to cancel the snapshot listener. + +### onSnapshot(firestore, json, options, observer, converter) {:#onsnapshot_7fccf8d} + +Attaches a listener for `DocumentSnapshot` events based on QuerySnapshot data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual `onNext` and `onError` callbacks or pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by calling the function that is returned when `onSnapshot` is called. + +NOTE: Although an `onCompletion` callback can be provided, it will never be called because the snapshot stream is never-ending. + +Signature: + +```typescript +export declare function onSnapshot(firestore: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, options: SnapshotListenOptions, observer: { + next: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}, converter?: FirestoreDataConverter): Unsubscribe; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firestore | [Firestore](./firestore_.firestore.md#firestore_class) | The [Firestore](./firestore_.firestore.md#firestore_class) instance to enable persistence for. | +| json | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). | +| options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md#snapshotlistenoptions_interface) | Options controlling the listen behavior. | +| observer | { next: (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class)<AppModelType, DbModelType>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void; complete?: () => void; } | A single object containing next and error callbacks. | +| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | + +Returns: + +[Unsubscribe](./firestore_.unsubscribe.md#unsubscribe_interface) + +An unsubscribe function that can be called to cancel the snapshot listener. + +### onSnapshot(firestore, json, onNext, onError, onCompletion, converter) {:#onsnapshot_3e4fc0f} + +Attaches a listener for `QuerySnapshot` events based on data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual `onNext` and `onError` callbacks or pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by calling the function that is returned when `onSnapshot` is called. + +NOTE: Although an `onCompletion` callback can be provided, it will never be called because the snapshot stream is never-ending. + +Signature: + +```typescript +export declare function onSnapshot(firestore: Firestore, json: { + bundle: string; + bundleName: string; + bundleSource: string; +}, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firestore | [Firestore](./firestore_.firestore.md#firestore_class) | The [Firestore](./firestore_.firestore.md#firestore_class) instance to enable persistence for. | +| json | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). | +| onNext | (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class)<AppModelType, DbModelType>) => void | A callback to be called every time a new QuerySnapshot is available. | +| onError | (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void | A callback to be called if the listen fails or is cancelled. No further callbacks will occur. | +| onCompletion | () => void | Can be provided, but will not be called since streams are never ending. | +| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | + +Returns: + +[Unsubscribe](./firestore_.unsubscribe.md#unsubscribe_interface) + +An unsubscribe function that can be called to cancel the snapshot listener. + ### onSnapshotsInSync(firestore, observer) {:#onsnapshotsinsync_2f0dfa4} Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync event indicates that all listeners affected by a given change have fired, even if a single server-generated change affects multiple listeners. diff --git a/docs-devsite/firestore_.querysnapshot.md b/docs-devsite/firestore_.querysnapshot.md index da0913d7b6e..f2693378d7f 100644 --- a/docs-devsite/firestore_.querysnapshot.md +++ b/docs-devsite/firestore_.querysnapshot.md @@ -132,9 +132,13 @@ void Signature: ```typescript -toJSON(): object; +toJSON(): { + bundle: string; + bundleSource: string; + bundleName: string; + }; ``` Returns: -object +{ bundle: string; bundleSource: string; bundleName: string; } From 697b6d4b8e78f8046002457206c2269f2d90cecd Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Thu, 10 Apr 2025 17:36:21 -0400 Subject: [PATCH 18/29] Use EventsAccumulator --- packages/firestore/src/api/reference_impl.ts | 20 +- .../test/integration/api/database.test.ts | 291 ++++++++---------- 2 files changed, 131 insertions(+), 180 deletions(-) diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index 68c707bc085..c55b5e8babe 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -1182,13 +1182,14 @@ function onSnapshotDocumentSnapshotBundle< ): Unsubscribe { let unsubscribed: boolean = false; let internalUnsubscribe: Unsubscribe | undefined; - const bundle = json.bundle; - const loadTask = loadBundle(db, bundle); + const loadTask = loadBundle(db, json.bundle); loadTask .then(() => { - const key = DocumentKey.fromPath(json.bundleName); - const docConverter = converter ? converter : null; - const docReference = new DocumentReference(db, docConverter, key); + const docReference = new DocumentReference( + db, + converter ? converter : null, + DocumentKey.fromPath(json.bundleName) + ); if (options !== undefined) { internalUnsubscribe = onSnapshot( docReference as DocumentReference, @@ -1205,9 +1206,8 @@ function onSnapshotDocumentSnapshotBundle< .catch(e => { if (observer.error) { observer.error(e); - } else { - throw e; } + return () => {}; }); return () => { if (unsubscribed) { @@ -1238,8 +1238,7 @@ function onSnapshotQuerySnapshotBundle< ): Unsubscribe { let unsubscribed: boolean = false; let internalUnsubscribe: Unsubscribe | undefined; - const bundle = json.bundle; - const loadTask = loadBundle(db, bundle); + const loadTask = loadBundle(db, json.bundle); loadTask .then(() => namedQuery(db, json.bundleName)) .then(query => { @@ -1265,9 +1264,8 @@ function onSnapshotQuerySnapshotBundle< .catch(e => { if (observer.error) { observer.error(e); - } else { - throw e; } + return () => {}; }); return () => { if (unsubscribed) { diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 3c4dc6cd947..83933ad604e 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -1198,190 +1198,143 @@ apiDescribe('Database', persistence => { onSnapshot(docA, () => deferred2.resolve()); }); }); - return Promise.all([deferred1.promise, deferred2.promise]).then(() => {}); + return Promise.all([deferred1.promise, deferred2.promise]).then(() => { }); }); }); - it('DocumentSnapshot events for snapshot created by a bundle', function (done) { - this.timeout(SNAPSHOT_TEST_TIMEOUT); - void withTestDoc(persistence, async (docRef, db) => { - const updateFound = new Deferred(); - await setDoc(docRef, { a: 0 }); - await waitForPendingWrites(db); - const docSnap = await getDoc(docRef); - expect(docSnap.data()).to.deep.equal({ a: 0 }); - const unlisten = onSnapshot( - db, - docSnap.toJSON(), - (doc: DocumentSnapshot) => { - if (doc) { - expect(doc.data()).to.deep.equal({ a: 0 }); - updateFound.resolve(); - } - } - ); - await updateFound.promise; - unlisten(); - done(); - }); - }); - - it('DocumentSnapshot updated doc events in snapshot created by a bundle', function (done) { - this.timeout(SNAPSHOT_TEST_TIMEOUT); - void withTestDoc(persistence, async (docRef, db) => { - const secondUpdateFound = new Deferred(); - await setDoc(docRef, { a: 0 }); - await waitForPendingWrites(db); - const docSnap = await getDoc(docRef); - expect(docSnap.data()).to.deep.equal({ a: 0 }); - let count = 0; - const unlisten = onSnapshot( - db, - docSnap.toJSON(), - (doc: DocumentSnapshot) => { - if (doc) { - count++; - if (count === 1) { - expect(doc.data()).to.deep.equal({ a: 1 }); - secondUpdateFound.resolve(); - } - } - } - ); - await setDoc(docRef, { a: 1 }); - await secondUpdateFound.promise; - expect(count).to.equal(1); - unlisten(); - done(); - }); + it('DocumentSnapshot events for snapshot created by a bundle', async () => { + const initialData = { a: 0 }; + const finalData = { a: 1 }; + await withTestDocAndInitialData( + persistence, + initialData, + async (docRef, db) => { + const doc = await getDoc(docRef); + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshot( + db, + doc.toJSON(), + accumulator.storeEvent + ); + await accumulator + .awaitEvent() + .then(snap => { + expect(snap.exists()).to.be.true; + expect(snap.data()).to.deep.equal(initialData); + }) + .then(() => setDoc(docRef, finalData)) + .then(() => accumulator.awaitEvent()) + .then(snap => { + expect(snap.exists()).to.be.true; + expect(snap.data()).to.deep.equal(finalData); + }); + unsubscribe(); + } + ); }); - it('DocumentSnapshot multiple events for snapshot created by a bundle', function (done) { - this.timeout(SNAPSHOT_TEST_TIMEOUT); - void withTestDoc(persistence, async (docRef, db) => { - const secondUpdateFound = new Deferred(); - await setDoc(docRef, { a: 0 }); - await waitForPendingWrites(db); - const docSnap = await getDoc(docRef); - expect(docSnap.data()).to.deep.equal({ a: 0 }); - let count = 0; - const unlisten = onSnapshot( - db, - docSnap.toJSON(), - (doc: DocumentSnapshot) => { - if (doc) { - count++; - if (count === 1) { - expect(doc.data()).to.deep.equal({ a: 1 }); - } else { - expect(doc.data()).to.deep.equal({ b: 1 }); - secondUpdateFound.resolve(); - } - } - } - ); - await setDoc(docRef, { a: 1 }).then(() => { - void setDoc(docRef, { b: 1 }); - }); - await secondUpdateFound.promise; - expect(count).to.equal(2); - unlisten(); - done(); - }); + it('DocumentSnapshot updated doc events in snapshot created by a bundle', async () => { + const initialData = { a: 0 }; + const finalData = { a: 1 }; + await withTestDocAndInitialData( + persistence, + initialData, + async (docRef, db) => { + const doc = await getDoc(docRef); + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshot( + db, + doc.toJSON(), + accumulator.storeEvent + ); + await accumulator + .awaitEvent() + .then(snap => { + expect(snap.exists()).to.be.true; + expect(snap.data()).to.deep.equal(initialData); + }) + .then(() => setDoc(docRef, finalData)) + .then(() => accumulator.awaitEvent()) + .then(snap => { + expect(snap.exists()).to.be.true; + expect(snap.data()).to.deep.equal(finalData); + }); + unsubscribe(); + } + ); }); - it('Querysnapshot events for snapshot created by a bundle', function (done) { - this.timeout(SNAPSHOT_TEST_TIMEOUT); + it('Querysnapshot events for snapshot created by a bundle', async () => { const testDocs = { a: { foo: 1 }, b: { bar: 2 } }; - void withTestCollection(persistence, testDocs, async (coll, db) => { + await withTestCollection(persistence, testDocs, async (coll, db) => { const q = query(coll, orderBy(documentId())); const querySnap = await getDocs(q); const json = querySnap.toJSON(); - const gotInitialSnapshot = new Deferred(); - const unlisten = onSnapshot(db, json, (querySnap: QuerySnapshot) => { - const docs = querySnap.docs; - expect(docs).to.not.be.null; - if (docs) { - expect(querySnap.docs.length).equals(2); - expect(docs[0].data()).to.deep.equal({ foo: 1 }); - expect(docs[1].data()).to.deep.equal({ bar: 2 }); - } - gotInitialSnapshot.resolve(); - }); - await gotInitialSnapshot.promise; - unlisten(); - done(); - }); + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshot( + db, + json, + accumulator.storeEvent + ); + await accumulator + .awaitEvent() + .then(snap => { + const docs = snap.docs; + expect(docs).not.to.be.null; + console.error("DEDB lenght: ", docs.length); + console.error("Doc0 data: ", docs[0].data()); + console.error("Doc1 data: ", docs[1].data()); + expect(docs.length).to.equal(2); + expect(docs[0].data()).to.deep.equal(testDocs.a); + expect(docs[1].data()).to.deep.equal(testDocs.b); + }) + unsubscribe(); + } + ); }); - it('QuerySnapshot updated doc events in snapshot created by a bundle', function (done) { - this.timeout(SNAPSHOT_TEST_TIMEOUT); + it('QuerySnapshot updated doc events in snapshot created by a bundle', async () => { const testDocs = { a: { foo: 1 }, b: { bar: 2 } }; - void withTestCollection(persistence, testDocs, async (coll, db) => { + await withTestCollection(persistence, testDocs, async (coll, db) => { const q = query(coll, orderBy(documentId())); const querySnap = await getDocs(q); + const refForDocA = querySnap.docs[0].ref; const json = querySnap.toJSON(); - const updateFound = new Deferred(); - let count = 0; - const unlisten = onSnapshot(db, json, (querySnap: QuerySnapshot) => { - count++; - const docs = querySnap.docs; - expect(docs).to.not.be.null; - if (docs) { - expect(docs[0].data()).to.deep.equal({ foo: 0 }); - updateFound.resolve(); - } - }); - await setDoc(querySnap.docs[0].ref, { foo: 0 }); - await updateFound.promise; - expect(count).to.equal(1); - unlisten(); - done(); - }); - }); - - it('QuerySnapshot multiple events for snapshot created by a bundle', function (done) { - this.timeout(SNAPSHOT_TEST_TIMEOUT); - const testDocs = { - a: { foo: 0 } - }; - void withTestCollection(persistence, testDocs, async (coll, db) => { - const q = query(coll, orderBy(documentId())); - const querySnap = await getDocs(q); - const json = querySnap.toJSON(); - const firstUpdateFound = new Deferred(); - const secondUpdateFound = new Deferred(); - const docRef = querySnap.docs[0].ref; - let count = 0; - const unlisten = onSnapshot(db, json, (querySnap: QuerySnapshot) => { - const docs = querySnap.docs; - expect(docs).to.not.be.null; - if (docs) { - expect(docs[0]).to.exist; - count++; - if (docs[0] !== undefined && count === 1) { - expect(docs[0].data()).to.deep.equal({ foo: 1 }); - firstUpdateFound.resolve(); - } else if (docs[0] !== undefined && count === 2) { - expect(docs[0].data()).to.deep.equal({ foo: 2 }); - secondUpdateFound.resolve(); - } - } - }); - await setDoc(docRef, { foo: 1 }); - await firstUpdateFound.promise; - await setDoc(docRef, { foo: 2 }); - await secondUpdateFound.promise; - expect(count).to.equal(2); - unlisten(); - done(); - }); + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshot( + db, + json, + accumulator.storeEvent + ); + await accumulator + .awaitEvent() + .then(snap => { + const docs = snap.docs; + console.error("DEDB: docs: ", docs); + expect(docs).not.to.be.null; + expect(docs.length).to.equal(2); + expect(docs[0].data()).to.deep.equal(testDocs.a); + expect(docs[1].data()).to.deep.equal(testDocs.b); + }) + .then(() => setDoc(refForDocA, {foo: 0})) + .then(() => accumulator.awaitEvent()) + .then(snap => { + const docs = snap.docs; + console.error("DEDB: docs: ", docs); + expect(docs).not.to.be.null; + expect(docs.length).to.equal(2); + expect(docs[0].data()).to.deep.equal({foo: 0}); + expect(docs[1].data()).to.deep.equal(testDocs.b); + }); + unsubscribe(); + } + ); }); it('Metadata only changes are not fired when no options provided', () => { @@ -1421,7 +1374,7 @@ apiDescribe('Database', persistence => { const queryForRejection = collection(db, 'a/__badpath__/b'); onSnapshot( queryForRejection, - () => {}, + () => { }, (err: Error) => { expect(err.name).to.exist; expect(err.message).to.exist; @@ -1438,13 +1391,13 @@ apiDescribe('Database', persistence => { const queryForRejection = collection(db, 'a/__badpath__/b'); onSnapshot( queryForRejection, - () => {}, + () => { }, (err: Error) => { expect(err.name).to.exist; expect(err.message).to.exist; onSnapshot( queryForRejection, - () => {}, + () => { }, (err2: Error) => { expect(err2.name).to.exist; expect(err2.message).to.exist; @@ -1859,7 +1812,7 @@ apiDescribe('Database', persistence => { it('can query after firestore restart', async () => { return withTestDoc(persistence, async (docRef, firestore) => { const deferred: Deferred = new Deferred(); - const unsubscribe = onSnapshot(docRef, snapshot => {}, deferred.resolve); + const unsubscribe = onSnapshot(docRef, snapshot => { }, deferred.resolve); await firestore._restart(); @@ -1879,7 +1832,7 @@ apiDescribe('Database', persistence => { it('query listener throws error on termination', async () => { return withTestDoc(persistence, async (docRef, firestore) => { const deferred: Deferred = new Deferred(); - const unsubscribe = onSnapshot(docRef, snapshot => {}, deferred.resolve); + const unsubscribe = onSnapshot(docRef, snapshot => { }, deferred.resolve); await terminate(firestore); @@ -1926,7 +1879,7 @@ apiDescribe('Database', persistence => { readonly title: string, readonly author: string, readonly ref: DocumentReference | null = null - ) {} + ) { } byline(): string { return this.title + ', by ' + this.author; } @@ -2056,8 +2009,8 @@ apiDescribe('Database', persistence => { batch.set(ref, { title: 'olive' }, { merge: true }) ).to.throw( 'Function WriteBatch.set() called with invalid ' + - 'data (via `toFirestore()`). Unsupported field value: undefined ' + - '(found in field author in document posts/some-post)' + 'data (via `toFirestore()`). Unsupported field value: undefined ' + + '(found in field author in document posts/some-post)' ); }); }); From df3cb20bb891d4ea618d38cbf20247146ae42a6f Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Fri, 11 Apr 2025 10:15:28 -0400 Subject: [PATCH 19/29] onSnapshot Observer and Error testing --- .../test/integration/api/database.test.ts | 238 ++++++++++++++---- 1 file changed, 190 insertions(+), 48 deletions(-) diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 83933ad604e..d587b1f386d 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -86,8 +86,6 @@ import { DEFAULT_SETTINGS, DEFAULT_PROJECT_ID } from '../util/settings'; use(chaiAsPromised); -const SNAPSHOT_TEST_TIMEOUT = 5000; - apiDescribe('Database', persistence => { it('can set a document', () => { return withTestDoc(persistence, docRef => { @@ -1198,7 +1196,7 @@ apiDescribe('Database', persistence => { onSnapshot(docA, () => deferred2.resolve()); }); }); - return Promise.all([deferred1.promise, deferred2.promise]).then(() => { }); + return Promise.all([deferred1.promise, deferred2.promise]).then(() => {}); }); }); @@ -1264,36 +1262,187 @@ apiDescribe('Database', persistence => { ); }); + it('DocumentSnapshot observer events for snapshot created by a bundle', async () => { + const initialData = { a: 0 }; + const finalData = { a: 1 }; + await withTestDocAndInitialData( + persistence, + initialData, + async (docRef, db) => { + const doc = await getDoc(docRef); + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshot(db, doc.toJSON(), { + next: accumulator.storeEvent + }); + await accumulator + .awaitEvent() + .then(snap => { + expect(snap.exists()).to.be.true; + expect(snap.data()).to.deep.equal(initialData); + }) + .then(() => setDoc(docRef, finalData)) + .then(() => accumulator.awaitEvent()) + .then(snap => { + expect(snap.exists()).to.be.true; + expect(snap.data()).to.deep.equal(finalData); + }); + unsubscribe(); + } + ); + }); + + it('DocumentSnapshot error events for snapshot created by a bundle', async () => { + const initialData = { a: 0 }; + await withTestDocAndInitialData( + persistence, + initialData, + async (docRef, db) => { + const doc = await getDoc(docRef); + const json = doc.toJSON(); + json.bundle = 'BadData'; + const deferred = new Deferred(); + const unsubscribe = onSnapshot( + db, + json, + ds => { + expect(ds).to.not.exist; + deferred.resolve(); + }, + err => { + expect(err.name).to.exist; + expect(err.message).to.exist; + deferred.resolve(); + } + ); + await deferred.promise; + unsubscribe(); + } + ); + }); + + it('DocumentSnapshot observer error events for snapshot created by a bundle', async () => { + const initialData = { a: 0 }; + await withTestDocAndInitialData( + persistence, + initialData, + async (docRef, db) => { + const doc = await getDoc(docRef); + const json = doc.toJSON(); + json.bundle = 'BadData'; + const deferred = new Deferred(); + const unsubscribe = onSnapshot(db, json, { + next: ds => { + expect(ds).to.not.exist; + deferred.resolve(); + }, + error: err => { + expect(err.name).to.exist; + expect(err.message).to.exist; + deferred.resolve(); + } + }); + await deferred.promise; + unsubscribe(); + } + ); + }); + it('Querysnapshot events for snapshot created by a bundle', async () => { const testDocs = { a: { foo: 1 }, b: { bar: 2 } }; await withTestCollection(persistence, testDocs, async (coll, db) => { - const q = query(coll, orderBy(documentId())); - const querySnap = await getDocs(q); - const json = querySnap.toJSON(); + const querySnap = await getDocs(query(coll, orderBy(documentId()))); const accumulator = new EventsAccumulator(); const unsubscribe = onSnapshot( db, - json, + querySnap.toJSON(), accumulator.storeEvent ); - await accumulator - .awaitEvent() - .then(snap => { - const docs = snap.docs; - expect(docs).not.to.be.null; - console.error("DEDB lenght: ", docs.length); - console.error("Doc0 data: ", docs[0].data()); - console.error("Doc1 data: ", docs[1].data()); - expect(docs.length).to.equal(2); - expect(docs[0].data()).to.deep.equal(testDocs.a); - expect(docs[1].data()).to.deep.equal(testDocs.b); - }) + await accumulator.awaitEvent().then(snap => { + expect(snap.docs).not.to.be.null; + expect(snap.docs.length).to.equal(2); + expect(snap.docs[0].data()).to.deep.equal(testDocs.a); + expect(snap.docs[1].data()).to.deep.equal(testDocs.b); + }); unsubscribe(); - } - ); + }); + }); + + it('Querysnapshot observer events for snapshot created by a bundle', async () => { + const testDocs = { + a: { foo: 1 }, + b: { bar: 2 } + }; + await withTestCollection(persistence, testDocs, async (coll, db) => { + const querySnap = await getDocs(query(coll, orderBy(documentId()))); + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshot(db, querySnap.toJSON(), { + next: accumulator.storeEvent + }); + await accumulator.awaitEvent().then(snap => { + expect(snap.docs).not.to.be.null; + expect(snap.docs.length).to.equal(2); + expect(snap.docs[0].data()).to.deep.equal(testDocs.a); + expect(snap.docs[1].data()).to.deep.equal(testDocs.b); + }); + unsubscribe(); + }); + }); + + it('QuerySnapshot error events for snapshot created by a bundle', async () => { + const testDocs = { + a: { foo: 1 }, + b: { bar: 2 } + }; + await withTestCollection(persistence, testDocs, async (coll, db) => { + const querySnap = await getDocs(query(coll, orderBy(documentId()))); + const deferred = new Deferred(); + const json = querySnap.toJSON(); + json.bundle = 'BadData'; + const unsubscribe = onSnapshot( + db, + json, + qs => { + expect(qs).to.not.exist; + deferred.resolve(); + }, + err => { + expect(err.name).to.exist; + expect(err.message).to.exist; + deferred.resolve(); + } + ); + await deferred.promise; + unsubscribe(); + }); + }); + + it('QuerySnapshot observer error events for snapshot created by a bundle', async () => { + const testDocs = { + a: { foo: 1 }, + b: { bar: 2 } + }; + await withTestCollection(persistence, testDocs, async (coll, db) => { + const querySnap = await getDocs(query(coll, orderBy(documentId()))); + const deferred = new Deferred(); + const json = querySnap.toJSON(); + json.bundle = 'BadData'; + const unsubscribe = onSnapshot(db, json, { + next: qs => { + expect(qs).to.not.exist; + deferred.resolve(); + }, + error: err => { + expect(err.name).to.exist; + expect(err.message).to.exist; + deferred.resolve(); + } + }); + await deferred.promise; + unsubscribe(); + }); }); it('QuerySnapshot updated doc events in snapshot created by a bundle', async () => { @@ -1302,39 +1451,32 @@ apiDescribe('Database', persistence => { b: { bar: 2 } }; await withTestCollection(persistence, testDocs, async (coll, db) => { - const q = query(coll, orderBy(documentId())); - const querySnap = await getDocs(q); + const querySnap = await getDocs(query(coll, orderBy(documentId()))); const refForDocA = querySnap.docs[0].ref; - const json = querySnap.toJSON(); const accumulator = new EventsAccumulator(); const unsubscribe = onSnapshot( db, - json, + querySnap.toJSON(), accumulator.storeEvent ); await accumulator .awaitEvent() .then(snap => { - const docs = snap.docs; - console.error("DEDB: docs: ", docs); - expect(docs).not.to.be.null; - expect(docs.length).to.equal(2); - expect(docs[0].data()).to.deep.equal(testDocs.a); - expect(docs[1].data()).to.deep.equal(testDocs.b); + expect(snap.docs).not.to.be.null; + expect(snap.docs.length).to.equal(2); + expect(snap.docs[0].data()).to.deep.equal(testDocs.a); + expect(snap.docs[1].data()).to.deep.equal(testDocs.b); }) - .then(() => setDoc(refForDocA, {foo: 0})) + .then(() => setDoc(refForDocA, { foo: 0 })) .then(() => accumulator.awaitEvent()) .then(snap => { - const docs = snap.docs; - console.error("DEDB: docs: ", docs); - expect(docs).not.to.be.null; - expect(docs.length).to.equal(2); - expect(docs[0].data()).to.deep.equal({foo: 0}); - expect(docs[1].data()).to.deep.equal(testDocs.b); + expect(snap.docs).not.to.be.null; + expect(snap.docs.length).to.equal(2); + expect(snap.docs[0].data()).to.deep.equal({ foo: 0 }); + expect(snap.docs[1].data()).to.deep.equal(testDocs.b); }); unsubscribe(); - } - ); + }); }); it('Metadata only changes are not fired when no options provided', () => { @@ -1374,7 +1516,7 @@ apiDescribe('Database', persistence => { const queryForRejection = collection(db, 'a/__badpath__/b'); onSnapshot( queryForRejection, - () => { }, + () => {}, (err: Error) => { expect(err.name).to.exist; expect(err.message).to.exist; @@ -1391,13 +1533,13 @@ apiDescribe('Database', persistence => { const queryForRejection = collection(db, 'a/__badpath__/b'); onSnapshot( queryForRejection, - () => { }, + () => {}, (err: Error) => { expect(err.name).to.exist; expect(err.message).to.exist; onSnapshot( queryForRejection, - () => { }, + () => {}, (err2: Error) => { expect(err2.name).to.exist; expect(err2.message).to.exist; @@ -1812,7 +1954,7 @@ apiDescribe('Database', persistence => { it('can query after firestore restart', async () => { return withTestDoc(persistence, async (docRef, firestore) => { const deferred: Deferred = new Deferred(); - const unsubscribe = onSnapshot(docRef, snapshot => { }, deferred.resolve); + const unsubscribe = onSnapshot(docRef, snapshot => {}, deferred.resolve); await firestore._restart(); @@ -1832,7 +1974,7 @@ apiDescribe('Database', persistence => { it('query listener throws error on termination', async () => { return withTestDoc(persistence, async (docRef, firestore) => { const deferred: Deferred = new Deferred(); - const unsubscribe = onSnapshot(docRef, snapshot => { }, deferred.resolve); + const unsubscribe = onSnapshot(docRef, snapshot => {}, deferred.resolve); await terminate(firestore); @@ -1879,7 +2021,7 @@ apiDescribe('Database', persistence => { readonly title: string, readonly author: string, readonly ref: DocumentReference | null = null - ) { } + ) {} byline(): string { return this.title + ', by ' + this.author; } @@ -2009,8 +2151,8 @@ apiDescribe('Database', persistence => { batch.set(ref, { title: 'olive' }, { merge: true }) ).to.throw( 'Function WriteBatch.set() called with invalid ' + - 'data (via `toFirestore()`). Unsupported field value: undefined ' + - '(found in field author in document posts/some-post)' + 'data (via `toFirestore()`). Unsupported field value: undefined ' + + '(found in field author in document posts/some-post)' ); }); }); From a35575a28af3112190216b0c4c40551766e4ae73 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Fri, 11 Apr 2025 13:42:18 -0400 Subject: [PATCH 20/29] Internal function formal comments --- packages/firestore/src/api/reference_impl.ts | 50 +++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index c55b5e8babe..18ee67cf19f 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -1075,8 +1075,20 @@ function convertToDocSnapshot( ); } -// onSnapshot for a QuerySnapshot or DocumentSnapshot bundle. Parses the bundle and then -// invokes onSnapshot with a Document or Query reference. +/** + * Handles {@link onSnapshot} for a bundle generated by calling {@link QuerySnapshot.toJSON} or + * {@link DocumentSnapshot.toJSON}. Parse the JSON object containing the bundle to determine the + * `bundleSource` (either form a {@link DocumentSnapshot} or {@link QuerySnapshot}, and marshall the + * other optional parameters before sending the request to either + * {@link onSnapshotDocumentSnapshotBundle} or {@link onSnapshotQuerySnapshotBundle}, respectively. + * + * @param firestore - The {@link Firestore} instance for the {@link onSnapshot} operation request. + * @param args - The variadic arguments passed to {@link onSnapshot}. + * @returns An unsubscribe function that can be called to cancel the snapshot + * listener. + * + * @internal + */ function onSnapshotBundle( reference: Firestore, ...args: unknown[] @@ -1164,8 +1176,21 @@ function onSnapshotBundle( } } -// Loads the bundle in a separate task and then invokes onSnapshot with a DocumentReference -// for the document in the bundle. Returns the unsubscribe callback immediately. +/** + * Loads the bundle in a separate task and then invokes {@link onSnapshot} with a + * {@link DocumentReference} for the document in the bundle. + * + * @param firestore - The {@link Firestore} instance for the {@link onSnapshot} operation request. + * @param json - The JSON bundle to load, produced by {@link DocumentSnapshot.toJSON}. + * @param options - Options controlling the listen behavior. + * @param observer - A single object containing `next` and `error` callbacks. + * @param converter - An optional object that converts objects from Firestore before the onNext + * listener is invoked. + * @returns An unsubscribe function that can be called to cancel the snapshot + * listener. + * + * @internal + */ function onSnapshotDocumentSnapshotBundle< AppModelType, DbModelType extends DocumentData @@ -1220,8 +1245,21 @@ function onSnapshotDocumentSnapshotBundle< }; } -// Loads the bundle in a separate task and then invokes onSnapshot with a Query -// for the documents in the bundle. Returns the unsubscribe callback immediately. +/** + * Loads the bundle in a separate task and then invokes {@link onSnapshot} with a + * {@link Query} that represents the Query in the bundle. + * + * @param firestore - The {@link Firestore} instance for the {@link onSnapshot} operation request. + * @param json - The JSON bundle to load, produced by {@link QuerySnapshot.toJSON}. + * @param options - Options controlling the listen behavior. + * @param observer - A single object containing `next` and `error` callbacks. + * @param converter - An optional object that converts objects from Firestore before the onNext + * listener is invoked. + * @returns An unsubscribe function that can be called to cancel the snapshot + * listener. + * + * @internal + */ function onSnapshotQuerySnapshotBundle< AppModelType, DbModelType extends DocumentData From afc7f448ebd3b14eb969b07ed7fe586f387bcebe Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Sat, 12 Apr 2025 16:18:06 -0400 Subject: [PATCH 21/29] PR fixes json => snapshotJSON. Common observer definition default SnapshotOption object unsubscribe check on onSnapshotDocumentSnapshotBundle --- common/api-review/firestore.api.md | 16 +- docs-devsite/firestore_.md | 64 ++++---- packages/firestore/src/api/reference_impl.ts | 162 +++++++++---------- 3 files changed, 115 insertions(+), 127 deletions(-) diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 70986703d5d..944fa434977 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -466,35 +466,35 @@ export function onSnapshot(query export function onSnapshot(query: Query, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, json: { +export function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; }, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, json: { +export function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; }, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, json: { +export function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; }, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, json: { +export function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; }, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, json: { +export function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; @@ -505,7 +505,7 @@ export function onSnapshot(fires }, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, json: { +export function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; @@ -516,7 +516,7 @@ export function onSnapshot(fires }, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, json: { +export function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; @@ -527,7 +527,7 @@ export function onSnapshot(fires }, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, json: { +export function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; diff --git a/docs-devsite/firestore_.md b/docs-devsite/firestore_.md index 19505ce9547..d5b7ab445b2 100644 --- a/docs-devsite/firestore_.md +++ b/docs-devsite/firestore_.md @@ -32,14 +32,14 @@ https://github.com/firebase/firebase-js-sdk | [getPersistentCacheIndexManager(firestore)](./firestore_.md#getpersistentcacheindexmanager_231a8e0) | Returns the PersistentCache Index Manager used by the given Firestore object. The PersistentCacheIndexManager instance, or null if local persistent storage is not in use. | | [loadBundle(firestore, bundleData)](./firestore_.md#loadbundle_bec5b75) | Loads a Firestore bundle into the local cache. | | [namedQuery(firestore, name)](./firestore_.md#namedquery_6438876) | Reads a Firestore [Query](./firestore_.query.md#query_class) from local cache, identified by the given name.The named queries are packaged into bundles on the server side (along with resulting documents), and loaded to local cache using loadBundle. Once in local cache, use this method to extract a [Query](./firestore_.query.md#query_class) by name. | -| [onSnapshot(firestore, json, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_ce95e22) | Attaches a listener for DocumentSnapshot events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | -| [onSnapshot(firestore, json, options, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_0b786a3) | Attaches a listener for QuerySnapshot events based on data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | -| [onSnapshot(firestore, json, options, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_1f316d3) | Attaches a listener for DocumentSnapshot events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | -| [onSnapshot(firestore, json, observer, converter)](./firestore_.md#onsnapshot_9b07eb9) | Attaches a listener for QuerySnapshot events based on QuerySnapshot data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | -| [onSnapshot(firestore, json, observer, converter)](./firestore_.md#onsnapshot_a4eb012) | Attaches a listener for DocumentSnapshot events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | -| [onSnapshot(firestore, json, options, observer, converter)](./firestore_.md#onsnapshot_11d66d6) | Attaches a listener for QuerySnapshot events based on QuerySnapshot data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | -| [onSnapshot(firestore, json, options, observer, converter)](./firestore_.md#onsnapshot_7fccf8d) | Attaches a listener for DocumentSnapshot events based on QuerySnapshot data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | -| [onSnapshot(firestore, json, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_3e4fc0f) | Attaches a listener for QuerySnapshot events based on data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_7947cd9) | Attaches a listener for DocumentSnapshot events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, options, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_287808a) | Attaches a listener for QuerySnapshot events based on data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, options, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_820a7f8) | Attaches a listener for DocumentSnapshot events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, observer, converter)](./firestore_.md#onsnapshot_31d89c4) | Attaches a listener for QuerySnapshot events based on QuerySnapshot data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, observer, converter)](./firestore_.md#onsnapshot_0499ec0) | Attaches a listener for DocumentSnapshot events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, options, observer, converter)](./firestore_.md#onsnapshot_8e77c51) | Attaches a listener for QuerySnapshot events based on QuerySnapshot data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, options, observer, converter)](./firestore_.md#onsnapshot_84bc82a) | Attaches a listener for DocumentSnapshot events based on QuerySnapshot data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_aaa7087) | Attaches a listener for QuerySnapshot events based on data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | | [onSnapshotsInSync(firestore, observer)](./firestore_.md#onsnapshotsinsync_2f0dfa4) | Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync event indicates that all listeners affected by a given change have fired, even if a single server-generated change affects multiple listeners.NOTE: The snapshots-in-sync event only indicates that listeners are in sync with each other, but does not relate to whether those snapshots are in sync with the server. Use SnapshotMetadata in the individual listeners to determine if a snapshot is from the cache or the server. | | [onSnapshotsInSync(firestore, onSync)](./firestore_.md#onsnapshotsinsync_1901c06) | Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync event indicates that all listeners affected by a given change have fired, even if a single server-generated change affects multiple listeners.NOTE: The snapshots-in-sync event only indicates that listeners are in sync with each other, but does not relate to whether those snapshots are in sync with the server. Use SnapshotMetadata in the individual listeners to determine if a snapshot is from the cache or the server. | | [runTransaction(firestore, updateFunction, options)](./firestore_.md#runtransaction_6f03ec4) | Executes the given updateFunction and then attempts to commit the changes applied within the transaction. If any document read within the transaction has changed, Cloud Firestore retries the updateFunction. If it fails to commit after 5 attempts, the transaction fails.The maximum number of writes allowed in a single transaction is 500. | @@ -625,7 +625,7 @@ Promise<[Query](./firestore_.query.md#query_class) \| null> A `Promise` that is resolved with the Query or `null`. -### onSnapshot(firestore, json, onNext, onError, onCompletion, converter) {:#onsnapshot_ce95e22} +### onSnapshot(firestore, snapshotJson, onNext, onError, onCompletion, converter) {:#onsnapshot_7947cd9} Attaches a listener for `DocumentSnapshot` events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual `onNext` and `onError` callbacks or pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by calling the function that is returned when `onSnapshot` is called. @@ -634,7 +634,7 @@ NOTE: Although an `onCompletion` callback can be provided, it will never be call Signature: ```typescript -export declare function onSnapshot(firestore: Firestore, json: { +export declare function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; @@ -646,7 +646,7 @@ export declare function onSnapshot. | +| snapshotJson | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). | | onNext | (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class)<AppModelType, DbModelType>) => void | A callback to be called every time a new DocumentSnapshot is available. | | onError | (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void | A callback to be called if the listen fails or is cancelled. No fruther callbacks will occur. | | onCompletion | () => void | Can be provided, but will not be called since streams are never ending. | @@ -658,7 +658,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, json: { +export declare function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; @@ -679,7 +679,7 @@ export declare function onSnapshot. | +| snapshotJson | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). | | options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md#snapshotlistenoptions_interface) | Options controlling the listen behavior. | | onNext | (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class)<AppModelType, DbModelType>) => void | A callback to be called every time a new QuerySnapshot is available. | | onError | (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void | A callback to be called if the listen fails or is cancelled. No further callbacks will occur. | @@ -692,7 +692,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, json: { +export declare function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; @@ -713,7 +713,7 @@ export declare function onSnapshot. | +| snapshotJson | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). | | options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md#snapshotlistenoptions_interface) | Options controlling the listen behavior. | | onNext | (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class)<AppModelType, DbModelType>) => void | A callback to be called every time a new DocumentSnapshot is available. | | onError | (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void | A callback to be called if the listen fails or is cancelled. No further callbacks will occur. | @@ -726,7 +726,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, json: { +export declare function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; @@ -751,7 +751,7 @@ export declare function onSnapshot. | +| snapshotJson | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). | | observer | { next: (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class)<AppModelType, DbModelType>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void; complete?: () => void; } | A single object containing next and error callbacks. | | converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | @@ -761,7 +761,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, json: { +export declare function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; @@ -786,7 +786,7 @@ export declare function onSnapshot. | +| snapshotJson | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). | | observer | { next: (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class)<AppModelType, DbModelType>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void; complete?: () => void; } | A single object containing next and error callbacks. | | converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | @@ -796,7 +796,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, json: { +export declare function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; @@ -821,7 +821,7 @@ export declare function onSnapshot. | +| snapshotJson | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). | | options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md#snapshotlistenoptions_interface) | Options controlling the listen behavior. | | observer | { next: (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class)<AppModelType, DbModelType>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void; complete?: () => void; } | A single object containing next and error callbacks. | | converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | @@ -832,7 +832,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, json: { +export declare function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; @@ -857,7 +857,7 @@ export declare function onSnapshot. | +| snapshotJson | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). | | options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md#snapshotlistenoptions_interface) | Options controlling the listen behavior. | | observer | { next: (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class)<AppModelType, DbModelType>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void; complete?: () => void; } | A single object containing next and error callbacks. | | converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | @@ -868,7 +868,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, json: { +export declare function onSnapshot(firestore: Firestore, snapshotJson: { bundle: string; bundleName: string; bundleSource: string; @@ -889,7 +889,7 @@ export declare function onSnapshot. | +| snapshotJson | { bundle: string; bundleName: string; bundleSource: string; } | A JSON object generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). | | onNext | (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class)<AppModelType, DbModelType>) => void | A callback to be called every time a new QuerySnapshot is available. | | onError | (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void | A callback to be called if the listen fails or is cancelled. No further callbacks will occur. | | onCompletion | () => void | Can be provided, but will not be called since streams are never ending. | diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index 18ee67cf19f..12bfd702d8b 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -670,7 +670,7 @@ export function onSnapshot( * snapshot stream is never-ending. * * @param firestore - The {@link Firestore} instance to enable persistence for. - * @param json - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. + * @param snapshotJson - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. * @param onNext - A callback to be called every time a new `QuerySnapshot` is available. * @param onError - A callback to be called if the listen fails or is cancelled. No further * callbacks will occur. @@ -681,7 +681,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - json: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, @@ -697,7 +697,7 @@ export function onSnapshot( * snapshot stream is never-ending. * * @param firestore - The {@link Firestore} instance to enable persistence for. - * @param json - A JSON object generated by invoking {@link DocumentSnapshot.toJSON}. + * @param snapshotJson - A JSON object generated by invoking {@link DocumentSnapshot.toJSON}. * @param onNext - A callback to be called every time a new `DocumentSnapshot` is available. * @param onError - A callback to be called if the listen fails or is cancelled. No fruther * callbacks will occur. @@ -709,7 +709,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - json: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, @@ -725,7 +725,7 @@ export function onSnapshot( * snapshot stream is never-ending. * * @param firestore - The {@link Firestore} instance to enable persistence for. - * @param json - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. + * @param snapshotJson - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. * @param options - Options controlling the listen behavior. * @param onNext - A callback to be called every time a new `QuerySnapshot` is available. * @param onError - A callback to be called if the listen fails or is cancelled. No further @@ -737,7 +737,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - json: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, @@ -754,7 +754,7 @@ export function onSnapshot( * snapshot stream is never-ending. * * @param firestore - The {@link Firestore} instance to enable persistence for. - * @param json - A JSON object generated by invoking {@link DocumentSnapshot.toJSON}. + * @param snapshotJson - A JSON object generated by invoking {@link DocumentSnapshot.toJSON}. * @param options - Options controlling the listen behavior. * @param onNext - A callback to be called every time a new `DocumentSnapshot` is available. * @param onError - A callback to be called if the listen fails or is cancelled. No further @@ -767,7 +767,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - json: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, @@ -785,7 +785,7 @@ export function onSnapshot( * snapshot stream is never-ending. * * @param firestore - The {@link Firestore} instance to enable persistence for. - * @param json - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. + * @param snapshotJson - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. * @param observer - A single object containing `next` and `error` callbacks. * @param converter - An optional object that converts objects from Firestore before the onNext * listener is invoked. @@ -794,7 +794,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - json: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, observer: { next: (snapshot: QuerySnapshot) => void; error?: (error: FirestoreError) => void; @@ -812,7 +812,7 @@ export function onSnapshot( * snapshot stream is never-ending. * * @param firestore - The {@link Firestore} instance to enable persistence for. - * @param json - A JSON object generated by invoking {@link DocumentSnapshot.toJSON}. + * @param snapshotJson - A JSON object generated by invoking {@link DocumentSnapshot.toJSON}. * @param observer - A single object containing `next` and `error` callbacks. * @param converter - An optional object that converts objects from Firestore before the onNext * listener is invoked. @@ -821,7 +821,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - json: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, observer: { next: (snapshot: DocumentSnapshot) => void; error?: (error: FirestoreError) => void; @@ -839,7 +839,7 @@ export function onSnapshot( * snapshot stream is never-ending. * * @param firestore - The {@link Firestore} instance to enable persistence for. - * @param json - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. + * @param snapshotJson - A JSON object generated by invoking {@link QuerySnapshot.toJSON}. * @param options - Options controlling the listen behavior. * @param observer - A single object containing `next` and `error` callbacks. * @param converter - An optional object that converts objects from Firestore before the onNext @@ -849,7 +849,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - json: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, options: SnapshotListenOptions, observer: { next: (snapshot: QuerySnapshot) => void; @@ -868,7 +868,7 @@ export function onSnapshot( * snapshot stream is never-ending. * * @param firestore - The {@link Firestore} instance to enable persistence for. - * @param json - A JSON object generated by invoking {@link DocumentSnapshot.toJSON}. + * @param snapshotJson - A JSON object generated by invoking {@link DocumentSnapshot.toJSON}. * @param options - Options controlling the listen behavior. * @param observer - A single object containing `next` and `error` callbacks. * @param converter - An optional object that converts objects from Firestore before the onNext @@ -877,7 +877,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - json: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, options: SnapshotListenOptions, observer: { next: (snapshot: DocumentSnapshot) => void; @@ -1107,67 +1107,67 @@ function onSnapshotBundle( } if (json.bundleSource === 'QuerySnapshot') { + let observer: { + next: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } | null = null; if (typeof args[curArg] === 'object' && isPartialObserver(args[1])) { const userObserver = args[curArg++] as PartialObserver< QuerySnapshot >; - return onSnapshotQuerySnapshotBundle( - db, - json, - options, - { - next: userObserver.next!, - error: userObserver.error, - complete: userObserver.complete - }, - args[curArg] as FirestoreDataConverter - ); + observer = { + next: userObserver.next!, + error: userObserver.error, + complete: userObserver.complete + }; } else { - return onSnapshotQuerySnapshotBundle( - db, - json, - options, - { - next: args[curArg++] as ( - snapshot: QuerySnapshot - ) => void, - error: args[curArg++] as (error: FirestoreError) => void, - complete: args[curArg++] as () => void - }, - args[curArg] as FirestoreDataConverter - ); + observer = { + next: args[curArg++] as ( + snapshot: QuerySnapshot + ) => void, + error: args[curArg++] as (error: FirestoreError) => void, + complete: args[curArg++] as () => void + }; } + return onSnapshotQuerySnapshotBundle( + db, + json, + options, + observer!, + args[curArg] as FirestoreDataConverter + ); } else if (json.bundleSource === 'DocumentSnapshot') { + let observer: { + next: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } | null = null; if (typeof args[curArg] === 'object' && isPartialObserver(args[1])) { const userObserver = args[curArg++] as PartialObserver< DocumentSnapshot >; - return onSnapshotDocumentSnapshotBundle( - db, - json, - options, - { - next: userObserver.next!, - error: userObserver.error, - complete: userObserver.complete - }, - args[curArg] as FirestoreDataConverter - ); + observer = { + next: userObserver.next!, + error: userObserver.error, + complete: userObserver.complete + }; } else { - return onSnapshotDocumentSnapshotBundle( - db, - json, - options, - { - next: args[curArg++] as ( - snapshot: DocumentSnapshot - ) => void, - error: args[curArg++] as (error: FirestoreError) => void, - complete: args[curArg++] as () => void - }, - args[curArg] as FirestoreDataConverter - ); + observer = { + next: args[curArg++] as ( + snapshot: DocumentSnapshot + ) => void, + error: args[curArg++] as (error: FirestoreError) => void, + complete: args[curArg++] as () => void + }; } + return onSnapshotDocumentSnapshotBundle( + db, + json, + options, + observer!, + args[curArg] as FirestoreDataConverter + ); } else { throw new FirestoreError( Code.INVALID_ARGUMENT, @@ -1210,20 +1210,15 @@ function onSnapshotDocumentSnapshotBundle< const loadTask = loadBundle(db, json.bundle); loadTask .then(() => { - const docReference = new DocumentReference( - db, - converter ? converter : null, - DocumentKey.fromPath(json.bundleName) - ); - if (options !== undefined) { - internalUnsubscribe = onSnapshot( - docReference as DocumentReference, - options, - observer + if (!unsubscribed) { + const docReference = new DocumentReference( + db, + converter ? converter : null, + DocumentKey.fromPath(json.bundleName) ); - } else { internalUnsubscribe = onSnapshot( docReference as DocumentReference, + options ? options : {}, observer ); } @@ -1285,18 +1280,11 @@ function onSnapshotQuerySnapshotBundle< if (converter) { realQuery.withConverter(converter); } - if (options !== undefined) { - internalUnsubscribe = onSnapshot( - query as Query, - options, - observer - ); - } else { - internalUnsubscribe = onSnapshot( - query as Query, - observer - ); - } + internalUnsubscribe = onSnapshot( + query as Query, + options ? options : {}, + observer + ); } }) .catch(e => { From 68d49a6ea0b543b46a9f0a4c65e4c25e14c0e104 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 14 Apr 2025 10:48:34 -0400 Subject: [PATCH 22/29] Fixes for review. --- common/api-review/firestore.api.md | 60 ++------- docs-devsite/firestore_.documentsnapshot.md | 8 +- docs-devsite/firestore_.md | 96 +++++--------- docs-devsite/firestore_.querysnapshot.md | 8 +- packages/firestore/src/api/reference_impl.ts | 87 ++++++++++--- packages/firestore/src/api/snapshot.ts | 4 +- .../test/integration/api/database.test.ts | 122 ++++++++---------- 7 files changed, 171 insertions(+), 214 deletions(-) diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 944fa434977..ebd620e43ca 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -179,11 +179,7 @@ export class DocumentSnapshot; // (undocumented) - toJSON(): { - bundle: string; - bundleSource: string; - bundleName: string; - }; + toJSON(): object; } export { EmulatorMockTokenOptions } @@ -466,72 +462,40 @@ export function onSnapshot(query export function onSnapshot(query: Query, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +export function onSnapshot(firestore: Firestore, snapshotJson: object, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +export function onSnapshot(firestore: Firestore, snapshotJson: object, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +export function onSnapshot(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +export function onSnapshot(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, observer: { +export function onSnapshot(firestore: Firestore, snapshotJson: object, observer: { next: (snapshot: QuerySnapshot) => void; error?: (error: FirestoreError) => void; complete?: () => void; }, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, observer: { +export function onSnapshot(firestore: Firestore, snapshotJson: object, observer: { next: (snapshot: DocumentSnapshot) => void; error?: (error: FirestoreError) => void; complete?: () => void; }, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, options: SnapshotListenOptions, observer: { +export function onSnapshot(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, observer: { next: (snapshot: QuerySnapshot) => void; error?: (error: FirestoreError) => void; complete?: () => void; }, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, options: SnapshotListenOptions, observer: { +export function onSnapshot(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, observer: { next: (snapshot: DocumentSnapshot) => void; error?: (error: FirestoreError) => void; complete?: () => void; @@ -689,11 +653,7 @@ export class QuerySnapshot; get size(): number; // (undocumented) - toJSON(): { - bundle: string; - bundleSource: string; - bundleName: string; - }; + toJSON(): object; } // @public diff --git a/docs-devsite/firestore_.documentsnapshot.md b/docs-devsite/firestore_.documentsnapshot.md index a21cbde871d..8c4825593dc 100644 --- a/docs-devsite/firestore_.documentsnapshot.md +++ b/docs-devsite/firestore_.documentsnapshot.md @@ -150,13 +150,9 @@ The data at the specified field location or undefined if no such field exists in Signature: ```typescript -toJSON(): { - bundle: string; - bundleSource: string; - bundleName: string; - }; +toJSON(): object; ``` Returns: -{ bundle: string; bundleSource: string; bundleName: string; } +object diff --git a/docs-devsite/firestore_.md b/docs-devsite/firestore_.md index d5b7ab445b2..685958d364c 100644 --- a/docs-devsite/firestore_.md +++ b/docs-devsite/firestore_.md @@ -32,14 +32,14 @@ https://github.com/firebase/firebase-js-sdk | [getPersistentCacheIndexManager(firestore)](./firestore_.md#getpersistentcacheindexmanager_231a8e0) | Returns the PersistentCache Index Manager used by the given Firestore object. The PersistentCacheIndexManager instance, or null if local persistent storage is not in use. | | [loadBundle(firestore, bundleData)](./firestore_.md#loadbundle_bec5b75) | Loads a Firestore bundle into the local cache. | | [namedQuery(firestore, name)](./firestore_.md#namedquery_6438876) | Reads a Firestore [Query](./firestore_.query.md#query_class) from local cache, identified by the given name.The named queries are packaged into bundles on the server side (along with resulting documents), and loaded to local cache using loadBundle. Once in local cache, use this method to extract a [Query](./firestore_.query.md#query_class) by name. | -| [onSnapshot(firestore, snapshotJson, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_7947cd9) | Attaches a listener for DocumentSnapshot events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | -| [onSnapshot(firestore, snapshotJson, options, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_287808a) | Attaches a listener for QuerySnapshot events based on data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | -| [onSnapshot(firestore, snapshotJson, options, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_820a7f8) | Attaches a listener for DocumentSnapshot events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | -| [onSnapshot(firestore, snapshotJson, observer, converter)](./firestore_.md#onsnapshot_31d89c4) | Attaches a listener for QuerySnapshot events based on QuerySnapshot data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | -| [onSnapshot(firestore, snapshotJson, observer, converter)](./firestore_.md#onsnapshot_0499ec0) | Attaches a listener for DocumentSnapshot events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | -| [onSnapshot(firestore, snapshotJson, options, observer, converter)](./firestore_.md#onsnapshot_8e77c51) | Attaches a listener for QuerySnapshot events based on QuerySnapshot data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | -| [onSnapshot(firestore, snapshotJson, options, observer, converter)](./firestore_.md#onsnapshot_84bc82a) | Attaches a listener for DocumentSnapshot events based on QuerySnapshot data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | -| [onSnapshot(firestore, snapshotJson, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_aaa7087) | Attaches a listener for QuerySnapshot events based on data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_712362a) | Attaches a listener for DocumentSnapshot events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, options, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_8807e6e) | Attaches a listener for QuerySnapshot events based on data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, options, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_301fcec) | Attaches a listener for DocumentSnapshot events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, observer, converter)](./firestore_.md#onsnapshot_b8b5c9d) | Attaches a listener for QuerySnapshot events based on QuerySnapshot data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, observer, converter)](./firestore_.md#onsnapshot_9b75d28) | Attaches a listener for DocumentSnapshot events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, options, observer, converter)](./firestore_.md#onsnapshot_fb80adf) | Attaches a listener for QuerySnapshot events based on QuerySnapshot data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, options, observer, converter)](./firestore_.md#onsnapshot_f76d912) | Attaches a listener for DocumentSnapshot events based on QuerySnapshot data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [onSnapshot(firestore, snapshotJson, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshot_7c84f5e) | Attaches a listener for QuerySnapshot events based on data generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | | [onSnapshotsInSync(firestore, observer)](./firestore_.md#onsnapshotsinsync_2f0dfa4) | Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync event indicates that all listeners affected by a given change have fired, even if a single server-generated change affects multiple listeners.NOTE: The snapshots-in-sync event only indicates that listeners are in sync with each other, but does not relate to whether those snapshots are in sync with the server. Use SnapshotMetadata in the individual listeners to determine if a snapshot is from the cache or the server. | | [onSnapshotsInSync(firestore, onSync)](./firestore_.md#onsnapshotsinsync_1901c06) | Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync event indicates that all listeners affected by a given change have fired, even if a single server-generated change affects multiple listeners.NOTE: The snapshots-in-sync event only indicates that listeners are in sync with each other, but does not relate to whether those snapshots are in sync with the server. Use SnapshotMetadata in the individual listeners to determine if a snapshot is from the cache or the server. | | [runTransaction(firestore, updateFunction, options)](./firestore_.md#runtransaction_6f03ec4) | Executes the given updateFunction and then attempts to commit the changes applied within the transaction. If any document read within the transaction has changed, Cloud Firestore retries the updateFunction. If it fails to commit after 5 attempts, the transaction fails.The maximum number of writes allowed in a single transaction is 500. | @@ -625,7 +625,7 @@ Promise<[Query](./firestore_.query.md#query_class) \| null> A `Promise` that is resolved with the Query or `null`. -### onSnapshot(firestore, snapshotJson, onNext, onError, onCompletion, converter) {:#onsnapshot_7947cd9} +### onSnapshot(firestore, snapshotJson, onNext, onError, onCompletion, converter) {:#onsnapshot_712362a} Attaches a listener for `DocumentSnapshot` events based on data generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) You may either pass individual `onNext` and `onError` callbacks or pass a single observer object with `next` and `error` callbacks. The listener can be cancelled by calling the function that is returned when `onSnapshot` is called. @@ -634,11 +634,7 @@ NOTE: Although an `onCompletion` callback can be provided, it will never be call Signature: ```typescript -export declare function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +export declare function onSnapshot(firestore: Firestore, snapshotJson: object, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; ``` #### Parameters @@ -646,7 +642,7 @@ export declare function onSnapshot. | +| snapshotJson | object | A JSON object generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). | | onNext | (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class)<AppModelType, DbModelType>) => void | A callback to be called every time a new DocumentSnapshot is available. | | onError | (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void | A callback to be called if the listen fails or is cancelled. No fruther callbacks will occur. | | onCompletion | () => void | Can be provided, but will not be called since streams are never ending. | @@ -658,7 +654,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +export declare function onSnapshot(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; ``` #### Parameters @@ -679,7 +671,7 @@ export declare function onSnapshot. | +| snapshotJson | object | A JSON object generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). | | options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md#snapshotlistenoptions_interface) | Options controlling the listen behavior. | | onNext | (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class)<AppModelType, DbModelType>) => void | A callback to be called every time a new QuerySnapshot is available. | | onError | (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void | A callback to be called if the listen fails or is cancelled. No further callbacks will occur. | @@ -692,7 +684,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +export declare function onSnapshot(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; ``` #### Parameters @@ -713,7 +701,7 @@ export declare function onSnapshot. | +| snapshotJson | object | A JSON object generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). | | options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md#snapshotlistenoptions_interface) | Options controlling the listen behavior. | | onNext | (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class)<AppModelType, DbModelType>) => void | A callback to be called every time a new DocumentSnapshot is available. | | onError | (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void | A callback to be called if the listen fails or is cancelled. No further callbacks will occur. | @@ -726,7 +714,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, observer: { +export declare function onSnapshot(firestore: Firestore, snapshotJson: object, observer: { next: (snapshot: QuerySnapshot) => void; error?: (error: FirestoreError) => void; complete?: () => void; @@ -751,7 +735,7 @@ export declare function onSnapshot. | +| snapshotJson | object | A JSON object generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). | | observer | { next: (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class)<AppModelType, DbModelType>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void; complete?: () => void; } | A single object containing next and error callbacks. | | converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | @@ -761,7 +745,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, observer: { +export declare function onSnapshot(firestore: Firestore, snapshotJson: object, observer: { next: (snapshot: DocumentSnapshot) => void; error?: (error: FirestoreError) => void; complete?: () => void; @@ -786,7 +766,7 @@ export declare function onSnapshot. | +| snapshotJson | object | A JSON object generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). | | observer | { next: (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class)<AppModelType, DbModelType>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void; complete?: () => void; } | A single object containing next and error callbacks. | | converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | @@ -796,7 +776,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, options: SnapshotListenOptions, observer: { +export declare function onSnapshot(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, observer: { next: (snapshot: QuerySnapshot) => void; error?: (error: FirestoreError) => void; complete?: () => void; @@ -821,7 +797,7 @@ export declare function onSnapshot. | +| snapshotJson | object | A JSON object generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). | | options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md#snapshotlistenoptions_interface) | Options controlling the listen behavior. | | observer | { next: (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class)<AppModelType, DbModelType>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void; complete?: () => void; } | A single object containing next and error callbacks. | | converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | @@ -832,7 +808,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, options: SnapshotListenOptions, observer: { +export declare function onSnapshot(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, observer: { next: (snapshot: DocumentSnapshot) => void; error?: (error: FirestoreError) => void; complete?: () => void; @@ -857,7 +829,7 @@ export declare function onSnapshot. | +| snapshotJson | object | A JSON object generated by invoking [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). | | options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md#snapshotlistenoptions_interface) | Options controlling the listen behavior. | | observer | { next: (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class)<AppModelType, DbModelType>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void; complete?: () => void; } | A single object containing next and error callbacks. | | converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<DbModelType> | An optional object that converts objects from Firestore before the onNext listener is invoked. | @@ -868,7 +840,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, snapshotJson: { - bundle: string; - bundleName: string; - bundleSource: string; -}, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +export declare function onSnapshot(firestore: Firestore, snapshotJson: object, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; ``` #### Parameters @@ -889,7 +857,7 @@ export declare function onSnapshot. | +| snapshotJson | object | A JSON object generated by invoking [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). | | onNext | (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class)<AppModelType, DbModelType>) => void | A callback to be called every time a new QuerySnapshot is available. | | onError | (error: [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class)) => void | A callback to be called if the listen fails or is cancelled. No further callbacks will occur. | | onCompletion | () => void | Can be provided, but will not be called since streams are never ending. | diff --git a/docs-devsite/firestore_.querysnapshot.md b/docs-devsite/firestore_.querysnapshot.md index f2693378d7f..da0913d7b6e 100644 --- a/docs-devsite/firestore_.querysnapshot.md +++ b/docs-devsite/firestore_.querysnapshot.md @@ -132,13 +132,9 @@ void Signature: ```typescript -toJSON(): { - bundle: string; - bundleSource: string; - bundleName: string; - }; +toJSON(): object; ``` Returns: -{ bundle: string; bundleSource: string; bundleName: string; } +object diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index 12bfd702d8b..a137a490af7 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -681,7 +681,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: object, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, @@ -709,7 +709,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: object, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, @@ -737,7 +737,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: object, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, @@ -767,7 +767,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: object, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, @@ -794,7 +794,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: object, observer: { next: (snapshot: QuerySnapshot) => void; error?: (error: FirestoreError) => void; @@ -821,7 +821,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: object, observer: { next: (snapshot: DocumentSnapshot) => void; error?: (error: FirestoreError) => void; @@ -849,7 +849,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: object, options: SnapshotListenOptions, observer: { next: (snapshot: QuerySnapshot) => void; @@ -877,7 +877,7 @@ export function onSnapshot( */ export function onSnapshot( firestore: Firestore, - snapshotJson: { bundle: string; bundleName: string; bundleSource: string }, + snapshotJson: object, options: SnapshotListenOptions, observer: { next: (snapshot: DocumentSnapshot) => void; @@ -1095,24 +1095,22 @@ function onSnapshotBundle( ): Unsubscribe { const db = getModularInstance(reference); let curArg = 0; - const json = args[curArg++] as { - bundle: string; - bundleName: string; - bundleSource: string; - }; - + const snapshotJson = normalizeSnapshotJsonFields(args[curArg++] as object); + if (snapshotJson.error) { + throw new FirestoreError(Code.INVALID_ARGUMENT, snapshotJson.error); + } let options: SnapshotListenOptions | undefined = undefined; if (typeof args[curArg] === 'object' && !isPartialObserver(args[curArg])) { options = args[curArg++] as SnapshotListenOptions; } - if (json.bundleSource === 'QuerySnapshot') { + if (snapshotJson.bundleSource === 'QuerySnapshot') { let observer: { next: (snapshot: QuerySnapshot) => void; error?: (error: FirestoreError) => void; complete?: () => void; } | null = null; - if (typeof args[curArg] === 'object' && isPartialObserver(args[1])) { + if (typeof args[curArg] === 'object' && isPartialObserver(args[1])) {) const userObserver = args[curArg++] as PartialObserver< QuerySnapshot >; @@ -1132,12 +1130,12 @@ function onSnapshotBundle( } return onSnapshotQuerySnapshotBundle( db, - json, + snapshotJson, options, observer!, args[curArg] as FirestoreDataConverter ); - } else if (json.bundleSource === 'DocumentSnapshot') { + } else if (snapshotJson.bundleSource === 'DocumentSnapshot') { let observer: { next: (snapshot: DocumentSnapshot) => void; error?: (error: FirestoreError) => void; @@ -1163,7 +1161,7 @@ function onSnapshotBundle( } return onSnapshotDocumentSnapshotBundle( db, - json, + snapshotJson, options, observer!, args[curArg] as FirestoreDataConverter @@ -1171,11 +1169,60 @@ function onSnapshotBundle( } else { throw new FirestoreError( Code.INVALID_ARGUMENT, - `unsupported bundle source: ${json.bundleSource}` + `unsupported bundle source: ${snapshotJson.bundleSource}` ); } } +/** + * Ensures the data required to construct an {@link onSnapshot} listener exist in a `snapshotJson` + * object that originates from {@link DocumentSnapshot.toJSON} or {@link Querysnapshot.toJSON}. The + * data is normalized into a typed object. + * + * @param snapshotJson - The JSON object that the app provided to {@link onSnapshot}. + * @returns A normalized object that contains all of the required bundle JSON fields. If + * {@link snapshotJson} doesn't contain the required fields, or if the fields exist as empty + * strings, then the {@link snapshotJson.error} field will be a non empty string. + * + * @internal + */ +function normalizeSnapshotJsonFields(snapshotJson: object): { + bundle: string; + bundleName: string; + bundleSource: string; + error?: string; +} { + const result: { + bundle: string; + bundleName: string; + bundleSource: string; + error?: string; + } = { + bundle: '', + bundleName: '', + bundleSource: '' + }; + const requiredKeys = ['bundle', 'bundleName', 'bundleSource']; + for (const key of requiredKeys) { + if (!(key in snapshotJson)) { + result.error = `snapshotJson missing required field: ${key}`; + break; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const value = (snapshotJson as any)[key]; + if (typeof value !== 'string') { + result.error = `snapshotJson field '${key}' must be a string.`; + break; + } + if (value.length === 0) { + result.error = `snapshotJson field '${key}' cannot be an empty string.`; + break; + } + result[key as keyof typeof result] = value; + } + return result; +} + /** * Loads the bundle in a separate task and then invokes {@link onSnapshot} with a * {@link DocumentReference} for the document in the bundle. diff --git a/packages/firestore/src/api/snapshot.ts b/packages/firestore/src/api/snapshot.ts index 01f974c097e..4f2f056d720 100644 --- a/packages/firestore/src/api/snapshot.ts +++ b/packages/firestore/src/api/snapshot.ts @@ -503,7 +503,7 @@ export class DocumentSnapshot< return undefined; } - toJSON(): { bundle: string; bundleSource: string; bundleName: string } { + toJSON(): object { const document = this._document; // eslint-disable-next-line @typescript-eslint/no-explicit-any const result: any = {}; @@ -699,7 +699,7 @@ export class QuerySnapshot< return this._cachedChanges; } - toJSON(): { bundle: string; bundleSource: string; bundleName: string } { + toJSON(): object { // eslint-disable-next-line @typescript-eslint/no-explicit-any const result: any = {}; result['bundleSource'] = 'QuerySnapshot'; diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index d587b1f386d..b6320169582 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -1292,59 +1292,53 @@ apiDescribe('Database', persistence => { }); it('DocumentSnapshot error events for snapshot created by a bundle', async () => { - const initialData = { a: 0 }; - await withTestDocAndInitialData( - persistence, - initialData, - async (docRef, db) => { - const doc = await getDoc(docRef); - const json = doc.toJSON(); - json.bundle = 'BadData'; - const deferred = new Deferred(); - const unsubscribe = onSnapshot( - db, - json, - ds => { - expect(ds).to.not.exist; - deferred.resolve(); - }, - err => { - expect(err.name).to.exist; - expect(err.message).to.exist; - deferred.resolve(); - } - ); - await deferred.promise; - unsubscribe(); - } - ); + return withTestDb(persistence, async db => { + const json = { + bundle: 'BadData', + bundleName: 'bundleName', + bundleSource: 'DocumentSnapshot' + }; + const deferred = new Deferred(); + const unsubscribe = onSnapshot( + db, + json, + ds => { + expect(ds).to.not.exist; + deferred.resolve(); + }, + err => { + expect(err.name).to.exist; + expect(err.message).to.exist; + deferred.resolve(); + } + ); + await deferred.promise; + unsubscribe(); + }); }); it('DocumentSnapshot observer error events for snapshot created by a bundle', async () => { - const initialData = { a: 0 }; - await withTestDocAndInitialData( - persistence, - initialData, - async (docRef, db) => { - const doc = await getDoc(docRef); - const json = doc.toJSON(); - json.bundle = 'BadData'; - const deferred = new Deferred(); - const unsubscribe = onSnapshot(db, json, { - next: ds => { - expect(ds).to.not.exist; - deferred.resolve(); - }, - error: err => { - expect(err.name).to.exist; - expect(err.message).to.exist; - deferred.resolve(); - } - }); - await deferred.promise; - unsubscribe(); - } - ); + return withTestDb(persistence, async db => { + const json = { + bundle: 'BadData', + bundleName: 'bundleName', + bundleSource: 'QuerySnapshot' + }; + const deferred = new Deferred(); + const unsubscribe = onSnapshot(db, json, { + next: ds => { + expect(ds).to.not.exist; + deferred.resolve(); + }, + error: err => { + expect(err.name).to.exist; + expect(err.message).to.exist; + deferred.resolve(); + } + }); + await deferred.promise; + unsubscribe(); + }); }); it('Querysnapshot events for snapshot created by a bundle', async () => { @@ -1392,15 +1386,13 @@ apiDescribe('Database', persistence => { }); it('QuerySnapshot error events for snapshot created by a bundle', async () => { - const testDocs = { - a: { foo: 1 }, - b: { bar: 2 } - }; - await withTestCollection(persistence, testDocs, async (coll, db) => { - const querySnap = await getDocs(query(coll, orderBy(documentId()))); + return withTestDb(persistence, async db => { + const json = { + bundle: 'BadData', + bundleName: 'bundleName', + bundleSource: 'QuerySnapshot' + }; const deferred = new Deferred(); - const json = querySnap.toJSON(); - json.bundle = 'BadData'; const unsubscribe = onSnapshot( db, json, @@ -1420,15 +1412,13 @@ apiDescribe('Database', persistence => { }); it('QuerySnapshot observer error events for snapshot created by a bundle', async () => { - const testDocs = { - a: { foo: 1 }, - b: { bar: 2 } - }; - await withTestCollection(persistence, testDocs, async (coll, db) => { - const querySnap = await getDocs(query(coll, orderBy(documentId()))); + return withTestDb(persistence, async db => { + const json = { + bundle: 'BadData', + bundleName: 'bundleName', + bundleSource: 'QuerySnapshot' + }; const deferred = new Deferred(); - const json = querySnap.toJSON(); - json.bundle = 'BadData'; const unsubscribe = onSnapshot(db, json, { next: qs => { expect(qs).to.not.exist; From 078d0c46f51c4ca6e7b980cb8429050ce3a548b6 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 14 Apr 2025 11:10:38 -0400 Subject: [PATCH 23/29] removed typo. --- packages/firestore/src/api/reference_impl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index a137a490af7..3e299f47510 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -1110,7 +1110,7 @@ function onSnapshotBundle( error?: (error: FirestoreError) => void; complete?: () => void; } | null = null; - if (typeof args[curArg] === 'object' && isPartialObserver(args[1])) {) + if (typeof args[curArg] === 'object' && isPartialObserver(args[1])) { const userObserver = args[curArg++] as PartialObserver< QuerySnapshot >; From c4dab65f98da3739ddc11ac18f353cc17942fd41 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 14 Apr 2025 12:53:05 -0400 Subject: [PATCH 24/29] Debug output for CI. --- packages/firestore/src/api/reference_impl.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index 3e299f47510..0b7f5bd2c0a 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -1095,7 +1095,9 @@ function onSnapshotBundle( ): Unsubscribe { const db = getModularInstance(reference); let curArg = 0; + console.error("DEDB json args: ", args[curArg] as object); const snapshotJson = normalizeSnapshotJsonFields(args[curArg++] as object); + console.error("DEDB parsed snapshotJson: ", snapshotJson); if (snapshotJson.error) { throw new FirestoreError(Code.INVALID_ARGUMENT, snapshotJson.error); } From 3ef49bbc18e01e40b3e2151e6ad614da47241d1b Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 14 Apr 2025 13:21:00 -0400 Subject: [PATCH 25/29] more debug info --- packages/firestore/src/api/reference_impl.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index 0b7f5bd2c0a..11b95a3f919 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -1106,7 +1106,8 @@ function onSnapshotBundle( options = args[curArg++] as SnapshotListenOptions; } - if (snapshotJson.bundleSource === 'QuerySnapshot') { + if (snapshotJson.bundleSource === "QuerySnapshot") { + console.error("DEDB QuerySnapshot"); let observer: { next: (snapshot: QuerySnapshot) => void; error?: (error: FirestoreError) => void; @@ -1137,7 +1138,8 @@ function onSnapshotBundle( observer!, args[curArg] as FirestoreDataConverter ); - } else if (snapshotJson.bundleSource === 'DocumentSnapshot') { + } else if (snapshotJson.bundleSource === "DocumentSnapshot") { + console.error("DEDB DocumentSnapshot"); let observer: { next: (snapshot: DocumentSnapshot) => void; error?: (error: FirestoreError) => void; @@ -1169,6 +1171,7 @@ function onSnapshotBundle( args[curArg] as FirestoreDataConverter ); } else { + console.error("DEDB other, bundlesource : ", snapshotJson.bundleSource); throw new FirestoreError( Code.INVALID_ARGUMENT, `unsupported bundle source: ${snapshotJson.bundleSource}` From 72e970c47b7174a06fe36fbc878fe1fa5d52b132 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 14 Apr 2025 13:41:37 -0400 Subject: [PATCH 26/29] ... --- packages/firestore/src/api/reference_impl.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index 11b95a3f919..ae4b5e35f15 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -1096,18 +1096,20 @@ function onSnapshotBundle( const db = getModularInstance(reference); let curArg = 0; console.error("DEDB json args: ", args[curArg] as object); - const snapshotJson = normalizeSnapshotJsonFields(args[curArg++] as object); + const snapshotJson = normalizeSnapshotJsonFields(args[curArg] as object); console.error("DEDB parsed snapshotJson: ", snapshotJson); + console.error("DEDB parsed bundleSource: ", snapshotJson.bundleSource); if (snapshotJson.error) { throw new FirestoreError(Code.INVALID_ARGUMENT, snapshotJson.error); } + curArg++; let options: SnapshotListenOptions | undefined = undefined; if (typeof args[curArg] === 'object' && !isPartialObserver(args[curArg])) { options = args[curArg++] as SnapshotListenOptions; } if (snapshotJson.bundleSource === "QuerySnapshot") { - console.error("DEDB QuerySnapshot"); + console.error("DEDB QuerySnapshot, snapshotJson.bundleSource: ", snapshotJson.bundleSource); let observer: { next: (snapshot: QuerySnapshot) => void; error?: (error: FirestoreError) => void; @@ -1139,7 +1141,7 @@ function onSnapshotBundle( args[curArg] as FirestoreDataConverter ); } else if (snapshotJson.bundleSource === "DocumentSnapshot") { - console.error("DEDB DocumentSnapshot"); + console.error("DEDB DocumentSnapshot snapshotJson.bundleSource", snapshotJson.bundleSource); let observer: { next: (snapshot: DocumentSnapshot) => void; error?: (error: FirestoreError) => void; @@ -1171,7 +1173,7 @@ function onSnapshotBundle( args[curArg] as FirestoreDataConverter ); } else { - console.error("DEDB other, bundlesource : ", snapshotJson.bundleSource); + console.error("DEDB other, bundleSource : ", snapshotJson.bundleSource); throw new FirestoreError( Code.INVALID_ARGUMENT, `unsupported bundle source: ${snapshotJson.bundleSource}` From 378d1fc2097a181a2e8d2820aec44ebd75d892cb Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 14 Apr 2025 14:12:43 -0400 Subject: [PATCH 27/29] ... --- packages/firestore/src/api/reference_impl.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index ae4b5e35f15..6d745243fcd 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -1225,8 +1225,10 @@ function normalizeSnapshotJsonFields(snapshotJson: object): { result.error = `snapshotJson field '${key}' cannot be an empty string.`; break; } + console.error("DEDB setting key: \"" + key + "\" value: \"" + value + "\""); result[key as keyof typeof result] = value; } + console.error("DEDB returning result.bundleSource: ", result.bundleSource, " result.error: ", result.error); return result; } From 9b2f0fef66091a03f3a6a5d7448c16fea1708871 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 14 Apr 2025 14:45:40 -0400 Subject: [PATCH 28/29] Alt impl for setting keys. --- packages/firestore/src/api/reference_impl.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index 6d745243fcd..2fae79f50c2 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -1226,7 +1226,13 @@ function normalizeSnapshotJsonFields(snapshotJson: object): { break; } console.error("DEDB setting key: \"" + key + "\" value: \"" + value + "\""); - result[key as keyof typeof result] = value; + if (key === 'bundle') { + result.bundle = value; // No assertion needed, TS knows 'bundle' is a key + } else if (key === 'bundleName') { + result.bundleName = value; // No assertion needed + } else if (key === 'bundleSource') { + result.bundleSource = value; // No assertion needed + } } console.error("DEDB returning result.bundleSource: ", result.bundleSource, " result.error: ", result.error); return result; From fc8d5685bd55e844a7395f19dc46c903d6fb79d7 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 14 Apr 2025 15:17:09 -0400 Subject: [PATCH 29/29] Remove debug output --- packages/firestore/src/api/reference_impl.ts | 21 ++++++-------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index 2fae79f50c2..b920c479d0b 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -1095,21 +1095,16 @@ function onSnapshotBundle( ): Unsubscribe { const db = getModularInstance(reference); let curArg = 0; - console.error("DEDB json args: ", args[curArg] as object); - const snapshotJson = normalizeSnapshotJsonFields(args[curArg] as object); - console.error("DEDB parsed snapshotJson: ", snapshotJson); - console.error("DEDB parsed bundleSource: ", snapshotJson.bundleSource); + const snapshotJson = normalizeSnapshotJsonFields(args[curArg++] as object); if (snapshotJson.error) { throw new FirestoreError(Code.INVALID_ARGUMENT, snapshotJson.error); } - curArg++; let options: SnapshotListenOptions | undefined = undefined; if (typeof args[curArg] === 'object' && !isPartialObserver(args[curArg])) { options = args[curArg++] as SnapshotListenOptions; } - if (snapshotJson.bundleSource === "QuerySnapshot") { - console.error("DEDB QuerySnapshot, snapshotJson.bundleSource: ", snapshotJson.bundleSource); + if (snapshotJson.bundleSource === 'QuerySnapshot') { let observer: { next: (snapshot: QuerySnapshot) => void; error?: (error: FirestoreError) => void; @@ -1140,8 +1135,7 @@ function onSnapshotBundle( observer!, args[curArg] as FirestoreDataConverter ); - } else if (snapshotJson.bundleSource === "DocumentSnapshot") { - console.error("DEDB DocumentSnapshot snapshotJson.bundleSource", snapshotJson.bundleSource); + } else if (snapshotJson.bundleSource === 'DocumentSnapshot') { let observer: { next: (snapshot: DocumentSnapshot) => void; error?: (error: FirestoreError) => void; @@ -1173,7 +1167,6 @@ function onSnapshotBundle( args[curArg] as FirestoreDataConverter ); } else { - console.error("DEDB other, bundleSource : ", snapshotJson.bundleSource); throw new FirestoreError( Code.INVALID_ARGUMENT, `unsupported bundle source: ${snapshotJson.bundleSource}` @@ -1225,16 +1218,14 @@ function normalizeSnapshotJsonFields(snapshotJson: object): { result.error = `snapshotJson field '${key}' cannot be an empty string.`; break; } - console.error("DEDB setting key: \"" + key + "\" value: \"" + value + "\""); if (key === 'bundle') { - result.bundle = value; // No assertion needed, TS knows 'bundle' is a key + result.bundle = value; } else if (key === 'bundleName') { - result.bundleName = value; // No assertion needed + result.bundleName = value; } else if (key === 'bundleSource') { - result.bundleSource = value; // No assertion needed + result.bundleSource = value; } } - console.error("DEDB returning result.bundleSource: ", result.bundleSource, " result.error: ", result.error); return result; }