Skip to content

Commit

Permalink
Add getQuery(), getQueryFromCache() and getQueryFromServer()
Browse files Browse the repository at this point in the history
  • Loading branch information
schmidt-sebastian committed Jun 25, 2020
1 parent 6289e19 commit f4451f0
Show file tree
Hide file tree
Showing 6 changed files with 370 additions and 151 deletions.
69 changes: 63 additions & 6 deletions packages/firestore/exp/src/api/reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ import { Firestore } from './database';
import { DocumentKeyReference } from '../../../src/api/user_data_reader';
import { debugAssert } from '../../../src/util/assert';
import { cast } from '../../../lite/src/api/util';
import { DocumentSnapshot } from './snapshot';
import { DocumentSnapshot, QuerySnapshot } from './snapshot';
import {
getDocsViaSnapshotListener,
getDocViaSnapshotListener,
SnapshotMetadata
} from '../../../src/api/database';
import { ViewSnapshot } from '../../../src/core/view_snapshot';
import { DocumentReference } from '../../../lite/src/api/reference';
import { DocumentReference, Query } from '../../../lite/src/api/reference';
import { Document } from '../../../src/model/document';

export function getDoc<T>(
Expand Down Expand Up @@ -59,11 +60,11 @@ export function getDocFromCache<T>(
firestore,
ref._key,
doc,
ref._converter,
new SnapshotMetadata(
doc instanceof Document ? doc.hasLocalMutations : false,
/* fromCache= */ true
)
),
ref._converter
);
});
}
Expand All @@ -83,6 +84,62 @@ export function getDocFromServer<T>(
});
}

export function getQuery<T>(
query: firestore.Query<T>
): Promise<QuerySnapshot<T>> {
const internalQuery = cast<Query<T>>(query, Query);
const firestore = cast<Firestore>(query.firestore, Firestore);
return firestore._getFirestoreClient().then(async firestoreClient => {
const snapshot = await getDocsViaSnapshotListener(
firestoreClient,
internalQuery._query
);
return new QuerySnapshot(
firestore,
internalQuery,
snapshot,
new SnapshotMetadata(snapshot.hasPendingWrites, snapshot.fromCache)
);
});
}

export function getQueryFromCache<T>(
query: firestore.Query<T>
): Promise<QuerySnapshot<T>> {
const internalQuery = cast<Query<T>>(query, Query);
const firestore = cast<Firestore>(query.firestore, Firestore);
return firestore._getFirestoreClient().then(async firestoreClient => {
const snapshot = await firestoreClient.getDocumentsFromLocalCache(
internalQuery._query
);
return new QuerySnapshot(
firestore,
internalQuery,
snapshot,
new SnapshotMetadata(snapshot.hasPendingWrites, /* fromCache= */ true)
);
});
}
export function getQueryFromServer<T>(
query: firestore.Query<T>
): Promise<QuerySnapshot<T>> {
const internalQuery = cast<Query<T>>(query, Query);
const firestore = cast<Firestore>(query.firestore, Firestore);
return firestore._getFirestoreClient().then(async firestoreClient => {
const snapshot = await getDocsViaSnapshotListener(
firestoreClient,
internalQuery._query,
{ source: 'server' }
);
return new QuerySnapshot(
firestore,
internalQuery,
snapshot,
new SnapshotMetadata(snapshot.hasPendingWrites, snapshot.fromCache)
);
});
}

