Skip to content

Commit d8637a9

Browse files
Options cleanup for includeMetadataChanges
Options cleanup for includeMetadataChanges
2 parents e2ef160 + add5b32 commit d8637a9

File tree

15 files changed

+246
-183
lines changed

15 files changed

+246
-183
lines changed

packages/firestore-types/index.d.ts

Lines changed: 20 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -468,13 +468,14 @@ export class WriteBatch {
468468
}
469469

470470
/**
471-
* Options for use with `DocumentReference.onSnapshot()` to control the
472-
* behavior of the snapshot listener.
471+
* An options object that can be passed to `DocumentReference.onSnapshot()`,
472+
* `Query.onSnapshot()` and `QuerySnapshot.docChanges()` to control which
473+
* types of changes to include in the result set.
473474
*/
474-
export interface DocumentListenOptions {
475+
export interface SnapshotListenOptions {
475476
/**
476-
* Raise an event even if only metadata of the document changed. Default is
477-
* false.
477+
* Include a change even if only the metadata of the query or a document
478+
* changed. Default is false.
478479
*/
479480
readonly includeMetadataChanges?: boolean;
480481
}
@@ -660,7 +661,7 @@ export class DocumentReference {
660661
complete?: () => void;
661662
}): () => void;
662663
onSnapshot(
663-
options: DocumentListenOptions,
664+
options: SnapshotListenOptions,
664665
observer: {
665666
next?: (snapshot: DocumentSnapshot) => void;
666667
error?: (error: Error) => void;
@@ -673,7 +674,7 @@ export class DocumentReference {
673674
onCompletion?: () => void
674675
): () => void;
675676
onSnapshot(
676-
options: DocumentListenOptions,
677+
options: SnapshotListenOptions,
677678
onNext: (snapshot: DocumentSnapshot) => void,
678679
onError?: (error: Error) => void,
679680
onCompletion?: () => void
@@ -843,25 +844,6 @@ export type OrderByDirection = 'desc' | 'asc';
843844
*/
844845
export type WhereFilterOp = '<' | '<=' | '==' | '>=' | '>';
845846

846-
/**
847-
* Options for use with `Query.onSnapshot() to control the behavior of the
848-
* snapshot listener.
849-
*/
850-
export interface QueryListenOptions {
851-
/**
852-
* Raise an event even if only metadata changes (i.e. one of the
853-
* `QuerySnapshot.metadata` properties). Default is false.
854-
*/
855-
readonly includeQueryMetadataChanges?: boolean;
856-
857-
/**
858-
* Raise an event even if only metadata of a document in the query results
859-
* changes (i.e. one of the `DocumentSnapshot.metadata` properties on one of
860-
* the documents). Default is false.
861-
*/
862-
readonly includeDocumentMetadataChanges?: boolean;
863-
}
864-
865847
/**
866848
* A `Query` refers to a Query which you can read or listen to. You can also
867849
* construct refined `Query` objects by adding filters and ordering.
@@ -1042,7 +1024,7 @@ export class Query {
10421024
complete?: () => void;
10431025
}): () => void;
10441026
onSnapshot(
1045-
options: QueryListenOptions,
1027+
options: SnapshotListenOptions,
10461028
observer: {
10471029
next?: (snapshot: QuerySnapshot) => void;
10481030
error?: (error: Error) => void;
@@ -1055,7 +1037,7 @@ export class Query {
10551037
onCompletion?: () => void
10561038
): () => void;
10571039
onSnapshot(
1058-
options: QueryListenOptions,
1040+
options: SnapshotListenOptions,
10591041
onNext: (snapshot: QuerySnapshot) => void,
10601042
onError?: (error: Error) => void,
10611043
onCompletion?: () => void
@@ -1082,12 +1064,6 @@ export class QuerySnapshot {
10821064
* modifications.
10831065
*/
10841066
readonly metadata: SnapshotMetadata;
1085-
/**
1086-
* An array of the documents that changed since the last snapshot. If this
1087-
* is the first snapshot, all documents will be in the list as added
1088-
* changes.
1089-
*/
1090-
readonly docChanges: DocumentChange[];
10911067

10921068
/** An array of all the documents in the QuerySnapshot. */
10931069
readonly docs: QueryDocumentSnapshot[];
@@ -1098,6 +1074,16 @@ export class QuerySnapshot {
10981074
/** True if there are no documents in the QuerySnapshot. */
10991075
readonly empty: boolean;
11001076

1077+
/**
1078+
* Returns an array of the documents changes since the last snapshot. If this
1079+
* is the first snapshot, all documents will be in the list as added changes.
1080+
*
1081+
* @param options `SnapshotListenOptions` that control whether metadata-only
1082+
* changes (i.e. only `DocumentSnapshot.metadata` changed) should trigger
1083+
* snapshot events.
1084+
*/
1085+
docChanges(options?: SnapshotListenOptions): DocumentChange[];
1086+
11011087
/**
11021088
* Enumerates all of the documents in the QuerySnapshot.
11031089
*

packages/firestore/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
# Unreleased
2+
- [changed] Merged the `includeQueryMetadataChanges` and
3+
`includeDocumentMetadataChanges` options passed to `Query.onSnapshot()` into
4+
a single `includeMetadataChanges` option.
5+
- [changed] `QuerySnapshot.docChanges()` is now a method that optionally takes
6+
an `includeMetadataChanges` option. By default, even when listening to a query
7+
with `{ includeMetadataChanges:true }`, metadata-only document changes are
8+
suppressed in `docChanges()`.
29
- [fixed] Fixed a regression in the Firebase JS release 4.11.0 that could
310
cause get() requests made while offline to be delayed by up to 10
411
seconds (rather than returning from cache immediately).

packages/firestore/src/api/database.ts

Lines changed: 97 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -877,7 +877,7 @@ export class DocumentReference implements firestore.DocumentReference {
877877
observer: PartialObserver<firestore.DocumentSnapshot>
878878
): Unsubscribe;
879879
onSnapshot(
880-
options: firestore.DocumentListenOptions,
880+
options: firestore.SnapshotListenOptions,
881881
observer: PartialObserver<firestore.DocumentSnapshot>
882882
): Unsubscribe;
883883
onSnapshot(
@@ -886,7 +886,7 @@ export class DocumentReference implements firestore.DocumentReference {
886886
onCompletion?: CompleteFn
887887
): Unsubscribe;
888888
onSnapshot(
889-
options: firestore.DocumentListenOptions,
889+
options: firestore.SnapshotListenOptions,
890890
onNext: NextFn<firestore.DocumentSnapshot>,
891891
onError?: ErrorFn,
892892
onCompletion?: CompleteFn
@@ -899,7 +899,7 @@ export class DocumentReference implements firestore.DocumentReference {
899899
1,
900900
4
901901
);
902-
let options: firestore.DocumentListenOptions = {
902+
let options: firestore.SnapshotListenOptions = {
903903
includeMetadataChanges: false
904904
};
905905
let observer: PartialObserver<firestore.DocumentSnapshot>;
@@ -908,7 +908,7 @@ export class DocumentReference implements firestore.DocumentReference {
908908
typeof args[currArg] === 'object' &&
909909
!isPartialObserver(args[currArg])
910910
) {
911-
options = args[currArg] as firestore.DocumentListenOptions;
911+
options = args[currArg] as firestore.SnapshotListenOptions;
912912
validateOptionNames('DocumentReference.onSnapshot', options, [
913913
'includeMetadataChanges'
914914
]);
@@ -922,8 +922,7 @@ export class DocumentReference implements firestore.DocumentReference {
922922
}
923923

924924
const internalOptions = {
925-
includeDocumentMetadataChanges: options.includeMetadataChanges,
926-
includeQueryMetadataChanges: options.includeMetadataChanges
925+
includeMetadataChanges: options.includeMetadataChanges
927926
};
928927

929928
if (isPartialObserver(args[currArg])) {
@@ -1041,8 +1040,7 @@ export class DocumentReference implements firestore.DocumentReference {
10411040
): void {
10421041
const unlisten = this.onSnapshotInternal(
10431042
{
1044-
includeQueryMetadataChanges: true,
1045-
includeDocumentMetadataChanges: true,
1043+
includeMetadataChanges: true,
10461044
waitForSyncWhenOnline: true
10471045
},
10481046
{
@@ -1566,7 +1564,7 @@ export class Query implements firestore.Query {
15661564

15671565
onSnapshot(observer: PartialObserver<firestore.QuerySnapshot>): Unsubscribe;
15681566
onSnapshot(
1569-
options: firestore.QueryListenOptions,
1567+
options: firestore.SnapshotListenOptions,
15701568
observer: PartialObserver<firestore.QuerySnapshot>
15711569
): Unsubscribe;
15721570
onSnapshot(
@@ -1575,37 +1573,30 @@ export class Query implements firestore.Query {
15751573
onCompletion?: CompleteFn
15761574
): Unsubscribe;
15771575
onSnapshot(
1578-
options: firestore.QueryListenOptions,
1576+
options: firestore.SnapshotListenOptions,
15791577
onNext: NextFn<firestore.QuerySnapshot>,
15801578
onError?: ErrorFn,
15811579
onCompletion?: CompleteFn
15821580
): Unsubscribe;
15831581

15841582
onSnapshot(...args: AnyJs[]): Unsubscribe {
15851583
validateBetweenNumberOfArgs('Query.onSnapshot', arguments, 1, 4);
1586-
let options: firestore.QueryListenOptions = {};
1584+
let options: firestore.SnapshotListenOptions = {};
15871585
let observer: PartialObserver<firestore.QuerySnapshot>;
15881586
let currArg = 0;
15891587
if (
15901588
typeof args[currArg] === 'object' &&
15911589
!isPartialObserver(args[currArg])
15921590
) {
1593-
options = args[currArg] as firestore.QueryListenOptions;
1591+
options = args[currArg] as firestore.SnapshotListenOptions;
15941592
validateOptionNames('Query.onSnapshot', options, [
1595-
'includeQueryMetadataChanges',
1596-
'includeDocumentMetadataChanges'
1593+
'includeMetadataChanges'
15971594
]);
15981595
validateNamedOptionalType(
15991596
'Query.onSnapshot',
16001597
'boolean',
1601-
'includeDocumentMetadataChanges',
1602-
options.includeDocumentMetadataChanges
1603-
);
1604-
validateNamedOptionalType(
1605-
'Query.onSnapshot',
1606-
'boolean',
1607-
'includeQueryMetadataChanges',
1608-
options.includeQueryMetadataChanges
1598+
'includeMetadataChanges',
1599+
options.includeMetadataChanges
16091600
);
16101601
currArg++;
16111602
}
@@ -1692,8 +1683,7 @@ export class Query implements firestore.Query {
16921683
): void {
16931684
const unlisten = this.onSnapshotInternal(
16941685
{
1695-
includeDocumentMetadataChanges: false,
1696-
includeQueryMetadataChanges: true,
1686+
includeMetadataChanges: true,
16971687
waitForSyncWhenOnline: true
16981688
},
16991689
{
@@ -1774,6 +1764,7 @@ export class Query implements firestore.Query {
17741764

17751765
export class QuerySnapshot implements firestore.QuerySnapshot {
17761766
private _cachedChanges: firestore.DocumentChange[] | null = null;
1767+
private _cachedChangesIncludeMetadataChanges: boolean | null = null;
17771768

17781769
readonly metadata: firestore.SnapshotMetadata;
17791770

@@ -1817,13 +1808,44 @@ export class QuerySnapshot implements firestore.QuerySnapshot {
18171808
return new Query(this._originalQuery, this._firestore);
18181809
}
18191810

1820-
get docChanges(): firestore.DocumentChange[] {
1821-
if (!this._cachedChanges) {
1811+
docChanges(
1812+
options?: firestore.SnapshotListenOptions
1813+
): firestore.DocumentChange[] {
1814+
validateOptionNames('QuerySnapshot.docChanges', options, [
1815+
'includeMetadataChanges'
1816+
]);
1817+
1818+
if (options) {
1819+
validateNamedOptionalType(
1820+
'QuerySnapshot.docChanges',
1821+
'boolean',
1822+
'includeMetadataChanges',
1823+
options.includeMetadataChanges
1824+
);
1825+
}
1826+
1827+
const includeMetadataChanges = options && options.includeMetadataChanges;
1828+
1829+
if (includeMetadataChanges && this._snapshot.excludesMetadataChanges) {
1830+
throw new FirestoreError(
1831+
Code.INVALID_ARGUMENT,
1832+
'To include metadata changes with your document changes, you must ' +
1833+
'also pass { includeMetadataChanges:true } to onSnapshot().'
1834+
);
1835+
}
1836+
1837+
if (
1838+
!this._cachedChanges ||
1839+
this._cachedChangesIncludeMetadataChanges !== includeMetadataChanges
1840+
) {
18221841
this._cachedChanges = changesFromSnapshot(
18231842
this._firestore,
1843+
includeMetadataChanges,
18241844
this._snapshot
18251845
);
1846+
this._cachedChangesIncludeMetadataChanges = includeMetadataChanges;
18261847
}
1848+
18271849
return this._cachedChanges;
18281850
}
18291851

@@ -1850,6 +1872,30 @@ export class QuerySnapshot implements firestore.QuerySnapshot {
18501872
}
18511873
}
18521874

1875+
// TODO(2018/11/01): As of 2018/04/17 we're changing docChanges from an array
1876+
// into a method. Because this is a runtime breaking change and somewhat subtle
1877+
// (both Array and Function have a .length, etc.), we'll replace the .length and
1878+
// @@iterator properties to throw a custom error message. In ~6 months we can
1879+
// delete the custom error as most folks will have hopefully migrated.
1880+
function throwDocChangesMethodError(): never {
1881+
throw new FirestoreError(
1882+
Code.INVALID_ARGUMENT,
1883+
'QuerySnapshot.docChanges has been changed from a property into a ' +
1884+
'method, so usages like "querySnapshot.docChanges" should become ' +
1885+
'"querySnapshot.docChanges()"'
1886+
);
1887+
}
1888+
1889+
Object.defineProperty(QuerySnapshot.prototype.docChanges, 'length', {
1890+
get: () => throwDocChangesMethodError()
1891+
});
1892+
1893+
if (typeof Symbol !== 'undefined') {
1894+
Object.defineProperty(QuerySnapshot.prototype.docChanges, Symbol.iterator, {
1895+
get: () => throwDocChangesMethodError()
1896+
});
1897+
}
1898+
18531899
export class CollectionReference extends Query
18541900
implements firestore.CollectionReference {
18551901
constructor(path: ResourcePath, firestore: Firestore) {
@@ -1968,6 +2014,7 @@ function validateReference(
19682014
*/
19692015
export function changesFromSnapshot(
19702016
firestore: Firestore,
2017+
includeMetadataChanges: boolean,
19712018
snapshot: ViewSnapshot
19722019
): firestore.DocumentChange[] {
19732020
if (snapshot.oldDocs.isEmpty()) {
@@ -2002,26 +2049,30 @@ export function changesFromSnapshot(
20022049
// A DocumentSet that is updated incrementally as changes are applied to use
20032050
// to lookup the index of a document.
20042051
let indexTracker = snapshot.oldDocs;
2005-
return snapshot.docChanges.map(change => {
2006-
const doc = new QueryDocumentSnapshot(
2007-
firestore,
2008-
change.doc.key,
2009-
change.doc,
2010-
snapshot.fromCache
2011-
);
2012-
let oldIndex = -1;
2013-
let newIndex = -1;
2014-
if (change.type !== ChangeType.Added) {
2015-
oldIndex = indexTracker.indexOf(change.doc.key);
2016-
assert(oldIndex >= 0, 'Index for document not found');
2017-
indexTracker = indexTracker.delete(change.doc.key);
2018-
}
2019-
if (change.type !== ChangeType.Removed) {
2020-
indexTracker = indexTracker.add(change.doc);
2021-
newIndex = indexTracker.indexOf(change.doc.key);
2022-
}
2023-
return { type: resultChangeType(change.type), doc, oldIndex, newIndex };
2024-
});
2052+
return snapshot.docChanges
2053+
.filter(
2054+
change => includeMetadataChanges || change.type !== ChangeType.Metadata
2055+
)
2056+
.map(change => {
2057+
const doc = new QueryDocumentSnapshot(
2058+
firestore,
2059+
change.doc.key,
2060+
change.doc,
2061+
snapshot.fromCache
2062+
);
2063+
let oldIndex = -1;
2064+
let newIndex = -1;
2065+
if (change.type !== ChangeType.Added) {
2066+
oldIndex = indexTracker.indexOf(change.doc.key);
2067+
assert(oldIndex >= 0, 'Index for document not found');
2068+
indexTracker = indexTracker.delete(change.doc.key);
2069+
}
2070+
if (change.type !== ChangeType.Removed) {
2071+
indexTracker = indexTracker.add(change.doc);
2072+
newIndex = indexTracker.indexOf(change.doc.key);
2073+
}
2074+
return { type: resultChangeType(change.type), doc, oldIndex, newIndex };
2075+
});
20252076
}
20262077
}
20272078

0 commit comments

Comments
 (0)