Skip to content

Commit f4451f0

Browse files
Add getQuery(), getQueryFromCache() and getQueryFromServer()
1 parent 6289e19 commit f4451f0

File tree

6 files changed

+370
-151
lines changed

6 files changed

+370
-151
lines changed

packages/firestore/exp/src/api/reference.ts

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@ import { Firestore } from './database';
2323
import { DocumentKeyReference } from '../../../src/api/user_data_reader';
2424
import { debugAssert } from '../../../src/util/assert';
2525
import { cast } from '../../../lite/src/api/util';
26-
import { DocumentSnapshot } from './snapshot';
26+
import { DocumentSnapshot, QuerySnapshot } from './snapshot';
2727
import {
28+
getDocsViaSnapshotListener,
2829
getDocViaSnapshotListener,
2930
SnapshotMetadata
3031
} from '../../../src/api/database';
3132
import { ViewSnapshot } from '../../../src/core/view_snapshot';
32-
import { DocumentReference } from '../../../lite/src/api/reference';
33+
import { DocumentReference, Query } from '../../../lite/src/api/reference';
3334
import { Document } from '../../../src/model/document';
3435

3536
export function getDoc<T>(
@@ -59,11 +60,11 @@ export function getDocFromCache<T>(
5960
firestore,
6061
ref._key,
6162
doc,
62-
ref._converter,
6363
new SnapshotMetadata(
6464
doc instanceof Document ? doc.hasLocalMutations : false,
6565
/* fromCache= */ true
66-
)
66+
),
67+
ref._converter
6768
);
6869
});
6970
}
@@ -83,6 +84,62 @@ export function getDocFromServer<T>(
8384
});
8485
}
8586