/**
* Converts a ViewSnapshot that contains the single document specified by `ref`
* to a DocumentSnapshot.
Expand All @@ -102,7 +159,7 @@ function convertToDocSnapshot<T>(
firestore,
ref._key,
doc,
ref._converter,
new SnapshotMetadata(snapshot.hasPendingWrites, snapshot.fromCache)
new SnapshotMetadata(snapshot.hasPendingWrites, snapshot.fromCache),
ref._converter
);
}
102 changes: 96 additions & 6 deletions packages/firestore/exp/src/api/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,13 @@ import {
} from '../../../lite/src/api/snapshot';
import { Firestore } from './database';
import { cast } from '../../../lite/src/api/util';
import { DocumentReference } from '../../../lite/src/api/reference';
import { SnapshotMetadata } from '../../../src/api/database';
import { DocumentReference, Query } from '../../../lite/src/api/reference';
import {
changesFromSnapshot,
SnapshotMetadata
} from '../../../src/api/database';
import { Code, FirestoreError } from '../../../src/util/error';
import { ViewSnapshot } from '../../../src/core/view_snapshot';

const DEFAULT_SERVER_TIMESTAMP_BEHAVIOR: ServerTimestampBehavior = 'none';

Expand All @@ -43,8 +48,8 @@ export class DocumentSnapshot<T = firestore.DocumentData>
readonly _firestore: Firestore,
key: DocumentKey,
document: Document | null,
converter: firestore.FirestoreDataConverter<T> | null,
readonly metadata: firestore.SnapshotMetadata
readonly metadata: firestore.SnapshotMetadata,
converter: firestore.FirestoreDataConverter<T> | null
) {
super(_firestore, key, document, converter);
this._firestoreImpl = cast(_firestore, Firestore);
Expand All @@ -64,8 +69,8 @@ export class DocumentSnapshot<T = firestore.DocumentData>
this._firestore,
this._key,
this._document,
/* converter= */ null,
this.metadata
this.metadata,
/* converter= */ null
);
return this._converter.fromFirestore(snapshot);
} else {
Expand Down Expand Up @@ -109,3 +114,88 @@ export class QueryDocumentSnapshot<T = firestore.DocumentData>
return super.data(options) as T;
}
}

export class QuerySnapshot<T = firestore.DocumentData>
implements firestore.QuerySnapshot<T> {
private _cachedChanges?: Array<firestore.DocumentChange<T>>;
private _cachedChangesIncludeMetadataChanges?: boolean;

constructor(
private readonly _firestore: Firestore,
readonly query: Query<T>,
private readonly _snapshot: ViewSnapshot,
readonly metadata: SnapshotMetadata
) {}

get docs(): Array<firestore.QueryDocumentSnapshot<T>> {
const result: Array<firestore.QueryDocumentSnapshot<T>> = [];
this.forEach(doc => result.push(doc));
return result;
}

get size(): number {
return this._snapshot.docs.size;
}

get empty(): boolean {
return this.size === 0;
}

forEach(
callback: (result: firestore.QueryDocumentSnapshot<T>) => void,
thisArg?: unknown
): void {
this._snapshot.docs.forEach(doc => {
callback.call(
thisArg,
this._convertToDocumentSnapshot(
doc,
this.metadata.fromCache,
this._snapshot.mutatedKeys.has(doc.key)
)
);
});
}

docChanges(
options: firestore.SnapshotListenOptions = {}
): Array<firestore.DocumentChange<T>> {
const includeMetadataChanges = !!options.includeMetadataChanges;

if (includeMetadataChanges && this._snapshot.excludesMetadataChanges) {
throw new FirestoreError(
Code.INVALID_ARGUMENT,
'To include metadata changes with your document changes, you must ' +
'also pass { includeMetadataChanges:true } to onSnapshot().'
);
}

if (
!this._cachedChanges ||
this._cachedChangesIncludeMetadataChanges !== includeMetadataChanges
) {
this._cachedChanges = changesFromSnapshot<QueryDocumentSnapshot<T>>(
this._snapshot,
includeMetadataChanges,
this._convertToDocumentSnapshot.bind(this)
);
this._cachedChangesIncludeMetadataChanges = includeMetadataChanges;
}

return this._cachedChanges;
}

private _convertToDocumentSnapshot(
doc: Document,
fromCache: boolean,
hasPendingWrites: boolean
): QueryDocumentSnapshot<T> {
return new QueryDocumentSnapshot<T>(
this._firestore,
doc.key,
doc,
new SnapshotMetadata(hasPendingWrites, fromCache),
this.query._converter
);
}
}
9 changes: 9 additions & 0 deletions packages/firestore/exp/test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
DEFAULT_SETTINGS
} from '../../test/integration/util/settings';
import { collection } from '../../lite/src/api/reference';
import { AutoId } from '../../src/util/misc';

