diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 0dd52644d8f..46b85a0efc5 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -137,7 +137,8 @@ export function documentId(): FieldPath; export class DocumentReference { readonly converter: FirestoreDataConverter | null; readonly firestore: Firestore; - static fromJSON(firestore: Firestore, json: object, converter?: FirestoreDataConverter): DocumentReference; + static fromJSON(firestore: Firestore, json: object): DocumentReference; + static fromJSON(firestore: Firestore, json: object, converter: FirestoreDataConverter): DocumentReference; get id(): string; get parent(): CollectionReference; get path(): string; diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 7bce21a7cc4..292d81d7a75 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -163,7 +163,8 @@ export function documentId(): FieldPath; export class DocumentReference { readonly converter: FirestoreDataConverter | null; readonly firestore: Firestore; - static fromJSON(firestore: Firestore, json: object, converter?: FirestoreDataConverter): DocumentReference; + static fromJSON(firestore: Firestore, json: object): DocumentReference; + static fromJSON(firestore: Firestore, json: object, converter: FirestoreDataConverter): DocumentReference; get id(): string; get parent(): CollectionReference; get path(): string; @@ -182,10 +183,15 @@ export class DocumentSnapshot; - // (undocumented) toJSON(): object; } +// @public +export function documentSnapshotFromJSON(db: Firestore, json: object): DocumentSnapshot; + +// @public +export function documentSnapshotFromJSON(db: Firestore, json: object, converter: FirestoreDataConverter): DocumentSnapshot; + export { EmulatorMockTokenOptions } // @public @deprecated @@ -468,40 +474,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: object, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +export function onSnapshotResume(firestore: Firestore, snapshotJson: object, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, snapshotJson: object, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +export function onSnapshotResume(firestore: Firestore, snapshotJson: object, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; // @public -export function onSnapshot(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +export function onSnapshotResume(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: object, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +export function onSnapshotResume(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: object, observer: { +export function onSnapshotResume(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: object, observer: { +export function onSnapshotResume(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: object, options: SnapshotListenOptions, observer: { +export function onSnapshotResume(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: object, options: SnapshotListenOptions, observer: { +export function onSnapshotResume(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, observer: { next: (snapshot: DocumentSnapshot) => void; error?: (error: FirestoreError) => void; complete?: () => void; @@ -658,10 +664,15 @@ export class QuerySnapshot; get size(): number; - // (undocumented) toJSON(): object; } +// @public +export function querySnapshotFromJSON(db: Firestore, json: object): QuerySnapshot; + +// @public +export function querySnapshotFromJSON(db: Firestore, json: object, converter: FirestoreDataConverter): QuerySnapshot; + // @public export class QueryStartAtConstraint extends QueryConstraint { readonly type: 'startAt' | 'startAfter'; diff --git a/docs-devsite/firestore_.documentreference.md b/docs-devsite/firestore_.documentreference.md index 53a38cd8dd7..ee4be972b0c 100644 --- a/docs-devsite/firestore_.documentreference.md +++ b/docs-devsite/firestore_.documentreference.md @@ -33,6 +33,7 @@ export declare class DocumentReferencestatic | Builds a DocumentReference instance from a JSON object created by [DocumentReference.toJSON()](./firestore_.documentreference.md#documentreferencetojson). | | [fromJSON(firestore, json, converter)](./firestore_.documentreference.md#documentreferencefromjson) | static | Builds a DocumentReference instance from a JSON object created by [DocumentReference.toJSON()](./firestore_.documentreference.md#documentreferencetojson). | | [toJSON()](./firestore_.documentreference.md#documentreferencetojson) | | Returns a JSON-serializable representation of this DocumentReference instance. | | [withConverter(converter)](./firestore_.documentreference.md#documentreferencewithconverter) | | Applies a custom data converter to this DocumentReference, allowing you to use your own custom model objects with Firestore. When you call [setDoc()](./firestore_lite.md#setdoc_ee215ad), [getDoc()](./firestore_lite.md#getdoc_4569087), etc. with the returned DocumentReference instance, the provided converter will convert between Firestore data of type NewDbModelType and your custom type NewAppModelType. | @@ -105,16 +106,39 @@ Builds a `DocumentReference` instance from a JSON object created by [DocumentRef Signature: ```typescript -static fromJSON(firestore: Firestore, json: object, converter?: FirestoreDataConverter): DocumentReference; +static fromJSON(firestore: Firestore, json: object): DocumentReference; ``` #### Parameters | Parameter | Type | Description | | --- | --- | --- | -| firestore | [Firestore](./firestore_.firestore.md#firestore_class) | | +| firestore | [Firestore](./firestore_.firestore.md#firestore_class) | The [Firestore](./firestore_.firestore.md#firestore_class) instance the snapshot should be loaded for. | | json | object | a JSON object represention of a DocumentReference instance | -| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<NewAppModelType, NewDbModelType> | | + +Returns: + +[DocumentReference](./firestore_.documentreference.md#documentreference_class) + +an instance of [DocumentReference](./firestore_.documentreference.md#documentreference_class) if the JSON object could be parsed. Throws a [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class) if an error occurs. + +## DocumentReference.fromJSON() + +Builds a `DocumentReference` instance from a JSON object created by [DocumentReference.toJSON()](./firestore_.documentreference.md#documentreferencetojson). + +Signature: + +```typescript +static fromJSON(firestore: Firestore, json: object, converter: FirestoreDataConverter): DocumentReference; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firestore | [Firestore](./firestore_.firestore.md#firestore_class) | The [Firestore](./firestore_.firestore.md#firestore_class) instance the snapshot should be loaded for. | +| json | object | a JSON object represention of a DocumentReference instance | +| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<NewAppModelType, NewDbModelType> | Converts objects to and from Firestore. | Returns: diff --git a/docs-devsite/firestore_.documentsnapshot.md b/docs-devsite/firestore_.documentsnapshot.md index 8c4825593dc..3281873c525 100644 --- a/docs-devsite/firestore_.documentsnapshot.md +++ b/docs-devsite/firestore_.documentsnapshot.md @@ -41,7 +41,7 @@ export declare class DocumentSnapshotObject. Returns undefined if the document doesn't exist.By default, serverTimestamp() values that have not yet been set to their final value will be returned as null. You can override this by passing an options object. | | [exists()](./firestore_.documentsnapshot.md#documentsnapshotexists) | | Returns whether or not the data exists. True if the document exists. | | [get(fieldPath, options)](./firestore_.documentsnapshot.md#documentsnapshotget) | | Retrieves the field specified by fieldPath. Returns undefined if the document or field doesn't exist.By default, a serverTimestamp() that has not yet been set to its final value will be returned as null. You can override this by passing an options object. | -| [toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) | | | +| [toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) | | Returns a JSON-serializable representation of this DocumentSnapshot instance. | ## DocumentSnapshot.(constructor) @@ -147,6 +147,8 @@ The data at the specified field location or undefined if no such field exists in ## DocumentSnapshot.toJSON() +Returns a JSON-serializable representation of this `DocumentSnapshot` instance. + Signature: ```typescript @@ -156,3 +158,5 @@ toJSON(): object; object +a JSON representation of this object. + diff --git a/docs-devsite/firestore_.md b/docs-devsite/firestore_.md index 685958d364c..ac00fe33aaa 100644 --- a/docs-devsite/firestore_.md +++ b/docs-devsite/firestore_.md @@ -19,6 +19,11 @@ https://github.com/firebase/firebase-js-sdk | [getFirestore(app)](./firestore_.md#getfirestore_cf608e1) | Returns the existing default [Firestore](./firestore_.firestore.md#firestore_class) instance that is associated with the provided [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface). If no instance exists, initializes a new instance with default settings. | | [getFirestore(app, databaseId)](./firestore_.md#getfirestore_48de6cb) | (Public Preview) Returns the existing named [Firestore](./firestore_.firestore.md#firestore_class) instance that is associated with the provided [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface). If no instance exists, initializes a new instance with default settings. | | [initializeFirestore(app, settings, databaseId)](./firestore_.md#initializefirestore_fc7d200) | Initializes a new instance of [Firestore](./firestore_.firestore.md#firestore_class) with the provided settings. Can only be called before any other function, including [getFirestore()](./firestore_.md#getfirestore). If the custom settings are empty, this function is equivalent to calling [getFirestore()](./firestore_.md#getfirestore). | +| function(db, ...) | +| [documentSnapshotFromJSON(db, json)](./firestore_.md#documentsnapshotfromjson_a318ff2) | Builds a DocumentSnapshot instance from a JSON object created by [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). | +| [documentSnapshotFromJSON(db, json, converter)](./firestore_.md#documentsnapshotfromjson_ddb369d) | Builds a DocumentSnapshot instance from a JSON object created by [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). | +| [querySnapshotFromJSON(db, json)](./firestore_.md#querysnapshotfromjson_a318ff2) | Builds a QuerySnapshot instance from a JSON object created by [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). | +| [querySnapshotFromJSON(db, json, converter)](./firestore_.md#querysnapshotfromjson_ddb369d) | Builds a QuerySnapshot instance from a JSON object created by [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). | | function(firestore, ...) | | [clearIndexedDbPersistence(firestore)](./firestore_.md#clearindexeddbpersistence_231a8e0) | Clears the persistent storage. This includes pending writes and cached documents.Must be called while the [Firestore](./firestore_.firestore.md#firestore_class) instance is not started (after the app is terminated or when the app is first initialized). On startup, this function must be called before other functions (other than [initializeFirestore()](./firestore_.md#initializefirestore_fc7d200) or [getFirestore()](./firestore_.md#getfirestore))). If the [Firestore](./firestore_.firestore.md#firestore_class) instance is still running, the promise will be rejected with the error code of failed-precondition.Note: clearIndexedDbPersistence() is primarily intended to help write reliable tests that use Cloud Firestore. It uses an efficient mechanism for dropping existing data but does not attempt to securely overwrite or otherwise make cached data unrecoverable. For applications that are sensitive to the disclosure of cached data in between user sessions, we strongly recommend not enabling persistence at all. | | [collection(firestore, path, pathSegments)](./firestore_.md#collection_1eb4c23) | Gets a CollectionReference instance that refers to the collection at the specified absolute path. | @@ -32,14 +37,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_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. | +| [onSnapshotResume(firestore, snapshotJson, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshotresume_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. | +| [onSnapshotResume(firestore, snapshotJson, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshotresume_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. | +| [onSnapshotResume(firestore, snapshotJson, options, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshotresume_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. | +| [onSnapshotResume(firestore, snapshotJson, options, onNext, onError, onCompletion, converter)](./firestore_.md#onsnapshotresume_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. | +| [onSnapshotResume(firestore, snapshotJson, observer, converter)](./firestore_.md#onsnapshotresume_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. | +| [onSnapshotResume(firestore, snapshotJson, observer, converter)](./firestore_.md#onsnapshotresume_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. | +| [onSnapshotResume(firestore, snapshotJson, options, observer, converter)](./firestore_.md#onsnapshotresume_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. | +| [onSnapshotResume(firestore, snapshotJson, options, observer, converter)](./firestore_.md#onsnapshotresume_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. | | [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. | @@ -306,6 +311,102 @@ export declare function initializeFirestore(app: FirebaseApp, settings: Firestor A newly initialized [Firestore](./firestore_.firestore.md#firestore_class) instance. +## function(db, ...) + +### documentSnapshotFromJSON(db, json) {:#documentsnapshotfromjson_a318ff2} + +Builds a `DocumentSnapshot` instance from a JSON object created by [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). + +Signature: + +```typescript +export declare function documentSnapshotFromJSON(db: Firestore, json: object): DocumentSnapshot; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| db | [Firestore](./firestore_.firestore.md#firestore_class) | | +| json | object | a JSON object represention of a DocumentSnapshot instance. | + +Returns: + +[DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class) + +an instance of [DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class) if the JSON object could be parsed. Throws a [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class) if an error occurs. + +### documentSnapshotFromJSON(db, json, converter) {:#documentsnapshotfromjson_ddb369d} + +Builds a `DocumentSnapshot` instance from a JSON object created by [DocumentSnapshot.toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson). + +Signature: + +```typescript +export declare function documentSnapshotFromJSON(db: Firestore, json: object, converter: FirestoreDataConverter): DocumentSnapshot; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| db | [Firestore](./firestore_.firestore.md#firestore_class) | | +| json | object | a JSON object represention of a DocumentSnapshot instance. | +| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<AppModelType, DbModelType> | Converts objects to and from Firestore. | + +Returns: + +[DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class)<AppModelType, DbModelType> + +an instance of [DocumentSnapshot](./firestore_.documentsnapshot.md#documentsnapshot_class) if the JSON object could be parsed. Throws a [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class) if an error occurs. + +### querySnapshotFromJSON(db, json) {:#querysnapshotfromjson_a318ff2} + +Builds a `QuerySnapshot` instance from a JSON object created by [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). + +Signature: + +```typescript +export declare function querySnapshotFromJSON(db: Firestore, json: object): QuerySnapshot; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| db | [Firestore](./firestore_.firestore.md#firestore_class) | | +| json | object | a JSON object represention of a QuerySnapshot instance. | + +Returns: + +[QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class) + +an instance of [QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class) if the JSON object could be parsed. Throws a [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class) if an error occurs. + +### querySnapshotFromJSON(db, json, converter) {:#querysnapshotfromjson_ddb369d} + +Builds a `QuerySnapshot` instance from a JSON object created by [QuerySnapshot.toJSON()](./firestore_.querysnapshot.md#querysnapshottojson). + +Signature: + +```typescript +export declare function querySnapshotFromJSON(db: Firestore, json: object, converter: FirestoreDataConverter): QuerySnapshot; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| db | [Firestore](./firestore_.firestore.md#firestore_class) | | +| json | object | a JSON object represention of a QuerySnapshot instance. | +| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md#firestoredataconverter_interface)<AppModelType, DbModelType> | Converts objects to and from Firestore. | + +Returns: + +[QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class)<AppModelType, DbModelType> + +an instance of [QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class) if the JSON object could be parsed. Throws a [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class) if an error occurs. + ## function(firestore, ...) ### clearIndexedDbPersistence(firestore) {:#clearindexeddbpersistence_231a8e0} @@ -625,16 +726,45 @@ 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_712362a} +### onSnapshotResume(firestore, snapshotJson, onNext, onError, onCompletion, converter) {:#onsnapshotresume_7c84f5e} -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. +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 onSnapshotResume(firestore: Firestore, snapshotJson: object, 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. | +| 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. | +| 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. + +### onSnapshotResume(firestore, snapshotJson, onNext, onError, onCompletion, converter) {:#onsnapshotresume_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. Signature: ```typescript -export declare function onSnapshot(firestore: Firestore, snapshotJson: object, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +export declare function onSnapshotResume(firestore: Firestore, snapshotJson: object, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; ``` #### Parameters @@ -644,7 +774,7 @@ export declare function onSnapshot. | | 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. | +| 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. | @@ -654,16 +784,16 @@ export declare function onSnapshot. 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, snapshotJson: object, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +export declare function onSnapshotResume(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; ``` #### Parameters @@ -684,16 +814,16 @@ export declare function onSnapshot. 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, snapshotJson: object, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; +export declare function onSnapshotResume(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void, converter?: FirestoreDataConverter): Unsubscribe; ``` #### Parameters @@ -714,16 +844,16 @@ export declare function onSnapshot. 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, snapshotJson: object, observer: { +export declare function onSnapshotResume(firestore: Firestore, snapshotJson: object, observer: { next: (snapshot: QuerySnapshot) => void; error?: (error: FirestoreError) => void; complete?: () => void; @@ -745,7 +875,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, snapshotJson: object, observer: { +export declare function onSnapshotResume(firestore: Firestore, snapshotJson: object, observer: { next: (snapshot: DocumentSnapshot) => void; error?: (error: FirestoreError) => void; complete?: () => void; @@ -776,7 +906,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, observer: { +export declare function onSnapshotResume(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, observer: { next: (snapshot: QuerySnapshot) => void; error?: (error: FirestoreError) => void; complete?: () => void; @@ -808,7 +938,7 @@ export declare function onSnapshotSignature: ```typescript -export declare function onSnapshot(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, observer: { +export declare function onSnapshotResume(firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, observer: { next: (snapshot: DocumentSnapshot) => void; error?: (error: FirestoreError) => void; complete?: () => void; @@ -840,35 +970,6 @@ export declare function onSnapshotSignature: - -```typescript -export declare function onSnapshot(firestore: Firestore, snapshotJson: object, 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. | -| 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. | -| 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..78f1ac3f23b 100644 --- a/docs-devsite/firestore_.querysnapshot.md +++ b/docs-devsite/firestore_.querysnapshot.md @@ -34,7 +34,7 @@ export declare class QuerySnapshotQuerySnapshot. | -| [toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) | | | +| [toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) | | Returns a JSON-serializable representation of this QuerySnapshot instance. | ## QuerySnapshot.docs @@ -129,6 +129,8 @@ void ## QuerySnapshot.toJSON() +Returns a JSON-serializable representation of this `QuerySnapshot` instance. + Signature: ```typescript @@ -138,3 +140,5 @@ toJSON(): object; object +a JSON representation of this object. + diff --git a/docs-devsite/firestore_.timestamp.md b/docs-devsite/firestore_.timestamp.md index dd902aeba75..9d7282e5a2a 100644 --- a/docs-devsite/firestore_.timestamp.md +++ b/docs-devsite/firestore_.timestamp.md @@ -40,7 +40,7 @@ export declare class Timestamp | Method | Modifiers | Description | | --- | --- | --- | | [fromDate(date)](./firestore_.timestamp.md#timestampfromdate) | static | Creates a new timestamp from the given date. | -| [fromJSON(json)](./firestore_.timestamp.md#timestampfromjson) | static | Builds a Timestamp instance from a JSON serialized version of Bytes. | +| [fromJSON(json)](./firestore_.timestamp.md#timestampfromjson) | static | Builds a Timestamp instance from a JSON object created by [Timestamp.toJSON()](./firestore_.timestamp.md#timestamptojson). | | [fromMillis(milliseconds)](./firestore_.timestamp.md#timestampfrommillis) | static | Creates a new timestamp from the given number of milliseconds. | | [isEqual(other)](./firestore_.timestamp.md#timestampisequal) | | Returns true if this Timestamp is equal to the provided one. | | [now()](./firestore_.timestamp.md#timestampnow) | static | Creates a new timestamp with the current date, with millisecond precision. | @@ -113,7 +113,7 @@ A new `Timestamp` representing the same point in time as the given date. ## Timestamp.fromJSON() -Builds a `Timestamp` instance from a JSON serialized version of `Bytes`. +Builds a `Timestamp` instance from a JSON object created by [Timestamp.toJSON()](./firestore_.timestamp.md#timestamptojson). Signature: diff --git a/docs-devsite/firestore_.vectorvalue.md b/docs-devsite/firestore_.vectorvalue.md index 265251142ce..1fc4e2b35ab 100644 --- a/docs-devsite/firestore_.vectorvalue.md +++ b/docs-devsite/firestore_.vectorvalue.md @@ -43,7 +43,7 @@ static fromJSON(json: object): VectorValue; | Parameter | Type | Description | | --- | --- | --- | -| json | object | a JSON object represention of a VectorValue instance | +| json | object | a JSON object represention of a VectorValue instance. | Returns: diff --git a/docs-devsite/firestore_lite.documentreference.md b/docs-devsite/firestore_lite.documentreference.md index 4dd970afb7e..2a09e2e5964 100644 --- a/docs-devsite/firestore_lite.documentreference.md +++ b/docs-devsite/firestore_lite.documentreference.md @@ -33,6 +33,7 @@ export declare class DocumentReferencestatic | Builds a DocumentReference instance from a JSON object created by [DocumentReference.toJSON()](./firestore_.documentreference.md#documentreferencetojson). | | [fromJSON(firestore, json, converter)](./firestore_lite.documentreference.md#documentreferencefromjson) | static | Builds a DocumentReference instance from a JSON object created by [DocumentReference.toJSON()](./firestore_.documentreference.md#documentreferencetojson). | | [toJSON()](./firestore_lite.documentreference.md#documentreferencetojson) | | Returns a JSON-serializable representation of this DocumentReference instance. | | [withConverter(converter)](./firestore_lite.documentreference.md#documentreferencewithconverter) | | Applies a custom data converter to this DocumentReference, allowing you to use your own custom model objects with Firestore. When you call [setDoc()](./firestore_lite.md#setdoc_ee215ad), [getDoc()](./firestore_lite.md#getdoc_4569087), etc. with the returned DocumentReference instance, the provided converter will convert between Firestore data of type NewDbModelType and your custom type NewAppModelType. | @@ -105,16 +106,39 @@ Builds a `DocumentReference` instance from a JSON object created by [DocumentRef Signature: ```typescript -static fromJSON(firestore: Firestore, json: object, converter?: FirestoreDataConverter): DocumentReference; +static fromJSON(firestore: Firestore, json: object): DocumentReference; ``` #### Parameters | Parameter | Type | Description | | --- | --- | --- | -| firestore | [Firestore](./firestore_lite.firestore.md#firestore_class) | | +| firestore | [Firestore](./firestore_lite.firestore.md#firestore_class) | The [Firestore](./firestore_.firestore.md#firestore_class) instance the snapshot should be loaded for. | | json | object | a JSON object represention of a DocumentReference instance | -| converter | [FirestoreDataConverter](./firestore_lite.firestoredataconverter.md#firestoredataconverter_interface)<NewAppModelType, NewDbModelType> | | + +Returns: + +[DocumentReference](./firestore_lite.documentreference.md#documentreference_class) + +an instance of [DocumentReference](./firestore_.documentreference.md#documentreference_class) if the JSON object could be parsed. Throws a [FirestoreError](./firestore_.firestoreerror.md#firestoreerror_class) if an error occurs. + +## DocumentReference.fromJSON() + +Builds a `DocumentReference` instance from a JSON object created by [DocumentReference.toJSON()](./firestore_.documentreference.md#documentreferencetojson). + +Signature: + +```typescript +static fromJSON(firestore: Firestore, json: object, converter: FirestoreDataConverter): DocumentReference; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| firestore | [Firestore](./firestore_lite.firestore.md#firestore_class) | The [Firestore](./firestore_.firestore.md#firestore_class) instance the snapshot should be loaded for. | +| json | object | a JSON object represention of a DocumentReference instance | +| converter | [FirestoreDataConverter](./firestore_lite.firestoredataconverter.md#firestoredataconverter_interface)<NewAppModelType, NewDbModelType> | Converts objects to and from Firestore. | Returns: diff --git a/docs-devsite/firestore_lite.timestamp.md b/docs-devsite/firestore_lite.timestamp.md index 5dbbbfda0bc..0fb35ada682 100644 --- a/docs-devsite/firestore_lite.timestamp.md +++ b/docs-devsite/firestore_lite.timestamp.md @@ -40,7 +40,7 @@ export declare class Timestamp | Method | Modifiers | Description | | --- | --- | --- | | [fromDate(date)](./firestore_lite.timestamp.md#timestampfromdate) | static | Creates a new timestamp from the given date. | -| [fromJSON(json)](./firestore_lite.timestamp.md#timestampfromjson) | static | Builds a Timestamp instance from a JSON serialized version of Bytes. | +| [fromJSON(json)](./firestore_lite.timestamp.md#timestampfromjson) | static | Builds a Timestamp instance from a JSON object created by [Timestamp.toJSON()](./firestore_.timestamp.md#timestamptojson). | | [fromMillis(milliseconds)](./firestore_lite.timestamp.md#timestampfrommillis) | static | Creates a new timestamp from the given number of milliseconds. | | [isEqual(other)](./firestore_lite.timestamp.md#timestampisequal) | | Returns true if this Timestamp is equal to the provided one. | | [now()](./firestore_lite.timestamp.md#timestampnow) | static | Creates a new timestamp with the current date, with millisecond precision. | @@ -113,7 +113,7 @@ A new `Timestamp` representing the same point in time as the given date. ## Timestamp.fromJSON() -Builds a `Timestamp` instance from a JSON serialized version of `Bytes`. +Builds a `Timestamp` instance from a JSON object created by [Timestamp.toJSON()](./firestore_.timestamp.md#timestamptojson). Signature: diff --git a/docs-devsite/firestore_lite.vectorvalue.md b/docs-devsite/firestore_lite.vectorvalue.md index 99b27bf8553..17c18e4c4ed 100644 --- a/docs-devsite/firestore_lite.vectorvalue.md +++ b/docs-devsite/firestore_lite.vectorvalue.md @@ -43,7 +43,7 @@ static fromJSON(json: object): VectorValue; | Parameter | Type | Description | | --- | --- | --- | -| json | object | a JSON object represention of a VectorValue instance | +| json | object | a JSON object represention of a VectorValue instance. | Returns: diff --git a/packages/firestore/src/api.ts b/packages/firestore/src/api.ts index ea969c6b94c..d05f032a910 100644 --- a/packages/firestore/src/api.ts +++ b/packages/firestore/src/api.ts @@ -89,9 +89,11 @@ export { DocumentChange, DocumentChangeType, DocumentSnapshot, + documentSnapshotFromJSON, FirestoreDataConverter, QueryDocumentSnapshot, QuerySnapshot, + querySnapshotFromJSON, snapshotEqual, SnapshotMetadata, SnapshotOptions @@ -161,6 +163,7 @@ export { getDocsFromServer, onSnapshot, onSnapshotsInSync, + onSnapshotResume, setDoc, updateDoc } from './api/reference_impl'; diff --git a/packages/firestore/src/api/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts index b920c479d0b..832d80c041a 100644 --- a/packages/firestore/src/api/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -659,6 +659,90 @@ export function onSnapshot( onError?: (error: FirestoreError) => void, onCompletion?: () => void ): Unsubscribe; +export function onSnapshot( + reference: + | Query + | DocumentReference, + ...args: unknown[] +): Unsubscribe { + // 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; + } + + 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); + } + + 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 { + 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 + }; + + validateHasExplicitOrderByForLimitToLast(reference._query); + } + + const client = ensureFirestoreConfigured(firestore); + return firestoreClientListen( + client, + internalQuery, + internalOptions, + observer + ); +} /** * Attaches a listener for `QuerySnapshot` events based on data generated by invoking @@ -679,7 +763,10 @@ export function onSnapshot( * listener is invoked. * @returns An unsubscribe function that can be called to cancel the snapshot listener. */ -export function onSnapshot( +export function onSnapshotResume< + AppModelType, + DbModelType extends DocumentData +>( firestore: Firestore, snapshotJson: object, onNext: (snapshot: QuerySnapshot) => void, @@ -689,7 +776,7 @@ export function onSnapshot( ): 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 + * {@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. * @@ -699,7 +786,7 @@ export function onSnapshot( * @param firestore - The {@link Firestore} instance to enable persistence for. * @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 + * @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. @@ -707,7 +794,10 @@ export function onSnapshot( * listener is invoked. * @returns An unsubscribe function that can be called to cancel the snapshot listener. */ -export function onSnapshot( +export function onSnapshotResume< + AppModelType, + DbModelType extends DocumentData +>( firestore: Firestore, snapshotJson: object, onNext: (snapshot: DocumentSnapshot) => void, @@ -717,7 +807,7 @@ export function onSnapshot( ): Unsubscribe; /** * 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 + * {@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. * @@ -735,7 +825,10 @@ export function onSnapshot( * listener is invoked. * @returns An unsubscribe function that can be called to cancel the snapshot listener. */ -export function onSnapshot( +export function onSnapshotResume< + AppModelType, + DbModelType extends DocumentData +>( firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, @@ -746,7 +839,7 @@ export function onSnapshot( ): 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 + * {@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. * @@ -765,7 +858,10 @@ export function onSnapshot( * @returns An unsubscribe function that can be called to cancel * the snapshot listener. */ -export function onSnapshot( +export function onSnapshotResume< + AppModelType, + DbModelType extends DocumentData +>( firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, @@ -774,10 +870,9 @@ export function onSnapshot( 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 + * {@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. * @@ -792,7 +887,10 @@ export function onSnapshot( * @returns An unsubscribe function that can be called to cancel * the snapshot listener. */ -export function onSnapshot( +export function onSnapshotResume< + AppModelType, + DbModelType extends DocumentData +>( firestore: Firestore, snapshotJson: object, observer: { @@ -819,7 +917,10 @@ export function onSnapshot( * @returns An unsubscribe function that can be called to cancel * the snapshot listener. */ -export function onSnapshot( +export function onSnapshotResume< + AppModelType, + DbModelType extends DocumentData +>( firestore: Firestore, snapshotJson: object, observer: { @@ -847,7 +948,10 @@ export function onSnapshot( * @returns An unsubscribe function that can be called to cancel * the snapshot listener. */ -export function onSnapshot( +export function onSnapshotResume< + AppModelType, + DbModelType extends DocumentData +>( firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, @@ -875,7 +979,10 @@ export function onSnapshot( * listener is invoked. * @returns An unsubscribe function that can be called to cancel the snapshot listener. */ -export function onSnapshot( +export function onSnapshotResume< + AppModelType, + DbModelType extends DocumentData +>( firestore: Firestore, snapshotJson: object, options: SnapshotListenOptions, @@ -886,94 +993,89 @@ export function onSnapshot( }, converter?: FirestoreDataConverter ): Unsubscribe; -export function onSnapshot( - reference: - | Query - | DocumentReference - | Firestore, - ...args: unknown[] -): Unsubscribe { - if (reference instanceof Firestore) { - return onSnapshotBundle(reference as Firestore, ...args); - } - - // 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; +export function onSnapshotResume< + AppModelType, + DbModelType extends DocumentData +>(reference: Firestore, snapshotJson: object, ...args: unknown[]): Unsubscribe { + const db = getModularInstance(reference); + const json = normalizeSnapshotJsonFields(snapshotJson); + if (json.error) { + throw new FirestoreError(Code.INVALID_ARGUMENT, json.error); } - - 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); + let curArg = 0; + let options: SnapshotListenOptions | undefined = undefined; + if (typeof args[curArg] === 'object' && !isPartialObserver(args[curArg])) { + options = args[curArg++] as SnapshotListenOptions; } - 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 - }; + 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[curArg])) { + const userObserver = args[curArg++] as PartialObserver< + QuerySnapshot + >; + observer = { + next: userObserver.next!, + error: userObserver.error, + complete: userObserver.complete + }; + } else { + 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[curArg])) { + const userObserver = args[curArg++] as PartialObserver< + DocumentSnapshot + >; + observer = { + next: userObserver.next!, + error: userObserver.error, + complete: userObserver.complete + }; + } else { + 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 { - 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 - }; - - validateHasExplicitOrderByForLimitToLast(reference._query); + throw new FirestoreError( + Code.INVALID_ARGUMENT, + `unsupported bundle source: ${json.bundleSource}` + ); } - - const client = ensureFirestoreConfigured(firestore); - return firestoreClientListen( - client, - internalQuery, - internalOptions, - observer - ); } // TODO(firestorexp): Make sure these overloads are tested via the Firestore @@ -1075,105 +1177,6 @@ function convertToDocSnapshot( ); } -/** - * 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[] -): Unsubscribe { - const db = getModularInstance(reference); - let curArg = 0; - 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 (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])) { - const userObserver = args[curArg++] as PartialObserver< - QuerySnapshot - >; - observer = { - next: userObserver.next!, - error: userObserver.error, - complete: userObserver.complete - }; - } else { - observer = { - next: args[curArg++] as ( - snapshot: QuerySnapshot - ) => void, - error: args[curArg++] as (error: FirestoreError) => void, - complete: args[curArg++] as () => void - }; - } - return onSnapshotQuerySnapshotBundle( - db, - snapshotJson, - options, - observer!, - args[curArg] as FirestoreDataConverter - ); - } else if (snapshotJson.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 - >; - observer = { - next: userObserver.next!, - error: userObserver.error, - complete: userObserver.complete - }; - } else { - observer = { - next: args[curArg++] as ( - snapshot: DocumentSnapshot - ) => void, - error: args[curArg++] as (error: FirestoreError) => void, - complete: args[curArg++] as () => void - }; - } - return onSnapshotDocumentSnapshotBundle( - db, - snapshotJson, - options, - observer!, - args[curArg] as FirestoreDataConverter - ); - } else { - throw new FirestoreError( - Code.INVALID_ARGUMENT, - `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 diff --git a/packages/firestore/src/api/snapshot.ts b/packages/firestore/src/api/snapshot.ts index e6a66b509d1..b451aabfce2 100644 --- a/packages/firestore/src/api/snapshot.ts +++ b/packages/firestore/src/api/snapshot.ts @@ -15,6 +15,8 @@ * limitations under the License. */ +import { BundleLoader } from '../core/bundle_impl'; +import { createBundleReaderSync } from '../core/firestore_client'; import { newQueryComparator } from '../core/query'; import { ChangeType, ViewSnapshot } from '../core/view_snapshot'; import { FieldPath } from '../lite-api/field_path'; @@ -26,6 +28,7 @@ import { SetOptions, WithFieldValue } from '../lite-api/reference'; +import { LiteUserDataWriter } from '../lite-api/reference_impl'; import { DocumentSnapshot as LiteDocumentSnapshot, fieldPathFromArgument, @@ -33,8 +36,14 @@ import { } from '../lite-api/snapshot'; import { UntypedFirestoreDataConverter } from '../lite-api/user_data_reader'; import { AbstractUserDataWriter } from '../lite-api/user_data_writer'; +import { fromBundledQuery } from '../local/local_serializer'; +import { documentKeySet } from '../model/collections'; import { Document } from '../model/document'; import { DocumentKey } from '../model/document_key'; +import { DocumentSet } from '../model/document_set'; +import { ResourcePath } from '../model/path'; +import { newSerializer } from '../platform/serializer'; +import { fromDocument } from '../remote/serializer'; import { debugAssert, fail } from '../util/assert'; import { BundleBuilder, @@ -42,6 +51,9 @@ import { QuerySnapshotBundleData } from '../util/bundle_builder_impl'; import { Code, FirestoreError } from '../util/error'; +// API extractor fails importing 'property' unless we also explicitly import 'Property'. +// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-imports-ts +import { Property, property, validateJSON } from '../util/json_validation'; import { AutoId } from '../util/misc'; import { Firestore } from './database'; @@ -503,10 +515,24 @@ export class DocumentSnapshot< return undefined; } + static _jsonSchemaVersion: string = 'firestore/documentSnapshot/1.0'; + static _jsonSchema = { + type: property('string', DocumentSnapshot._jsonSchemaVersion), + bundleSource: property('string', 'DocumentSnapshot'), + bundleName: property('string'), + bundle: property('string') + }; + + /** + * Returns a JSON-serializable representation of this `DocumentSnapshot` instance. + * + * @returns a JSON representation of this object. + */ toJSON(): object { const document = this._document; // eslint-disable-next-line @typescript-eslint/no-explicit-any const result: any = {}; + result['type'] = DocumentSnapshot._jsonSchemaVersion; result['bundle'] = ''; result['bundleSource'] = 'DocumentSnapshot'; result['bundleName'] = this._key.toString(); @@ -545,6 +571,92 @@ export class DocumentSnapshot< } } +/** + * Builds a `DocumentSnapshot` instance from a JSON object created by + * {@link DocumentSnapshot.toJSON}. + * + * @param firestore - The {@link Firestore} instance the snapshot should be loaded for. + * @param json - a JSON object represention of a `DocumentSnapshot` instance. + * @returns an instance of {@link DocumentSnapshot} if the JSON object could be + * parsed. Throws a {@link FirestoreError} if an error occurs. + */ +export function documentSnapshotFromJSON( + db: Firestore, + json: object +): DocumentSnapshot; +/** + * Builds a `DocumentSnapshot` instance from a JSON object created by + * {@link DocumentSnapshot.toJSON}. + * + * @param firestore - The {@link Firestore} instance the snapshot should be loaded for. + * @param json - a JSON object represention of a `DocumentSnapshot` instance. + * @param converter - Converts objects to and from Firestore. + * @returns an instance of {@link DocumentSnapshot} if the JSON object could be + * parsed. Throws a {@link FirestoreError} if an error occurs. + */ +export function documentSnapshotFromJSON< + AppModelType, + DbModelType extends DocumentData = DocumentData +>( + db: Firestore, + json: object, + converter: FirestoreDataConverter +): DocumentSnapshot; +export function documentSnapshotFromJSON< + AppModelType, + DbModelType extends DocumentData = DocumentData +>( + db: Firestore, + json: object, + converter?: FirestoreDataConverter +): DocumentSnapshot { + if (validateJSON(json, DocumentSnapshot._jsonSchema)) { + // Parse the bundle data. + const serializer = newSerializer(db._databaseId); + const bundleReader = createBundleReaderSync(json.bundle, serializer); + const elements = bundleReader.getElements(); + const bundleLoader: BundleLoader = new BundleLoader( + bundleReader.getMetadata(), + serializer + ); + for (const element of elements) { + bundleLoader.addSizedElement(element); + } + + // Ensure that we have the correct number of documents in the bundle. + const bundledDocuments = bundleLoader.documents; + if (bundledDocuments.length !== 1) { + throw new FirestoreError( + Code.INVALID_ARGUMENT, + `Expected bundle data to contain 1 document, but it contains ${bundledDocuments.length} documents.` + ); + } + + // Build out the internal document data. + const document = fromDocument(serializer, bundledDocuments[0].document!); + const documentKey = new DocumentKey( + ResourcePath.fromString(json.bundleName) + ); + + // Return the external facing DocumentSnapshot. + return new DocumentSnapshot( + db, + new LiteUserDataWriter(db), + documentKey, + document, + new SnapshotMetadata( + /* hasPendingWrites= */ false, + /* fromCache= */ false + ), + converter ? converter : null + ); + } + throw new FirestoreError( + Code.INTERNAL, + 'Unexpected error creating DocumentSnapshot from JSON.' + ); +} + /** * A `QueryDocumentSnapshot` contains data read from a document in your * Firestore database as part of a query. The document is guaranteed to exist @@ -699,9 +811,23 @@ export class QuerySnapshot< return this._cachedChanges; } + static _jsonSchemaVersion: string = 'firestore/querySnapshot/1.0'; + static _jsonSchema = { + type: property('string', QuerySnapshot._jsonSchemaVersion), + bundleSource: property('string', 'QuerySnapshot'), + bundleName: property('string'), + bundle: property('string') + }; + + /** + * Returns a JSON-serializable representation of this `QuerySnapshot` instance. + * + * @returns a JSON representation of this object. + */ toJSON(): object { // eslint-disable-next-line @typescript-eslint/no-explicit-any const result: any = {}; + result['type'] = QuerySnapshot._jsonSchemaVersion; result['bundleSource'] = 'QuerySnapshot'; result['bundleName'] = AutoId.newId(); @@ -749,6 +875,105 @@ export class QuerySnapshot< } } +/** + * Builds a `QuerySnapshot` instance from a JSON object created by + * {@link QuerySnapshot.toJSON}. + * + * @param firestore - The {@link Firestore} instance the snapshot should be loaded for. + * @param json - a JSON object represention of a `QuerySnapshot` instance. + * @returns an instance of {@link QuerySnapshot} if the JSON object could be + * parsed. Throws a {@link FirestoreError} if an error occurs. + */ +export function querySnapshotFromJSON( + db: Firestore, + json: object +): QuerySnapshot; +/** + * Builds a `QuerySnapshot` instance from a JSON object created by + * {@link QuerySnapshot.toJSON}. + * + * @param firestore - The {@link Firestore} instance the snapshot should be loaded for. + * @param json - a JSON object represention of a `QuerySnapshot` instance. + * @param converter - Converts objects to and from Firestore. + * @returns an instance of {@link QuerySnapshot} if the JSON object could be + * parsed. Throws a {@link FirestoreError} if an error occurs. + */ +export function querySnapshotFromJSON< + AppModelType, + DbModelType extends DocumentData = DocumentData +>( + db: Firestore, + json: object, + converter: FirestoreDataConverter +): QuerySnapshot; +export function querySnapshotFromJSON< + AppModelType, + DbModelType extends DocumentData +>( + db: Firestore, + json: object, + converter?: FirestoreDataConverter +): QuerySnapshot { + if (validateJSON(json, QuerySnapshot._jsonSchema)) { + // Parse the bundle data. + const serializer = newSerializer(db._databaseId); + const bundleReader = createBundleReaderSync(json.bundle, serializer); + const elements = bundleReader.getElements(); + const bundleLoader: BundleLoader = new BundleLoader( + bundleReader.getMetadata(), + serializer + ); + for (const element of elements) { + bundleLoader.addSizedElement(element); + } + + if (bundleLoader.queries.length !== 1) { + throw new FirestoreError( + Code.INVALID_ARGUMENT, + `Snapshot data expected 1 query but found ${bundleLoader.queries.length} queries.` + ); + } + + // Create an internal Query object from the named query in the budnle. + const query = fromBundledQuery(bundleLoader.queries[0].bundledQuery!); + + // Construct the arrays of document data for the query. + const bundledDocuments = bundleLoader.documents; + let documentSet = new DocumentSet(); + bundledDocuments.map(bundledDocument => { + const document = fromDocument(serializer, bundledDocument.document!); + documentSet = documentSet.add(document); + }); + // Create a view snapshot of the query and documents. + const viewSnapshot = ViewSnapshot.fromInitialDocuments( + query, + documentSet, + documentKeySet() /* Zero mutated keys signifies no pending writes. */, + /* fromCache= */ false, + /* hasCachedResults= */ false + ); + + // Create an external Query object, required to construct the QuerySnapshot. + const externalQuery = new Query( + db, + converter ? converter : null, + query + ); + + // Return a new QuerySnapshot with all of the collected data. + return new QuerySnapshot( + db, + new LiteUserDataWriter(db), + externalQuery, + viewSnapshot + ); + } + throw new FirestoreError( + Code.INTERNAL, + 'Unexpected error creating QuerySnapshot from JSON.' + ); +} + /** Calculates the array of `DocumentChange`s for a given `ViewSnapshot`. */ export function changesFromSnapshot< AppModelType, diff --git a/packages/firestore/src/core/bundle_impl.ts b/packages/firestore/src/core/bundle_impl.ts index 9a42e43261f..b91933f1349 100644 --- a/packages/firestore/src/core/bundle_impl.ts +++ b/packages/firestore/src/core/bundle_impl.ts @@ -82,27 +82,42 @@ export class BundleConverterImpl implements BundleConverter { } /** - * A class to process the elements from a bundle, load them into local + * A class to process the elements from a bundle, and optionally load them into local * storage and provide progress update while loading. */ export class BundleLoader { /** The current progress of loading */ private progress: LoadBundleTaskProgress; /** Batched queries to be saved into storage */ - private queries: ProtoNamedQuery[] = []; + private _queries: ProtoNamedQuery[] = []; /** Batched documents to be saved into storage */ - private documents: BundledDocuments = []; + private _documents: BundledDocuments = []; /** The collection groups affected by this bundle. */ private collectionGroups = new Set(); constructor( private bundleMetadata: ProtoBundleMetadata, - private localStore: LocalStore, private serializer: JsonProtoSerializer ) { this.progress = bundleInitialProgress(bundleMetadata); } + /** + * Returns the named queries that have been parsed from the SizeBundleElements added by + * calling {@link adSizedElement}. + */ + get queries(): ProtoNamedQuery[] { + return this._queries; + } + + /** + * Returns the BundledDocuments that have been parsed from the SizeBundleElements added by + * calling {@link addSizedElement}. + */ + get documents(): BundledDocuments { + return this._documents; + } + /** * Adds an element from the bundle to the loader. * @@ -117,9 +132,9 @@ export class BundleLoader { let documentsLoaded = this.progress.documentsLoaded; if (element.payload.namedQuery) { - this.queries.push(element.payload.namedQuery); + this._queries.push(element.payload.namedQuery); } else if (element.payload.documentMetadata) { - this.documents.push({ metadata: element.payload.documentMetadata }); + this._documents.push({ metadata: element.payload.documentMetadata }); if (!element.payload.documentMetadata.exists) { ++documentsLoaded; } @@ -133,12 +148,12 @@ export class BundleLoader { this.collectionGroups.add(path.get(path.length - 2)); } else if (element.payload.document) { debugAssert( - this.documents.length > 0 && - this.documents[this.documents.length - 1].metadata.name === + this._documents.length > 0 && + this._documents[this._documents.length - 1].metadata.name === element.payload.document.name, 'The document being added does not match the stored metadata.' ); - this.documents[this.documents.length - 1].document = + this._documents[this._documents.length - 1].document = element.payload.document; ++documentsLoaded; } @@ -176,26 +191,28 @@ export class BundleLoader { /** * Update the progress to 'Success' and return the updated progress. */ - async complete(): Promise { + async completeAndStoreAsync( + localStore: LocalStore + ): Promise { debugAssert( - this.documents[this.documents.length - 1]?.metadata.exists !== true || - !!this.documents[this.documents.length - 1].document, + this._documents[this._documents.length - 1]?.metadata.exists !== true || + !!this._documents[this._documents.length - 1].document, 'Bundled documents end with a document metadata element instead of a document.' ); debugAssert(!!this.bundleMetadata.id, 'Bundle ID must be set.'); const changedDocs = await localStoreApplyBundledDocuments( - this.localStore, + localStore, new BundleConverterImpl(this.serializer), - this.documents, + this._documents, this.bundleMetadata.id! ); const queryDocumentMap = this.getQueryDocumentMapping(this.documents); - for (const q of this.queries) { + for (const q of this._queries) { await localStoreSaveNamedQuery( - this.localStore, + localStore, q, queryDocumentMap.get(q.name!) ); diff --git a/packages/firestore/src/core/firestore_client.ts b/packages/firestore/src/core/firestore_client.ts index e2aa19aaba8..39bb8dd4eba 100644 --- a/packages/firestore/src/core/firestore_client.ts +++ b/packages/firestore/src/core/firestore_client.ts @@ -53,8 +53,9 @@ import { JsonProtoSerializer } from '../remote/serializer'; import { debugAssert } from '../util/assert'; import { AsyncObserver } from '../util/async_observer'; import { AsyncQueue, wrapInUserErrorIfRecoverable } from '../util/async_queue'; -import { BundleReader } from '../util/bundle_reader'; +import { BundleReader, BundleReaderSync } from '../util/bundle_reader'; import { newBundleReader } from '../util/bundle_reader_impl'; +import { newBundleReaderSync } from '../util/bundle_reader_sync_impl'; import { Code, FirestoreError } from '../util/error'; import { logDebug, logWarn } from '../util/log'; import { AutoId } from '../util/misc'; @@ -339,7 +340,7 @@ async function ensureOfflineComponents( return client._offlineComponents!; } -async function ensureOnlineComponents( +export async function ensureOnlineComponents( client: FirestoreClient ): Promise { if (!client._onlineComponents) { @@ -821,6 +822,13 @@ function createBundleReader( return newBundleReader(toByteStreamReader(content), serializer); } +export function createBundleReaderSync( + bundleData: string, + serializer: JsonProtoSerializer +): BundleReaderSync { + return newBundleReaderSync(bundleData, serializer); +} + export function firestoreClientSetIndexConfiguration( client: FirestoreClient, indexes: FieldIndex[] diff --git a/packages/firestore/src/core/sync_engine_impl.ts b/packages/firestore/src/core/sync_engine_impl.ts index 404d4663a47..f00acb5a4ee 100644 --- a/packages/firestore/src/core/sync_engine_impl.ts +++ b/packages/firestore/src/core/sync_engine_impl.ts @@ -1697,11 +1697,7 @@ async function loadBundleImpl( task._updateProgress(bundleInitialProgress(metadata)); - const loader = new BundleLoader( - metadata, - syncEngine.localStore, - reader.serializer - ); + const loader = new BundleLoader(metadata, reader.serializer); let element = await reader.nextElement(); while (element) { debugAssert( @@ -1716,7 +1712,7 @@ async function loadBundleImpl( element = await reader.nextElement(); } - const result = await loader.complete(); + const result = await loader.completeAndStoreAsync(syncEngine.localStore); await syncEngineEmitNewSnapsAndNotifyLocalStore( syncEngine, result.changedDocs, diff --git a/packages/firestore/src/lite-api/reference.ts b/packages/firestore/src/lite-api/reference.ts index ed66eae7b70..6b95e28eb69 100644 --- a/packages/firestore/src/lite-api/reference.ts +++ b/packages/firestore/src/lite-api/reference.ts @@ -304,10 +304,30 @@ export class DocumentReference< * Builds a `DocumentReference` instance from a JSON object created by * {@link DocumentReference.toJSON}. * + * @param firestore - The {@link Firestore} instance the snapshot should be loaded for. * @param json a JSON object represention of a `DocumentReference` instance * @returns an instance of {@link DocumentReference} if the JSON object could be parsed. Throws a * {@link FirestoreError} if an error occurs. */ + static fromJSON(firestore: Firestore, json: object): DocumentReference; + /** + * Builds a `DocumentReference` instance from a JSON object created by + * {@link DocumentReference.toJSON}. + * + * @param firestore - The {@link Firestore} instance the snapshot should be loaded for. + * @param json a JSON object represention of a `DocumentReference` instance + * @param converter - Converts objects to and from Firestore. + * @returns an instance of {@link DocumentReference} if the JSON object could be parsed. Throws a + * {@link FirestoreError} if an error occurs. + */ + static fromJSON< + NewAppModelType = DocumentData, + NewDbModelType extends DocumentData = DocumentData + >( + firestore: Firestore, + json: object, + converter: FirestoreDataConverter + ): DocumentReference; static fromJSON< NewAppModelType = DocumentData, NewDbModelType extends DocumentData = DocumentData diff --git a/packages/firestore/src/lite-api/timestamp.ts b/packages/firestore/src/lite-api/timestamp.ts index 5dde51b0e28..dac20ccc94f 100644 --- a/packages/firestore/src/lite-api/timestamp.ts +++ b/packages/firestore/src/lite-api/timestamp.ts @@ -184,7 +184,9 @@ export class Timestamp { nanoseconds: property('number') }; - /** Returns a JSON-serializable representation of this `Timestamp`. */ + /** + * Returns a JSON-serializable representation of this `Timestamp`. + */ toJSON(): { seconds: number; nanoseconds: number; type: string } { return { type: Timestamp._jsonSchemaVersion, @@ -193,7 +195,9 @@ export class Timestamp { }; } - /** Builds a `Timestamp` instance from a JSON serialized version of `Bytes`. */ + /** + * Builds a `Timestamp` instance from a JSON object created by {@link Timestamp.toJSON}. + */ static fromJSON(json: object): Timestamp { if (validateJSON(json, Timestamp._jsonSchema)) { return new Timestamp(json.seconds, json.nanoseconds); diff --git a/packages/firestore/src/lite-api/vector_value.ts b/packages/firestore/src/lite-api/vector_value.ts index 88bc55ef032..311ec351f0c 100644 --- a/packages/firestore/src/lite-api/vector_value.ts +++ b/packages/firestore/src/lite-api/vector_value.ts @@ -74,7 +74,7 @@ export class VectorValue { /** * Builds a `VectorValue` instance from a JSON object created by {@link VectorValue.toJSON}. * - * @param json a JSON object represention of a `VectorValue` instance + * @param json a JSON object represention of a `VectorValue` instance. * @returns an instance of {@link VectorValue} if the JSON object could be parsed. Throws a * {@link FirestoreError} if an error occurs. */ diff --git a/packages/firestore/src/util/bundle_reader.ts b/packages/firestore/src/util/bundle_reader.ts index 6ebfb2d5e8e..cca1c61a538 100644 --- a/packages/firestore/src/util/bundle_reader.ts +++ b/packages/firestore/src/util/bundle_reader.ts @@ -65,3 +65,24 @@ export interface BundleReader { */ nextElement(): Promise; } + +/** + * A class representing a synchronized bundle reader. + * + * Takes a bundle string buffer, parses the data, and provides accessors to the data contained + * within it. + */ +export interface BundleReaderSync { + serializer: JsonProtoSerializer; + + /** + * Returns the metadata of the bundle. + */ + getMetadata(): BundleMetadata; + + /** + * Returns BundleElements parsed from the bundle. Returns an empty array if no bundle elements + * exist. + */ + getElements(): SizedBundleElement[]; +} diff --git a/packages/firestore/src/util/bundle_reader_sync_impl.ts b/packages/firestore/src/util/bundle_reader_sync_impl.ts new file mode 100644 index 00000000000..9379bb5a5a7 --- /dev/null +++ b/packages/firestore/src/util/bundle_reader_sync_impl.ts @@ -0,0 +1,129 @@ +/** + * @license + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BundleMetadata } from '../protos/firestore_bundle_proto'; +import { JsonProtoSerializer } from '../remote/serializer'; +import { Code, FirestoreError } from '../util/error'; + +import { BundleReaderSync, SizedBundleElement } from './bundle_reader'; + +/** + * A class that can parse a bundle form the string serialization of a bundle. + */ +export class BundleReaderSyncImpl implements BundleReaderSync { + private metadata: BundleMetadata; + private elements: SizedBundleElement[]; + private cursor: number; + constructor( + private bundleData: string, + readonly serializer: JsonProtoSerializer + ) { + this.cursor = 0; + this.elements = []; + + let element = this.nextElement(); + if (element && element.isBundleMetadata()) { + this.metadata = element as BundleMetadata; + } else { + throw new Error(`The first element of the bundle is not a metadata object, it is + ${JSON.stringify(element?.payload)}`); + } + + do { + element = this.nextElement(); + if (element !== null) { + this.elements.push(element); + } + } while (element !== null); + } + + /* Returns the parsed metadata of the bundle. */ + getMetadata(): BundleMetadata { + return this.metadata; + } + + /* Returns the DocumentSnapshot or NamedQuery elements of the bundle. */ + getElements(): SizedBundleElement[] { + return this.elements; + } + + /** + * Parses the next element of the bundle. + * + * @returns a SizedBundleElement representation of the next element in the bundle, or null if + * no more elements exist. + */ + private nextElement(): SizedBundleElement | null { + if (this.cursor === this.bundleData.length) { + return null; + } + const length: number = this.readLength(); + const jsonString = this.readJsonString(length); + return new SizedBundleElement(JSON.parse(jsonString), length); + } + + /** + * Reads from a specified position from the bundleData string, for a specified + * number of bytes. + * + * @param length how many characters to read. + * @returns a string parsed from the bundle. + */ + private readJsonString(length: number): string { + if (this.cursor + length > this.bundleData.length) { + throw new FirestoreError( + Code.INTERNAL, + 'Reached the end of bundle when more is expected.' + ); + } + const result = this.bundleData.slice(this.cursor, (this.cursor += length)); + return result; + } + + /** + * Reads from the current cursor until the first '{'. + * + * @returns A string to integer represention of the parsed value. + * @throws An {@link Error} if the cursor has reached the end of the stream, since lengths + * prefix bundle objects. + */ + private readLength(): number { + const startIndex = this.cursor; + let curIndex = this.cursor; + while (curIndex < this.bundleData.length) { + if (this.bundleData[curIndex] === '{') { + if (curIndex === startIndex) { + throw new Error('First character is a bracket and not a number'); + } + this.cursor = curIndex; + return Number(this.bundleData.slice(startIndex, curIndex)); + } + curIndex++; + } + throw new Error('Reached the end of bundle when more is expected.'); + } +} + +/** + * Creates an instance of BundleReader without exposing the BundleReaderSyncImpl class type. + */ +export function newBundleReaderSync( + bundleData: string, + serializer: JsonProtoSerializer +): BundleReaderSync { + return new BundleReaderSyncImpl(bundleData, serializer); +} diff --git a/packages/firestore/src/util/json_validation.ts b/packages/firestore/src/util/json_validation.ts index f16e28cacff..1660c8c4e77 100644 --- a/packages/firestore/src/util/json_validation.ts +++ b/packages/firestore/src/util/json_validation.ts @@ -112,7 +112,7 @@ export function validateJSON( schema: S ): json is Json { if (!isPlainObject(json)) { - throw new FirestoreError(Code.INVALID_ARGUMENT, 'json must be an object'); + throw new FirestoreError(Code.INVALID_ARGUMENT, 'JSON must be an object'); } let error: string | undefined = undefined; for (const key in schema) { @@ -121,12 +121,13 @@ export function validateJSON( const value: { value: unknown } | undefined = 'value' in schema[key] ? { value: schema[key].value } : undefined; if (!(key in json)) { - error = `json missing required field: ${key}`; + error = `JSON missing required field: '${key}'`; + break; } // eslint-disable-next-line @typescript-eslint/no-explicit-any const fieldValue = (json as any)[key]; if (typeString && typeof fieldValue !== typeString) { - error = `json field '${key}' must be a ${typeString}.`; + error = `JSON field '${key}' must be a ${typeString}.`; break; } else if (value !== undefined && fieldValue !== value.value) { error = `Expected '${key}' field to equal '${value.value}'`; diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index b6320169582..15e3ab9b84b 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -33,6 +33,7 @@ import { DocumentData, documentId, DocumentSnapshot, + documentSnapshotFromJSON, enableIndexedDbPersistence, enableNetwork, getDoc, @@ -42,6 +43,7 @@ import { initializeFirestore, limit, onSnapshot, + onSnapshotResume, onSnapshotsInSync, orderBy, query, @@ -66,6 +68,7 @@ import { newTestApp, FirestoreError, QuerySnapshot, + querySnapshotFromJSON, vector, getDocsFromServer } from '../util/firebase_export'; @@ -1209,7 +1212,7 @@ apiDescribe('Database', persistence => { async (docRef, db) => { const doc = await getDoc(docRef); const accumulator = new EventsAccumulator(); - const unsubscribe = onSnapshot( + const unsubscribe = onSnapshotResume( db, doc.toJSON(), accumulator.storeEvent @@ -1240,7 +1243,7 @@ apiDescribe('Database', persistence => { async (docRef, db) => { const doc = await getDoc(docRef); const accumulator = new EventsAccumulator(); - const unsubscribe = onSnapshot( + const unsubscribe = onSnapshotResume( db, doc.toJSON(), accumulator.storeEvent @@ -1271,7 +1274,7 @@ apiDescribe('Database', persistence => { async (docRef, db) => { const doc = await getDoc(docRef); const accumulator = new EventsAccumulator(); - const unsubscribe = onSnapshot(db, doc.toJSON(), { + const unsubscribe = onSnapshotResume(db, doc.toJSON(), { next: accumulator.storeEvent }); await accumulator @@ -1299,7 +1302,7 @@ apiDescribe('Database', persistence => { bundleSource: 'DocumentSnapshot' }; const deferred = new Deferred(); - const unsubscribe = onSnapshot( + const unsubscribe = onSnapshotResume( db, json, ds => { @@ -1325,7 +1328,7 @@ apiDescribe('Database', persistence => { bundleSource: 'QuerySnapshot' }; const deferred = new Deferred(); - const unsubscribe = onSnapshot(db, json, { + const unsubscribe = onSnapshotResume(db, json, { next: ds => { expect(ds).to.not.exist; deferred.resolve(); @@ -1341,6 +1344,66 @@ apiDescribe('Database', persistence => { }); }); + it('DocumentSnapshot updated doc events in snapshot created by fromJSON bundle', async () => { + const initialData = { a: 0 }; + const finalData = { a: 1 }; + await withTestDocAndInitialData( + persistence, + initialData, + async (docRef, db) => { + const doc = await getDoc(docRef); + const fromJsonDoc = documentSnapshotFromJSON(db, doc.toJSON()); + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshotResume( + db, + fromJsonDoc.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 updated doc events in snapshot created by fromJSON doc ref', async () => { + const initialData = { a: 0 }; + const finalData = { a: 1 }; + await withTestDocAndInitialData( + persistence, + initialData, + async (docRef, db) => { + const doc = await getDoc(docRef); + const fromJsonDoc = documentSnapshotFromJSON(db, doc.toJSON()); + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshot(fromJsonDoc.ref, 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', async () => { const testDocs = { a: { foo: 1 }, @@ -1349,7 +1412,7 @@ apiDescribe('Database', persistence => { await withTestCollection(persistence, testDocs, async (coll, db) => { const querySnap = await getDocs(query(coll, orderBy(documentId()))); const accumulator = new EventsAccumulator(); - const unsubscribe = onSnapshot( + const unsubscribe = onSnapshotResume( db, querySnap.toJSON(), accumulator.storeEvent @@ -1372,7 +1435,7 @@ apiDescribe('Database', persistence => { 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(), { + const unsubscribe = onSnapshotResume(db, querySnap.toJSON(), { next: accumulator.storeEvent }); await accumulator.awaitEvent().then(snap => { @@ -1393,7 +1456,7 @@ apiDescribe('Database', persistence => { bundleSource: 'QuerySnapshot' }; const deferred = new Deferred(); - const unsubscribe = onSnapshot( + const unsubscribe = onSnapshotResume( db, json, qs => { @@ -1419,7 +1482,7 @@ apiDescribe('Database', persistence => { bundleSource: 'QuerySnapshot' }; const deferred = new Deferred(); - const unsubscribe = onSnapshot(db, json, { + const unsubscribe = onSnapshotResume(db, json, { next: qs => { expect(qs).to.not.exist; deferred.resolve(); @@ -1444,7 +1507,7 @@ apiDescribe('Database', persistence => { const querySnap = await getDocs(query(coll, orderBy(documentId()))); const refForDocA = querySnap.docs[0].ref; const accumulator = new EventsAccumulator(); - const unsubscribe = onSnapshot( + const unsubscribe = onSnapshotResume( db, querySnap.toJSON(), accumulator.storeEvent @@ -1469,6 +1532,75 @@ apiDescribe('Database', persistence => { }); }); + it('QuerySnapshot updated doc events in snapshot created by fromJSON 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 querySnapFromJson = querySnapshotFromJSON(db, querySnap.toJSON()); + const refForDocA = querySnapFromJson.docs[0].ref; + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshotResume( + db, + querySnapFromJson.toJSON(), + 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); + }) + .then(() => setDoc(refForDocA, { foo: 0 })) + .then(() => 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({ foo: 0 }); + expect(snap.docs[1].data()).to.deep.equal(testDocs.b); + }); + unsubscribe(); + }); + }); + + it('QuerySnapshot updated doc events in snapshot created by fromJSON query ref', 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 querySnapFromJson = querySnapshotFromJSON(db, querySnap.toJSON()); + const refForDocA = querySnapFromJson.docs[0].ref; + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshot( + querySnapFromJson.query, + 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); + }) + .then(() => setDoc(refForDocA, { foo: 0 })) + .then(() => 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({ foo: 0 }); + expect(snap.docs[1].data()).to.deep.equal(testDocs.b); + }); + unsubscribe(); + }); + }); + it('Metadata only changes are not fired when no options provided', () => { return withTestDoc(persistence, docRef => { const secondUpdateFound = new Deferred(); diff --git a/packages/firestore/test/unit/api/database.test.ts b/packages/firestore/test/unit/api/database.test.ts index 308835c8855..9fe02663d6f 100644 --- a/packages/firestore/test/unit/api/database.test.ts +++ b/packages/firestore/test/unit/api/database.test.ts @@ -19,6 +19,10 @@ import { expect } from 'chai'; import { DocumentReference, + DocumentSnapshot, + documentSnapshotFromJSON, + QuerySnapshot, + querySnapshotFromJSON, connectFirestoreEmulator, loadBundle, refEqual, @@ -31,6 +35,7 @@ import { collectionReference, documentReference, documentSnapshot, + firestore, newTestFirestore, query, querySnapshot @@ -88,6 +93,117 @@ describe('DocumentReference', () => { }); }); + it('fromJSON() throws with invalid data', () => { + const db = newTestFirestore(); + expect(() => { + DocumentReference.fromJSON(db, {}); + }).to.throw("JSON missing required field: 'type'"); + }); + + it('fromJSON() throws with missing type data', () => { + const db = newTestFirestore(); + expect(() => { + documentSnapshotFromJSON(db, { + bundleSource: 'DocumentSnapshot', + bundleName: 'test name', + bundle: 'test bundle' + }); + }).to.throw("JSON missing required field: 'type'"); + }); + + it('fromJSON() throws with invalid type data', () => { + const db = newTestFirestore(); + expect(() => { + documentSnapshotFromJSON(db, { + type: 1, + bundleSource: 'DocumentSnapshot', + bundleName: 'test name', + bundle: 'test bundle' + }); + }).to.throw("JSON field 'type' must be a string"); + }); + + it('fromJSON() throws with missing bundleSource', () => { + const db = newTestFirestore(); + expect(() => { + documentSnapshotFromJSON(db, { + type: DocumentSnapshot._jsonSchemaVersion, + bundleName: 'test name', + bundle: 'test bundle' + }); + }).to.throw("JSON missing required field: 'bundleSource'"); + }); + + it('fromJSON() throws with invalid bundleSource type', () => { + const db = newTestFirestore(); + expect(() => { + documentSnapshotFromJSON(db, { + type: DocumentSnapshot._jsonSchemaVersion, + bundleSource: 1, + bundleName: 'test name', + bundle: 'test bundle' + }); + }).to.throw("JSON field 'bundleSource' must be a string"); + }); + + it('fromJSON() throws with invalid bundleSource value', () => { + const db = newTestFirestore(); + expect(() => { + documentSnapshotFromJSON(db, { + type: DocumentSnapshot._jsonSchemaVersion, + bundleSource: 'QuerySnapshot', + bundleName: 'test name', + bundle: 'test bundle' + }); + }).to.throw("Expected 'bundleSource' field to equal 'DocumentSnapshot'"); + }); + + it('fromJSON() throws with missing bundleName', () => { + const db = newTestFirestore(); + expect(() => { + documentSnapshotFromJSON(db, { + type: DocumentSnapshot._jsonSchemaVersion, + bundleSource: 'DocumentSnapshot', + bundle: 'test bundle' + }); + }).to.throw("JSON missing required field: 'bundleName'"); + }); + + it('fromJSON() throws with invalid bundleName', () => { + const db = newTestFirestore(); + expect(() => { + documentSnapshotFromJSON(db, { + type: DocumentSnapshot._jsonSchemaVersion, + bundleSource: 'DocumentSnapshot', + bundleName: 1, + bundle: 'test bundle' + }); + }).to.throw("JSON field 'bundleName' must be a string"); + }); + + it('fromJSON() throws with missing bundle', () => { + const db = newTestFirestore(); + expect(() => { + documentSnapshotFromJSON(db, { + type: DocumentSnapshot._jsonSchemaVersion, + bundleSource: 'DocumentSnapshot', + bundleName: 'test name' + }); + }).to.throw("JSON missing required field: 'bundle'"); + }); + + it('fromJSON() throws with invalid bundle', () => { + const db = newTestFirestore(); + expect(() => { + documentSnapshotFromJSON(db, { + type: DocumentSnapshot._jsonSchemaVersion, + bundleSource: 'DocumentSnapshot', + bundleName: 'test name', + bundle: 1 + }); + }).to.throw("JSON field 'bundle' must be a string"); + }); + it('fromJSON() does not throw', () => { const db = newTestFirestore(); const docRef = documentReference('foo/bar'); @@ -190,6 +306,34 @@ describe('DocumentSnapshot', () => { `Await waitForPendingWrites() before invoking toJSON().` ); }); + + it('fromJSON parses toJSON result', () => { + const docSnap = documentSnapshot('foo/bar', { a: 1 }, /*fromCache=*/ true); + const json = docSnap.toJSON(); + expect(() => { + documentSnapshotFromJSON(docSnap._firestore, json); + }).to.not.throw; + }); + + it('fromJSON produces valid snapshot data.', () => { + const json = documentSnapshot( + 'foo/bar', + { a: 1 }, + /*fromCache=*/ true + ).toJSON(); + const db = firestore(); + const docSnap = documentSnapshotFromJSON(db, json); + expect(docSnap).to.exist; + const data = docSnap.data(); + expect(data).to.not.be.undefined; + expect(data).to.not.be.null; + if (data) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect((data as any).a).to.exist; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect((data as any).a).to.equal(1); + } + }); }); describe('Query', () => { @@ -318,7 +462,7 @@ describe('QuerySnapshot', () => { 'foo', {}, { a: { a: 1 } }, - keys(), + keys(), // An empty set of mutaded document keys signifies that there are no pending writes. false, false ).toJSON(); @@ -342,15 +486,173 @@ describe('QuerySnapshot', () => { 'foo', {}, { a: { a: 1 } }, - keys('foo/a'), - true, - true + keys('foo/a'), // A non empty set of mutated keys signifies pending writes. + false, + false ).toJSON() ).to.throw( `QuerySnapshot.toJSON() attempted to serialize a document with pending writes. ` + `Await waitForPendingWrites() before invoking toJSON().` ); }); + + it('fromJSON() throws with invalid data', () => { + const db = newTestFirestore(); + expect(() => { + querySnapshotFromJSON(db, {}); + }).to.throw("JSON missing required field: 'type'"); + }); + + it('fromJSON() throws with missing type data', () => { + const db = newTestFirestore(); + expect(() => { + querySnapshotFromJSON(db, { + bundleSource: 'QuerySnapshot', + bundleName: 'test name', + bundle: 'test bundle' + }); + }).to.throw("JSON missing required field: 'type'"); + }); + + it('fromJSON() throws with invalid type data', () => { + const db = newTestFirestore(); + expect(() => { + querySnapshotFromJSON(db, { + type: 1, + bundleSource: 'QuerySnapshot', + bundleName: 'test name', + bundle: 'test bundle' + }); + }).to.throw("JSON field 'type' must be a string"); + }); + + it('fromJSON() throws with missing bundle source data', () => { + const db = newTestFirestore(); + expect(() => { + querySnapshotFromJSON(db, { + type: QuerySnapshot._jsonSchemaVersion, + bundleName: 'test name', + bundle: 'test bundle' + }); + }).to.throw("JSON missing required field: 'bundleSource'"); + }); + + it('fromJSON() throws with invalid bundleSource type', () => { + const db = newTestFirestore(); + expect(() => { + querySnapshotFromJSON(db, { + type: QuerySnapshot._jsonSchemaVersion, + bundleSource: 1, + bundleName: 'test name', + bundle: 'test bundle' + }); + }).to.throw("JSON field 'bundleSource' must be a string"); + }); + + it('fromJSON() throws with invalid bundleSource value', () => { + const db = newTestFirestore(); + expect(() => { + querySnapshotFromJSON(db, { + type: QuerySnapshot._jsonSchemaVersion, + bundleSource: 'DocumentSnapshot', + bundleName: 'test name', + bundle: 'test bundle' + }); + }).to.throw("Expected 'bundleSource' field to equal 'QuerySnapshot'"); + }); + + it('fromJSON() throws with missing bundleName', () => { + const db = newTestFirestore(); + expect(() => { + querySnapshotFromJSON(db, { + type: QuerySnapshot._jsonSchemaVersion, + bundleSource: 'QuerySnapshot', + bundle: 'test bundle' + }); + }).to.throw("JSON missing required field: 'bundleName'"); + }); + + it('fromJSON() throws with invalid bundleName', () => { + const db = newTestFirestore(); + expect(() => { + querySnapshotFromJSON(db, { + type: QuerySnapshot._jsonSchemaVersion, + bundleSource: 'QuerySnapshot', + bundleName: 1, + bundle: 'test bundle' + }); + }).to.throw("JSON field 'bundleName' must be a string"); + }); + + it('fromJSON() throws with missing bundle field', () => { + const db = newTestFirestore(); + expect(() => { + querySnapshotFromJSON(db, { + type: QuerySnapshot._jsonSchemaVersion, + bundleSource: 'QuerySnapshot', + bundleName: 'test name' + }); + }).to.throw("JSON missing required field: 'bundle'"); + }); + + it('fromJSON() throws with invalid bundle field', () => { + const db = newTestFirestore(); + expect(() => { + querySnapshotFromJSON(db, { + type: QuerySnapshot._jsonSchemaVersion, + bundleSource: 'QuerySnapshot', + bundleName: 'test name', + bundle: 1 + }); + }).to.throw("JSON field 'bundle' must be a string"); + }); + + it('fromJSON does not throw', () => { + const json = querySnapshot( + 'foo', + {}, + { a: { a: 1 } }, + keys(), // An empty set of mutaded document keys signifies that there are no pending writes. + false, + false + ).toJSON(); + + const db = firestore(); + expect(() => { + querySnapshotFromJSON(db, json); + }).to.not.throw; + }); + + it('fromJSON parses produces valid snapshot data', () => { + const json = querySnapshot( + 'foo', + {}, + { a: { a: 1 } }, + keys(), // An empty set of mutaded document keys signifies that there are no pending writes. + false, + false + ).toJSON(); + + const db = firestore(); + const querySnap = querySnapshotFromJSON(db, json); + expect(querySnap).to.exist; + if (querySnap !== undefined) { + const docs = querySnap.docs; + expect(docs).to.not.be.undefined; + expect(docs).to.not.be.null; + if (docs) { + expect(docs.length).to.equal(1); + docs.map(document => { + const docData = document.data(); + expect(docData).to.exist; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect((docData as any).a).to.exist; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect((docData as any).a).to.equal(1); + }); + } + } + }); }); describe('SnapshotMetadata', () => {