87+
export function getQuery<T>(
88+
query: firestore.Query<T>
89+
): Promise<QuerySnapshot<T>> {
90+
const internalQuery = cast<Query<T>>(query, Query);
91+
const firestore = cast<Firestore>(query.firestore, Firestore);
92+
return firestore._getFirestoreClient().then(async firestoreClient => {
93+
const snapshot = await getDocsViaSnapshotListener(
94+
firestoreClient,
95+
internalQuery._query
96+
);
97+
return new QuerySnapshot(
98+
firestore,
99+
internalQuery,
100+
snapshot,
101+
new SnapshotMetadata(snapshot.hasPendingWrites, snapshot.fromCache)
102+
);
103+
});
104+
}
105+
106+
export function getQueryFromCache<T>(
107+
query: firestore.Query<T>
108+
): Promise<QuerySnapshot<T>> {
109+
const internalQuery = cast<Query<T>>(query, Query);
110+
const firestore = cast<Firestore>(query.firestore, Firestore);
111+
return firestore._getFirestoreClient().then(async firestoreClient => {
112+
const snapshot = await firestoreClient.getDocumentsFromLocalCache(
113+
internalQuery._query
114+
);
115+
return new QuerySnapshot(
116+
firestore,
117+
internalQuery,
118+
snapshot,
119+
new SnapshotMetadata(snapshot.hasPendingWrites, /* fromCache= */ true)
120+
);
121+
});
122+
}
123+
export function getQueryFromServer<T>(
124+
query: firestore.Query<T>
125+
): Promise<QuerySnapshot<T>> {
126+
const internalQuery = cast<Query<T>>(query, Query);
127+
const firestore = cast<Firestore>(query.firestore, Firestore);
128+
return firestore._getFirestoreClient().then(async firestoreClient => {
129+
const snapshot = await getDocsViaSnapshotListener(
130+
firestoreClient,
131+
internalQuery._query,
132+
{ source: 'server' }
133+
);
134+
return new QuerySnapshot(
135+
firestore,
136+
internalQuery,
137+
snapshot,
138+
new SnapshotMetadata(snapshot.hasPendingWrites, snapshot.fromCache)
139+
);
140+
});
141+
}
142+
86143
/**
87144
* Converts a ViewSnapshot that contains the single document specified by `ref`
88145
* to a DocumentSnapshot.
@@ -102,7 +159,7 @@ function convertToDocSnapshot<T>(
102159
firestore,
103160
ref._key,
104161
doc,
105-
ref._converter,
106-
new SnapshotMetadata(snapshot.hasPendingWrites, snapshot.fromCache)
162+
new SnapshotMetadata(snapshot.hasPendingWrites, snapshot.fromCache),
163+
ref._converter
107164
);
108165
}

packages/firestore/exp/src/api/snapshot.ts

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,13 @@ import {
2929
} from '../../../lite/src/api/snapshot';
3030
import { Firestore } from './database';
3131
import { cast } from '../../../lite/src/api/util';
32-
import { DocumentReference } from '../../../lite/src/api/reference';
33-
import { SnapshotMetadata } from '../../../src/api/database';
32+
import { DocumentReference, Query } from '../../../lite/src/api/reference';
33+
import {
34+
changesFromSnapshot,
35+
SnapshotMetadata
36+
} from '../../../src/api/database';
37+
import { Code, FirestoreError } from '../../../src/util/error';
38+
import { ViewSnapshot } from '../../../src/core/view_snapshot';
3439

3540
const DEFAULT_SERVER_TIMESTAMP_BEHAVIOR: ServerTimestampBehavior = 'none';
3641

@@ -43,8 +48,8 @@ export class DocumentSnapshot<T = firestore.DocumentData>
4348
readonly _firestore: Firestore,
4449
key: DocumentKey,
4550
document: Document | null,
46-
converter: firestore.FirestoreDataConverter<T> | null,
47-
readonly metadata: firestore.SnapshotMetadata
51+
readonly metadata: firestore.SnapshotMetadata,
52+
converter: firestore.FirestoreDataConverter<T> | null
4853
) {
4954
super(_firestore, key, document, converter);
5055
this._firestoreImpl = cast(_firestore, Firestore);
@@ -64,8 +69,8 @@ export class DocumentSnapshot<T = firestore.DocumentData>
6469
this._firestore,
6570
this._key,
6671
this._document,
67-
/* converter= */ null,
68-
this.metadata
72+
this.metadata,
73+
/* converter= */ null
6974
);
7075
return this._converter.fromFirestore(snapshot);
7176
} else {
@@ -109,3 +114,88 @@ export class QueryDocumentSnapshot<T = firestore.DocumentData>
109114
return super.data(options) as T;
110115
}
111116
}
117+
118+
export class QuerySnapshot<T = firestore.DocumentData>
119+
implements firestore.QuerySnapshot<T> {
120+
private _cachedChanges?: Array<firestore.DocumentChange<T>>;
121+
private _cachedChangesIncludeMetadataChanges?: boolean;
122+
123+
constructor(
124+
private readonly _firestore: Firestore,
125+
readonly query: Query<T>,
126+
private readonly _snapshot: ViewSnapshot,
127+
readonly metadata: SnapshotMetadata
128+
) {}
129+
130+
get docs(): Array<firestore.QueryDocumentSnapshot<T>> {
131+
const result: Array<firestore.QueryDocumentSnapshot<T>> = [];
132+
this.forEach(doc => result.push(doc));
133+
return result;
134+
}
135+
136+
get size(): number {
137+
return this._snapshot.docs.size;
138+
}
139+
140+
get empty(): boolean {
141+
return this.size === 0;
142+
}
143+
144+
forEach(
145+
callback: (result: firestore.QueryDocumentSnapshot<T>) => void,
146+
thisArg?: unknown
147+
): void {
148+
this._snapshot.docs.forEach(doc => {
149+
callback.call(
150+
thisArg,
151+
this._convertToDocumentSnapshot(
152+
doc,
153+
this.metadata.fromCache,
154+
this._snapshot.mutatedKeys.has(doc.key)
155+
)
156+
);
157+
});
158+
}
159+
160+
docChanges(
161+
options: firestore.SnapshotListenOptions = {}
162+
): Array<firestore.DocumentChange<T>> {
163+
const includeMetadataChanges = !!options.includeMetadataChanges;
164+
165+
if (includeMetadataChanges && this._snapshot.excludesMetadataChanges) {
166+
throw new FirestoreError(
167+
Code.INVALID_ARGUMENT,
168+
'To include metadata changes with your document changes, you must ' +
169+
'also pass { includeMetadataChanges:true } to onSnapshot().'
170+
);
171+
}
172+
173+
if (
174+
!this._cachedChanges ||
175+
this._cachedChangesIncludeMetadataChanges !== includeMetadataChanges
176+
) {
177+
this._cachedChanges = changesFromSnapshot<QueryDocumentSnapshot<T>>(
178+
this._snapshot,
179+
includeMetadataChanges,
180+
this._convertToDocumentSnapshot.bind(this)
181+
);
182+
this._cachedChangesIncludeMetadataChanges = includeMetadataChanges;
183+
}
184+
185+
return this._cachedChanges;
186+
}
187+
188+
private _convertToDocumentSnapshot(
189+
doc: Document,
190+
fromCache: boolean,
191+
hasPendingWrites: boolean
192+
): QueryDocumentSnapshot<T> {
193+
return new QueryDocumentSnapshot<T>(
194+
this._firestore,
195+
doc.key,
196+
doc,
197+
new SnapshotMetadata(hasPendingWrites, fromCache),
198+
this.query._converter
199+
);
200+
}
201+
}

