Skip to content

Commit b85747b

Browse files
authored
[Feat] Add bundler implementation to Firestore SSR Serialization feature branch (#8872)
Merge the initial bundler implementation into the Firestore Result Serialization feature branch. Implementation includes the ability to invoke `toJSON()` on `QuerySnapshot` and `DocumentSnapshot` instances.
1 parent c8cbfff commit b85747b

File tree

10 files changed

+562
-5
lines changed

10 files changed

+562
-5
lines changed

common/api-review/firestore.api.md

+4
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ export class DocumentSnapshot<AppModelType = DocumentData, DbModelType extends D
178178
get id(): string;
179179
readonly metadata: SnapshotMetadata;
180180
get ref(): DocumentReference<AppModelType, DbModelType>;
181+
// (undocumented)
182+
toJSON(): object;
181183
}
182184

183185
export { EmulatorMockTokenOptions }
@@ -610,6 +612,8 @@ export class QuerySnapshot<AppModelType = DocumentData, DbModelType extends Docu
610612
readonly metadata: SnapshotMetadata;
611613
readonly query: Query<AppModelType, DbModelType>;
612614
get size(): number;
615+
// (undocumented)
616+
toJSON(): object;
613617
}
614618

615619
// @public

docs-devsite/firestore_.documentsnapshot.md

