@@ -51,6 +51,7 @@ import {
5151 QuerySnapshotBundleData
5252} from '../util/bundle_builder_impl' ;
5353import { Code , FirestoreError } from '../util/error' ;
54+ import { Property , property , validateJSON } from '../util/json_validation' ;
5455import { AutoId } from '../util/misc' ;
5556
5657import { Firestore } from './database' ;
@@ -544,7 +545,7 @@ export class DocumentSnapshot<
544545 throw new FirestoreError (
545546 Code . FAILED_PRECONDITION ,
546547 'DocumentSnapshot.toJSON() attempted to serialize a document with pending writes. ' +
547- 'Await waitForPendingWrites() before invoking toJSON().'
548+ 'Await waitForPendingWrites() before invoking toJSON().'
548549 ) ;
549550 }
550551 builder . addBundleDocument (
@@ -612,8 +613,7 @@ export class DocumentSnapshot<
612613 ) . getElements ( ) ;
613614 if ( elements . length === 0 ) {
614615 error = 'No snapshat data was found in the bundle.' ;
615- }
616- if (
616+ } else if (
617617 elements . length !== 2 ||
618618 ! elements [ 0 ] . payload . documentMetadata ||
619619 ! elements [ 1 ] . payload . document
@@ -782,7 +782,7 @@ export class QuerySnapshot<
782782 throw new FirestoreError (
783783 Code . INVALID_ARGUMENT ,
784784 'To include metadata changes with your document changes, you must ' +
785- 'also pass { includeMetadataChanges:true } to onSnapshot().'
785+ 'also pass { includeMetadataChanges:true } to onSnapshot().'
786786 ) ;
787787 }
788788
@@ -797,6 +797,14 @@ export class QuerySnapshot<
797797 return this . _cachedChanges ;
798798 }
799799
800+ static _jsonSchemaVersion : string = 'firestore/querySnapshot/1.0' ;
801+ static _jsonSchema = {
802+ type : property ( 'string' , QuerySnapshot . _jsonSchemaVersion ) ,
803+ bundleSource : property ( 'string' ) ,
804+ bundleName : property ( 'string' ) ,
805+ bundle : property ( 'string' )
806+ } ;
807+
800808 /**
801809 * Returns a JSON-serializable representation of this `QuerySnapshot` instance.
802810 *
@@ -805,6 +813,7 @@ export class QuerySnapshot<
805813 toJSON ( ) : object {
806814 // eslint-disable-next-line @typescript-eslint/no-explicit-any
807815 const result : any = { } ;
816+ result [ 'type' ] = QuerySnapshot . _jsonSchemaVersion ;
808817 result [ 'bundleSource' ] = 'QuerySnapshot' ;
809818 result [ 'bundleName' ] = AutoId . newId ( ) ;
810819
@@ -829,7 +838,7 @@ export class QuerySnapshot<
829838 throw new FirestoreError (
830839 Code . FAILED_PRECONDITION ,
831840 'QuerySnapshot.toJSON() attempted to serialize a document with pending writes. ' +
832- 'Await waitForPendingWrites() before invoking toJSON().'
841+ 'Await waitForPendingWrites() before invoking toJSON().'
833842 ) ;
834843 }
835844 docBundleDataArray . push (
@@ -867,92 +876,69 @@ export class QuerySnapshot<
867876 db : Firestore ,
868877 json : object
869878 ) : QuerySnapshot < AppModelType , DbModelType > | null {
870- const requiredFields = [ 'bundle' , 'bundleName' , 'bundleSource' ] ;
871- let error : string | undefined = undefined ;
872- let bundleString : string = '' ;
873- for ( const key of requiredFields ) {
874- if ( ! ( key in json ) ) {
875- error = `json missing required field: ${ key } ` ;
879+ if ( validateJSON ( json , QuerySnapshot . _jsonSchema ) ) {
880+ if ( json . bundleSource !== 'QuerySnapshot' ) {
881+ throw new FirestoreError ( Code . INVALID_ARGUMENT , "Expected 'bundleSource' field to equal 'QuerySnapshot'" ) ;
876882 }
877- // eslint-disable-next-line @typescript-eslint/no-explicit-any
878- const value = ( json as any ) [ key ] ;
879- if ( key === 'bundleSource' ) {
880- if ( typeof value !== 'string' ) {
881- error = `json field 'bundleSource' must be a string.` ;
882- break ;
883- } else if ( value !== 'QuerySnapshot' ) {
884- error = "Expected 'bundleSource' field to equal 'QuerySnapshot'" ;
885- break ;
886- }
887- } else if ( key === 'bundle' ) {
888- if ( typeof value !== 'string' ) {
889- error = `json field 'bundle' must be a string.` ;
890- break ;
891- }
892- bundleString = value ;
893- }
894- }
895- if ( error ) {
896- throw new FirestoreError ( Code . INVALID_ARGUMENT , error ) ;
897- }
898- // Parse the bundle data.
899- const serializer = newSerializer ( db . _databaseId ) ;
900- const bundleReader = createBundleReaderSync ( bundleString , serializer ) ;
901- const bundleMetadata = bundleReader . getMetadata ( ) ;
902- const elements = bundleReader . getElements ( ) ;
903- if ( elements . length === 0 ) {
904- throw new FirestoreError (
905- Code . INVALID_ARGUMENT ,
906- 'No snapshat data was found in the bundle.'
883+ // Parse the bundle data.
884+ const serializer = newSerializer ( db . _databaseId ) ;
885+ const bundleReader = createBundleReaderSync ( json . bundle , serializer ) ;
886+ const bundleLoader : BundleLoader = new BundleLoader (
887+ bundleReader . getMetadata ( ) ,
888+ serializer
907889 ) ;
908- }
890+ const elements = bundleReader . getElements ( ) ;
891+ for ( const element of elements ) {
892+ bundleLoader . addSizedElement ( element ) ;
893+ }
894+ const parsedNamedQueries = bundleLoader . queries ;
895+ if ( parsedNamedQueries . length !== 1 ) {
896+ throw new FirestoreError (
897+ Code . INVALID_ARGUMENT ,
898+ `Snapshot data expected 1 query but found ${ parsedNamedQueries . length } queries.`
899+ ) ;
900+ }
909901
910- const bundleLoader : BundleLoader = new BundleLoader (
911- bundleMetadata ,
912- serializer
913- ) ;
914- for ( const element of elements ) {
915- bundleLoader . addSizedElement ( element ) ;
916- }
917- const parsedNamedQueries = bundleLoader . queries ;
918- if ( parsedNamedQueries . length !== 1 ) {
919- throw new FirestoreError (
920- Code . INVALID_ARGUMENT ,
921- 'Snapshot data contained more than one named query.'
922- ) ;
923- }
902+ // Create an internal Query object from the named query in the budnle.
903+ const query = fromBundledQuery ( parsedNamedQueries [ 0 ] . bundledQuery ! ) ;
924904
925- const query = fromBundledQuery ( parsedNamedQueries [ 0 ] . bundledQuery ! ) ;
926- // convert bundle data into the types that the DocumentSnapshot constructore requires.
927- const liteUserDataWriter = new LiteUserDataWriter ( db ) ;
905+ // Construct the arrays of document data for the query.
906+ const bundledDocuments = bundleLoader . documents ;
907+ const documentSet = new DocumentSet ( ) ;
908+ const documentKeys = documentKeySet ( ) ;
909+ for ( const bundledDocumet of bundledDocuments ) {
910+ const document = fromDocument ( serializer , bundledDocumet . document ! ) ;
911+ documentSet . add ( document ) ;
912+ const documentPath = ResourcePath . fromString (
913+ bundledDocumet . metadata . name !
914+ ) ;
915+ documentKeys . add ( new DocumentKey ( documentPath ) ) ;
916+ }
928917
929- const bundledDocuments = bundleLoader . documents ;
930- const documentSet = new DocumentSet ( ) ;
931- const documentKeys = documentKeySet ( ) ;
932- for ( const bundledDocumet of bundledDocuments ) {
933- const document = fromDocument ( serializer , bundledDocumet . document ! ) ;
934- documentSet . add ( document ) ;
935- const documentPath = ResourcePath . fromString (
936- bundledDocumet . metadata . name !
918+ // Create a view snapshot of the query and documents.
919+ const viewSnapshot = ViewSnapshot . fromInitialDocuments (
920+ query ,
921+ documentSet ,
922+ documentKeys ,
923+ /* fromCache= */ false ,
924+ /* hasCachedResults= */ false
937925 ) ;
938- documentKeys . add ( new DocumentKey ( documentPath ) ) ;
939- }
940926
941- const viewSnapshot = ViewSnapshot . fromInitialDocuments (
942- query ,
943- documentSet ,
944- documentKeys ,
945- false , // fromCache
946- false // hasCachedResults
947- ) ;
927+ // Create an external Query object, required to construct the QuerySnapshot.
928+ const externalQuery = new Query < AppModelType , DbModelType > ( db , null , query ) ;
948929
949- const externalQuery = new Query < AppModelType , DbModelType > ( db , null , query ) ;
930+ // Return a new QuerySnapshot with all of the collected data.
931+ return new QuerySnapshot < AppModelType , DbModelType > (
932+ db ,
933+ new LiteUserDataWriter ( db ) ,
934+ externalQuery ,
935+ viewSnapshot
936+ ) ;
937+ }
950938
951- return new QuerySnapshot < AppModelType , DbModelType > (
952- db ,
953- liteUserDataWriter ,
954- externalQuery ,
955- viewSnapshot
939+ throw new FirestoreError (
940+ Code . INTERNAL ,
941+ 'Unexpected error creating QuerySnapshot from JSON.'
956942 ) ;
957943 }
958944}
@@ -977,10 +963,10 @@ export function changesFromSnapshot<
977963 ) ;
978964 debugAssert (
979965 ! lastDoc ||
980- newQueryComparator ( querySnapshot . _snapshot . query ) (
981- lastDoc ,
982- change . doc
983- ) < 0 ,
966+ newQueryComparator ( querySnapshot . _snapshot . query ) (
967+ lastDoc ,
968+ change . doc
969+ ) < 0 ,
984970 'Got added events in wrong order'
985971 ) ;
986972 const doc = new QueryDocumentSnapshot < AppModelType , DbModelType > (
0 commit comments