let appCount = 0;

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

export function withTestCollection(
fn: (collRef: firestore.CollectionReference) => void | Promise<void>
): Promise<void> {
return withTestDb(db => {
return fn(collection(db, AutoId.newId()));
});
}

export function withTestDoc(
fn: (doc: firestore.DocumentReference) => void | Promise<void>
): Promise<void> {
Expand Down
50 changes: 48 additions & 2 deletions packages/firestore/exp/test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
* limitations under the License.
*/

import * as firestore from '../';

import { initializeApp } from '@firebase/app-exp';
import { expect, use } from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
Expand All @@ -24,12 +26,16 @@ import {
getFirestore,
initializeFirestore
} from '../src/api/database';
import { withTestDoc } from './helpers';
import { withTestCollection, withTestDoc } from './helpers';
import {
getDoc,
getDocFromCache,
getDocFromServer
getDocFromServer,
getQuery,
getQueryFromCache,
getQueryFromServer
} from '../src/api/reference';
import { QuerySnapshot } from '../src/api/snapshot';

use(chaiAsPromised);

Expand Down Expand Up @@ -87,3 +93,43 @@ describe('getDocFromServer()', () => {
});
});
});

describe('getQuery()', () => {
it('can query a non-existing collection', () => {
return withTestCollection(async collRef => {
const querySnap = await getQuery(collRef);
validateEmptySnapshot(querySnap, /* fromCache= */ false);
});
});
});

describe('getQueryFromCache()', () => {
it('can query a non-existing collection', () => {
return withTestCollection(async collRef => {
const querySnap = await getQueryFromCache(collRef);
validateEmptySnapshot(querySnap, /* fromCache= */ true);
});
});
});

describe('getQueryFromServer()', () => {
it('can query a non-existing collection', () => {
return withTestCollection(async collRef => {
const querySnap = await getQueryFromServer(collRef);
validateEmptySnapshot(querySnap, /* fromCache= */ false);
});
});
});

function validateEmptySnapshot(
querySnap: QuerySnapshot<firestore.DocumentData>,
fromCache: boolean
) {
expect(querySnap.metadata.fromCache).to.equal(fromCache);
expect(querySnap.metadata.hasPendingWrites).to.be.false;
expect(querySnap.empty).to.be.true;
expect(querySnap.size).to.equal(0);
expect(querySnap.docs).to.be.empty;
expect(querySnap.docChanges()).to.be.empty;
expect(querySnap.docChanges({ includeMetadataChanges: true })).to.be.empty;
}
2 changes: 1 addition & 1 deletion packages/firestore/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"dev": "rollup -c -w",
"lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'",
"lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'",
"prettier": "prettier --write '*.ts' '*.js' 'exp/**/*.ts' 'src/**/*.js' 'test/**/*.js' 'src/**/*.ts' 'test/**/*.ts'",
"prettier": "prettier --write '*.ts' '*.js' 'lite/**/*.ts' 'exp/**/*.ts' 'src/**/*.js' 'test/**/*.js' 'src/**/*.ts' 'test/**/*.ts'",
"pregendeps:exp": "yarn build:exp",
"gendeps:exp": "../../scripts/exp/extract-deps.sh --types ./exp/index.d.ts --bundle ./dist/exp/index.js --output ./exp/test/deps/dependencies.json",
"pretest:exp": "yarn build:exp",
Expand Down
Loading

0 comments on commit f4451f0

Please sign in to comment.