+12
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export declare class DocumentSnapshot<AppModelType = DocumentData, DbModelType e
4141
| [data(options)](./firestore_.documentsnapshot.md#documentsnapshotdata) | | Retrieves all fields in the document as an <code>Object</code>. Returns <code>undefined</code> if the document doesn't exist.<!-- -->By default, <code>serverTimestamp()</code> values that have not yet been set to their final value will be returned as <code>null</code>. You can override this by passing an options object. |
4242
| [exists()](./firestore_.documentsnapshot.md#documentsnapshotexists) | | Returns whether or not the data exists. True if the document exists. |
4343
| [get(fieldPath, options)](./firestore_.documentsnapshot.md#documentsnapshotget) | | Retrieves the field specified by <code>fieldPath</code>. Returns <code>undefined</code> if the document or field doesn't exist.<!-- -->By default, a <code>serverTimestamp()</code> that has not yet been set to its final value will be returned as <code>null</code>. You can override this by passing an options object. |
44+
| [toJSON()](./firestore_.documentsnapshot.md#documentsnapshottojson) | | |
4445

4546
## DocumentSnapshot.(constructor)
4647

@@ -144,3 +145,14 @@ any
144145

145146
The data at the specified field location or undefined if no such field exists in the document.
146147

148+
## DocumentSnapshot.toJSON()
149+
150+
<b>Signature:</b>
151+
152+
```typescript
153+
toJSON(): object;
154+
```
155+
<b>Returns:</b>
156+
157+
object
158+

docs-devsite/firestore_.querysnapshot.md

+12
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export declare class QuerySnapshot<AppModelType = DocumentData, DbModelType exte
3434
| --- | --- | --- |
3535
| [docChanges(options)](./firestore_.querysnapshot.md#querysnapshotdocchanges) | | Returns an array of the documents changes since the last snapshot. If this is the first snapshot, all documents will be in the list as 'added' changes. |
3636
| [forEach(callback, thisArg)](./firestore_.querysnapshot.md#querysnapshotforeach) | | Enumerates all of the documents in the <code>QuerySnapshot</code>. |
37+
| [toJSON()](./firestore_.querysnapshot.md#querysnapshottojson) | | |
3738

3839
## QuerySnapshot.docs
3940

@@ -126,3 +127,14 @@ forEach(callback: (result: QueryDocumentSnapshot<AppModelType, DbModelType>) =>
126127

127128
void
128129

130+
## QuerySnapshot.toJSON()
131+
132+
<b>Signature:</b>
133+
134+
```typescript
135+
toJSON(): object;
136+
```
137+
<b>Returns:</b>
138+
139+
object
140+

packages/firestore/src/api/snapshot.ts

+109
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@ import { AbstractUserDataWriter } from '../lite-api/user_data_writer';
3636
import { Document } from '../model/document';
3737
import { DocumentKey } from '../model/document_key';
3838
import { debugAssert, fail } from '../util/assert';
39+
import {
40+
BundleBuilder,
41+
DocumentSnapshotBundleData,
42+
QuerySnapshotBundleData
43+
} from '../util/bundle_builder_impl';
3944
import { Code, FirestoreError } from '../util/error';
45+
import { AutoId } from '../util/misc';
4046

4147
import { Firestore } from './database';
4248
import { SnapshotListenOptions } from './reference_impl';
@@ -496,6 +502,46 @@ export class DocumentSnapshot<
496502
}
497503
return undefined;
498504
}
505+
506+
toJSON(): object {
507+
const document = this._document;
508+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
509+
const result: any = {};
510+
result['bundle'] = '';
511+
result['source'] = 'DocumentSnapshot';
512+
513+
if (
514+
!document ||
515+
!document.isValidDocument() ||
516+
!document.isFoundDocument()
517+
) {
518+
return result;
519+
}
520+
const builder: BundleBuilder = new BundleBuilder(
521+
this._firestore,
522+
AutoId.newId()
523+
);
524+
const documentData = this._userDataWriter.convertObjectMap(
525+
document.data.value.mapValue.fields,
526+
'previous'
527+
);
528+
if (this.metadata.hasPendingWrites) {
529+
throw new FirestoreError(
530+
Code.FAILED_PRECONDITION,
531+
'DocumentSnapshot.toJSON() attempted to serialize a document with pending writes. ' +
532+
'Await waitForPendingWrites() before invoking toJSON().'
533+
);
534+
}
535+
builder.addBundleDocument(
536+
documentToDocumentSnapshotBundleData(
537+
this.ref.path,
538+
documentData,
539+
document
540+
)
541+
);
542+
result['bundle'] = builder.build();
543+
return result;
544+
}
499545
}
500546

501547
/**
@@ -651,6 +697,52 @@ export class QuerySnapshot<
651697

652698
return this._cachedChanges;
653699
}
700+
701+
toJSON(): object {
702+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
703+
const result: any = {};
704+
result['source'] = 'QuerySnapshot';
705+
const builder: BundleBuilder = new BundleBuilder(
706+
this._firestore,
707+
AutoId.newId()
708+
);
709+
const databaseId = this._firestore._databaseId.database;
710+
const projectId = this._firestore._databaseId.projectId;
711+
const parent = `projects/${projectId}/databases/${databaseId}/documents`;
712+
const docBundleDataArray: DocumentSnapshotBundleData[] = [];
713+
const docArray = this.docs;
714+
docArray.forEach(doc => {
715+
if (doc._document === null) {
716+
return;
717+
}
718+
const documentData = this._userDataWriter.convertObjectMap(
719+
doc._document.data.value.mapValue.fields,
720+
'previous'
721+
);
722+
if (this.metadata.hasPendingWrites) {
723+
throw new FirestoreError(
724+
Code.FAILED_PRECONDITION,
725+
'QuerySnapshot.toJSON() attempted to serialize a document with pending writes. ' +
726+
'Await waitForPendingWrites() before invoking toJSON().'
727+
);
728+
}
729+
docBundleDataArray.push(
730+
documentToDocumentSnapshotBundleData(
731+
doc.ref.path,
732+
documentData,
733+
doc._document
734+
)
735+
);
736+
});
737+
const bundleData: QuerySnapshotBundleData = {
738+
query: this.query._query,
739+
parent,
740+
docBundleDataArray
741+
};
742+
builder.addBundleQuery(bundleData);
743+
result['bundle'] = builder.build();
744+
return result;
745+
}
654746
}
655747

656748
/** Calculates the array of `DocumentChange`s for a given `ViewSnapshot`. */
@@ -790,3 +882,20 @@ export function snapshotEqual<AppModelType, DbModelType extends DocumentData>(
790882

791883
return false;
792884
}
885+
886+
// Formats Document data for bundling a DocumentSnapshot.
887+
function documentToDocumentSnapshotBundleData(
888+
path: string,
889+
documentData: DocumentData,
890+
document: Document
891+
): DocumentSnapshotBundleData {
892+
return {
893+
documentData,
894+
documentKey: document.mutableCopy().key,
895+
documentPath: path,
896+
documentExists: true,
897+
createdTime: document.createTime.toTimestamp(),
898+
readTime: document.readTime.toTimestamp(),
899+
versionTime: document.version.toTimestamp()
900+
};
901+
}

packages/firestore/src/lite-api/user_data_reader.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -778,7 +778,7 @@ export function parseData(
778778
}
779779
}
780780

781-
function parseObject(
781+
export function parseObject(
782782
obj: Dict<unknown>,
783783
context: ParseContextImpl
784784
): { mapValue: ProtoMapValue } {

packages/firestore/src/remote/serializer.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,10 @@ export function toTimestamp(
226226
}
227227
}
228228

229-
function fromTimestamp(date: ProtoTimestamp): Timestamp {
229+
/**
230+
* Returns a Timestamp typed object given protobuf timestamp value.
231+
*/
232+
export function fromTimestamp(date: ProtoTimestamp): Timestamp {
230233
const timestamp = normalizeTimestamp(date);
231234
return new Timestamp(timestamp.seconds, timestamp.nanos);
232235
}

0 commit comments

Comments
 (0)