packages/firestore/exp/test/helpers.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
DEFAULT_SETTINGS
2727
} from '../../test/integration/util/settings';
2828
import { collection } from '../../lite/src/api/reference';
29+
import { AutoId } from '../../src/util/misc';
2930

3031
let appCount = 0;
3132

@@ -49,6 +50,14 @@ export function withTestDb(
4950
return withTestDbSettings(DEFAULT_PROJECT_ID, DEFAULT_SETTINGS, fn);
5051
}
5152

53+
export function withTestCollection(
54+
fn: (collRef: firestore.CollectionReference) => void | Promise<void>
55+
): Promise<void> {
56+
return withTestDb(db => {
57+
return fn(collection(db, AutoId.newId()));
58+
});
59+
}
60+
5261
export function withTestDoc(
5362
fn: (doc: firestore.DocumentReference) => void | Promise<void>
5463
): Promise<void> {

packages/firestore/exp/test/integration.test.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
* limitations under the License.
1616
*/
1717

18+
import * as firestore from '../';
19+
1820
import { initializeApp } from '@firebase/app-exp';
1921
import { expect, use } from 'chai';
2022
import * as chaiAsPromised from 'chai-as-promised';
@@ -24,12 +26,16 @@ import {
2426
getFirestore,
2527
initializeFirestore
2628
} from '../src/api/database';
27-
import { withTestDoc } from './helpers';
29+
import { withTestCollection, withTestDoc } from './helpers';
2830
import {
2931
getDoc,
3032
getDocFromCache,
31-
getDocFromServer
33+
getDocFromServer,
34+
getQuery,
35+
getQueryFromCache,
36+
getQueryFromServer
3237
} from '../src/api/reference';
38+
import { QuerySnapshot } from '../src/api/snapshot';
3339

3440
use(chaiAsPromised);
3541

@@ -87,3 +93,43 @@ describe('getDocFromServer()', () => {
8793
});
8894
});
8995
});
96+
97+
describe('getQuery()', () => {
98+
it('can query a non-existing collection', () => {
99+
return withTestCollection(async collRef => {
100+
const querySnap = await getQuery(collRef);
101+
validateEmptySnapshot(querySnap, /* fromCache= */ false);
102+
});
103+
});
104+
});
105+
106+
describe('getQueryFromCache()', () => {
107+
it('can query a non-existing collection', () => {
108+
return withTestCollection(async collRef => {
109+
const querySnap = await getQueryFromCache(collRef);
110+
validateEmptySnapshot(querySnap, /* fromCache= */ true);
111+
});
112+
});
113+
});
114+
115+
describe('getQueryFromServer()', () => {
116+
it('can query a non-existing collection', () => {
117+
return withTestCollection(async collRef => {
118+
const querySnap = await getQueryFromServer(collRef);
119+
validateEmptySnapshot(querySnap, /* fromCache= */ false);
120+
});
121+
});
122+
});
123+
124+
function validateEmptySnapshot(
125+
querySnap: QuerySnapshot<firestore.DocumentData>,
126+
fromCache: boolean
127+
) {
128+
expect(querySnap.metadata.fromCache).to.equal(fromCache);
129+
expect(querySnap.metadata.hasPendingWrites).to.be.false;
130+
expect(querySnap.empty).to.be.true;
131+
expect(querySnap.size).to.equal(0);
132+
expect(querySnap.docs).to.be.empty;
133+
expect(querySnap.docChanges()).to.be.empty;
134+
expect(querySnap.docChanges({ includeMetadataChanges: true })).to.be.empty;
135+
}

packages/firestore/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"dev": "rollup -c -w",
2020
"lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'",
2121
"lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'",
22-
"prettier": "prettier --write '*.ts' '*.js' 'exp/**/*.ts' 'src/**/*.js' 'test/**/*.js' 'src/**/*.ts' 'test/**/*.ts'",
22+
"prettier": "prettier --write '*.ts' '*.js' 'lite/**/*.ts' 'exp/**/*.ts' 'src/**/*.js' 'test/**/*.js' 'src/**/*.ts' 'test/**/*.ts'",
2323
"pregendeps:exp": "yarn build:exp",
2424
"gendeps:exp": "../../scripts/exp/extract-deps.sh --types ./exp/index.d.ts --bundle ./dist/exp/index.js --output ./exp/test/deps/dependencies.json",
2525
"pretest:exp": "yarn build:exp",

0 commit comments

Comments
 (0)