1515 * limitations under the License.
1616 */
1717
18- import { queryToTarget } from '../../src/core/query' ;
18+ import { queryToTarget } from '../../src/core/query' ;
1919import {
2020 JsonProtoSerializer ,
21- toDocument ,
2221 toName ,
2322 toQueryTarget ,
24- toTimestamp ,
23+ toTimestamp
2524} from '../../src/remote/serializer' ;
26- import { Firestore } from '../api/database' ;
27- import { DatabaseId } from '../core/database_info' ;
28- import { DocumentSnapshot , QuerySnapshot } from '../lite-api/snapshot' ;
29- import { Timestamp } from '../lite-api/timestamp' ;
25+ import { encoder } from '../../test/unit/util/bundle_data' ;
26+ import { Firestore } from '../api/database' ;
27+ import { ExpUserDataWriter } from '../api/reference_impl' ;
28+ import { DatabaseId } from '../core/database_info' ;
29+ import { DocumentSnapshot , QuerySnapshot } from '../lite-api/snapshot' ;
30+ import { Timestamp } from '../lite-api/timestamp' ;
31+ import {
32+ parseObject ,
33+ UserDataReader ,
34+ UserDataSource
35+ } from '../lite-api/user_data_reader' ;
36+ import { AbstractUserDataWriter } from '../lite-api/user_data_writer' ;
37+ import { MutableDocument } from '../model/document' ;
3038import {
3139 BundledDocumentMetadata as ProtoBundledDocumentMetadata ,
3240 BundleElement as ProtoBundleElement ,
3341 BundleMetadata as ProtoBundleMetadata ,
34- NamedQuery as ProtoNamedQuery ,
42+ NamedQuery as ProtoNamedQuery
3543} from '../protos/firestore_bundle_proto' ;
3644import {
3745 Document as ProtoDocument ,
3846 Document
3947} from '../protos/firestore_proto_api' ;
4048
41- import {
42- invalidArgumentMessage ,
43- validateString ,
44- } from './bundle_builder_validation_utils' ;
45- import { encoder } from "../../test/unit/util/bundle_data" ;
46- import {
47- parseData , parseObject ,
48- UserDataReader ,
49- UserDataSource
50- } from "../lite-api/user_data_reader" ;
51- import { AbstractUserDataWriter } from "../lite-api/user_data_writer" ;
52- import { ExpUserDataWriter } from "../api/reference_impl" ;
53- import { MutableDocument } from "../model/document" ;
54- import { debugAssert } from "./assert" ;
49+ import { debugAssert } from './assert' ;
5550
5651const BUNDLE_VERSION = 1 ;
5752
5853/**
5954 * Builds a Firestore data bundle with results from the given document and query snapshots.
6055 */
6156export class BundleBuilder {
62-
6357 // Resulting documents for the bundle, keyed by full document path.
6458 private documents : Map < string , BundledDocument > = new Map ( ) ;
6559 // Named queries saved in the bundle, keyed by query name.
@@ -79,10 +73,17 @@ export class BundleBuilder {
7973
8074 // useProto3Json is true because the objects will be serialized to JSON string
8175 // before being written to the bundle buffer.
82- this . serializer = new JsonProtoSerializer ( this . databaseId , /*useProto3Json=*/ true ) ;
76+ this . serializer = new JsonProtoSerializer (
77+ this . databaseId ,
78+ /*useProto3Json=*/ true
79+ ) ;
8380
8481 this . userDataWriter = new ExpUserDataWriter ( firestore ) ;
85- this . userDataReader = new UserDataReader ( this . databaseId , true , this . serializer ) ;
82+ this . userDataReader = new UserDataReader (
83+ this . databaseId ,
84+ true ,
85+ this . serializer
86+ ) ;
8687 }
8788
8889 /**
@@ -110,7 +111,9 @@ export class BundleBuilder {
110111 querySnapshot ?: QuerySnapshot
111112 ) : BundleBuilder {
112113 if ( arguments . length < 1 || arguments . length > 2 ) {
113- throw new Error ( 'Function BundleBuilder.add() requires 1 or 2 arguments.' ) ;
114+ throw new Error (
115+ 'Function BundleBuilder.add() requires 1 or 2 arguments.'
116+ ) ;
114117 }
115118 if ( arguments . length === 1 ) {
116119 validateDocumentSnapshot ( 'documentOrName' , documentOrName ) ;
@@ -123,9 +126,7 @@ export class BundleBuilder {
123126 return this ;
124127 }
125128
126- toBundleDocument (
127- document : MutableDocument
128- ) : ProtoDocument {
129+ toBundleDocument ( document : MutableDocument ) : ProtoDocument {
129130 // TODO handle documents that have mutations
130131 debugAssert (
131132 ! document . hasLocalMutations ,
@@ -136,23 +137,35 @@ export class BundleBuilder {
136137 // to Proto3 JSON objects. This is the same approach used in
137138 // bundling in the nodejs-firestore SDK. It may not be the most
138139 // performant approach.
139- const documentData = this . userDataWriter . convertObjectMap ( document . data . value . mapValue . fields , 'previous' ) ;
140+ const documentData = this . userDataWriter . convertObjectMap (
141+ document . data . value . mapValue . fields ,
142+ 'previous'
143+ ) ;
140144 // a parse context is typically used for validating and parsing user data, but in this
141145 // case we are using it internally to convert DocumentData to Proto3 JSON
142- const context = this . userDataReader . createContext ( UserDataSource . ArrayArgument , 'internal toBundledDocument' ) ;
146+ const context = this . userDataReader . createContext (
147+ UserDataSource . ArrayArgument ,
148+ 'internal toBundledDocument'
149+ ) ;
143150 const proto3Fields = parseObject ( documentData , context ) ;
144151
145152 return {
146153 name : toName ( this . serializer , document . key ) ,
147154 fields : proto3Fields . mapValue . fields ,
148155 updateTime : toTimestamp ( this . serializer , document . version . toTimestamp ( ) ) ,
149- createTime : toTimestamp ( this . serializer , document . createTime . toTimestamp ( ) )
156+ createTime : toTimestamp (
157+ this . serializer ,
158+ document . createTime . toTimestamp ( )
159+ )
150160 } ;
151161 }
152162
153163 private addBundledDocument ( snap : DocumentSnapshot , queryName ?: string ) : void {
154- // TODO: is this a valid shortcircuit?
155- if ( ! snap . _document || ! snap . _document . isValidDocument ( ) ) {
164+ if (
165+ ! snap . _document ||
166+ ! snap . _document . isValidDocument ( ) ||
167+ ! snap . _document . isFoundDocument ( )
168+ ) {
156169 return ;
157170 }
158171 const originalDocument = this . documents . get ( snap . ref . path ) ;
@@ -161,18 +174,20 @@ export class BundleBuilder {
161174
162175 // Update with document built from `snap` because it is newer.
163176 const snapReadTime = snap . readTime ;
164- if ( ! originalDocument ||
165- ( ! snapReadTime && ! originalDocument . metadata . readTime ) ||
166- ( snapReadTime && originalDocument . metadata . readTime ! < snapReadTime )
177+ if (
178+ ! originalDocument ||
179+ ( ! snapReadTime && ! originalDocument . metadata . readTime ) ||
180+ ( snapReadTime && originalDocument . metadata . readTime ! < snapReadTime )
167181 ) {
168-
169182 this . documents . set ( snap . ref . path , {
170- document : snap . _document . isFoundDocument ( ) ? this . toBundleDocument ( mutableCopy ) : undefined ,
183+ document : this . toBundleDocument ( mutableCopy ) ,
171184 metadata : {
172185 name : toName ( this . serializer , mutableCopy . key ) ,
173- readTime : ! ! snapReadTime ? toTimestamp ( this . serializer , snapReadTime ) : undefined ,
174- exists : snap . exists ( ) ,
175- } ,
186+ readTime : ! ! snapReadTime
187+ ? toTimestamp ( this . serializer , snapReadTime )
188+ : undefined ,
189+ exists : snap . exists ( )
190+ }
176191 } ) ;
177192 }
178193
@@ -183,20 +198,21 @@ export class BundleBuilder {
183198 newDocument . metadata . queries ! . push ( queryName ) ;
184199 }
185200
186- const readTime = snap . readTime ;
187- if ( readTime && readTime > this . latestReadTime ) {
188- this . latestReadTime = readTime ;
201+ if ( snapReadTime && snapReadTime > this . latestReadTime ) {
202+ this . latestReadTime = snapReadTime ;
189203 }
190204 }
191205
206+ // TODO: remove this since we're not planning to serialize named queries.
192207 private addNamedQuery ( name : string , querySnap : QuerySnapshot ) : void {
193208 if ( this . namedQueries . has ( name ) ) {
194209 throw new Error ( `Query name conflict: ${ name } has already been added.` ) ;
195210 }
196- const queryTarget = toQueryTarget ( this . serializer , queryToTarget ( querySnap . query . _query ) ) ;
211+ const queryTarget = toQueryTarget (
212+ this . serializer ,
213+ queryToTarget ( querySnap . query . _query )
214+ ) ;
197215
198- // TODO: if we can't resolve the query's readTime then can we set it to the latest
199- // of the document collection?
200216 let latestReadTime = new Timestamp ( 0 , 0 ) ;
201217 for ( const snap of querySnap . docs ) {
202218 const readTime = snap . readTime ;
@@ -226,9 +242,7 @@ export class BundleBuilder {
226242 * @internal
227243 * @param bundleElement A ProtoBundleElement that is expected to be Proto3 JSON compatible.
228244 */
229- private lengthPrefixedString (
230- bundleElement : ProtoBundleElement
231- ) : string {
245+ private lengthPrefixedString ( bundleElement : ProtoBundleElement ) : string {
232246 const str = JSON . stringify ( bundleElement ) ;
233247 // TODO: it's not ideal to have to re-encode all of these strings multiple times
234248 // It may be more performant to return a UInt8Array that is concatenated to other
@@ -242,18 +256,18 @@ export class BundleBuilder {
242256 let bundleString = '' ;
243257
244258 for ( const namedQuery of this . namedQueries . values ( ) ) {
245- bundleString += this . lengthPrefixedString ( { namedQuery} ) ;
259+ bundleString += this . lengthPrefixedString ( { namedQuery } ) ;
246260 }
247261
248262 for ( const bundledDocument of this . documents . values ( ) ) {
249263 const documentMetadata : ProtoBundledDocumentMetadata =
250264 bundledDocument . metadata ;
251265
252- bundleString += this . lengthPrefixedString ( { documentMetadata} ) ;
266+ bundleString += this . lengthPrefixedString ( { documentMetadata } ) ;
253267 // Write to the bundle if document exists.
254268 const document = bundledDocument . document ;
255269 if ( document ) {
256- bundleString += this . lengthPrefixedString ( { document} ) ;
270+ bundleString += this . lengthPrefixedString ( { document } ) ;
257271 }
258272 }
259273
@@ -263,10 +277,10 @@ export class BundleBuilder {
263277 version : BUNDLE_VERSION ,
264278 totalDocuments : this . documents . size ,
265279 // TODO: it's not ideal to have to re-encode all of these strings multiple times
266- totalBytes : encoder . encode ( bundleString ) . length ,
280+ totalBytes : encoder . encode ( bundleString ) . length
267281 } ;
268282 // Prepends the metadata element to the bundleBuffer: `bundleBuffer` is the second argument to `Buffer.concat`.
269- bundleString = this . lengthPrefixedString ( { metadata} ) + bundleString ;
283+ bundleString = this . lengthPrefixedString ( { metadata } ) + bundleString ;
270284
271285 // TODO: it's not ideal to have to re-encode all of these strings multiple times
272286 // the implementation in nodejs-firestore concatenates Buffers instead of
@@ -314,3 +328,47 @@ function validateQuerySnapshot(arg: string | number, value: unknown): void {
314328 throw new Error ( invalidArgumentMessage ( arg , 'QuerySnapshot' ) ) ;
315329 }
316330}
331+
332+ /**
333+ * Validates that 'value' is a string.
334+ *
335+ * @private
336+ * @internal
337+ * @param arg The argument name or argument index (for varargs methods).
338+ * @param value The input to validate.
339+ * @param options Options that specify whether the string can be omitted.
340+ */
341+ export function validateString ( arg : string | number , value : unknown ) : void {
342+ if ( typeof value !== 'string' ) {
343+ throw new Error ( invalidArgumentMessage ( arg , 'string' ) ) ;
344+ }
345+ }
346+
347+ /**
348+ * Generates an error message to use with invalid arguments.
349+ *
350+ * @private
351+ * @internal
352+ * @param arg The argument name or argument index (for varargs methods).
353+ * @param expectedType The expected input type.
354+ */
355+ export function invalidArgumentMessage (
356+ arg : string | number ,
357+ expectedType : string
358+ ) : string {
359+ return `${ formatArgumentName ( arg ) } is not a valid ${ expectedType } .` ;
360+ }
361+
362+ /**
363+ * Creates a descriptive name for the provided argument name or index.
364+ *
365+ * @private
366+ * @internal
367+ * @param arg The argument name or argument index (for varargs methods).
368+ * @return Either the argument name or its index description.
369+ */
370+ function formatArgumentName ( arg : string | number ) : string {
371+ return typeof arg === 'string'
372+ ? `Value for argument "${ arg } "`
373+ : `Element at index ${ arg } ` ;
374+ }
0 commit comments