Skip to content

Commit

Permalink
Add List() API (#368)
Browse files Browse the repository at this point in the history
  • Loading branch information
schmidt-sebastian authored Oct 2, 2018
1 parent 0f97345 commit b752fef
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 13 deletions.
47 changes: 47 additions & 0 deletions dev/src/reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1826,6 +1826,53 @@ export class CollectionReference extends Query {
return this._path.relativeName;
}

/**
* Retrieves the list of documents in this collection.
*
* The document references returned may include references to "missing
* documents", i.e. document locations that have no document present but
* which contain subcollections with documents. Attempting to read such a
* document reference (e.g. via `.get()` or `.onSnapshot()`) will return a
* `DocumentSnapshot` whose `.exists` property is false.
*
* @return {Promise<DocumentReference[]>} The list of documents in this
* collection.
*
* @example
* let collectionRef = firestore.collection('col');
*
* return collectionRef.listDocuments().then(documentRefs => {
* return firestore.getAll(documentRefs);
* }).then(documentSnapshots => {
* for (let documentSnapshot of documentSnapshots) {
* if (documentSnapshot.exists) {
* console.log(`Found document with data: ${documentSnapshot.id}`);
* } else {
* console.log(`Found missing document: ${documentSnapshot.id}`);
* }
* }
* });
*/
listDocuments(): Promise<DocumentReference[]> {
const request: api.IListDocumentsRequest = {
parent: this._path.parent()!.formattedName,
collectionId: this.id,
showMissing: true,
mask: {fieldPaths: []}
};

return this.firestore.request('listDocuments', request, requestTag())
.then((documents: api.IDocument[]) => {
// Note that the backend already orders these documents by name,
// so we do not need to manually sort them.
return documents.map(doc => {
const path = ResourcePath.fromSlashSeparatedString(doc.name!);
return this.doc(path.id!);
});
});
}


/**
* Gets a [DocumentReference]{@link DocumentReference} instance that
* refers to the document at the specified path. If no path is specified, an
Expand Down
18 changes: 18 additions & 0 deletions dev/system-test/firestore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,24 @@ describe('CollectionReference class', function() {
assert.equal(doc.get('foo'), 'a');
});
});

it('lists missing documents', async () => {
let batch = firestore.batch();

batch.set(randomCol.doc('a'),{});
batch.set(randomCol.doc('b/b/b'),{});
batch.set(randomCol.doc('c'),{});
await batch.commit();

const documentRefs = await randomCol.listDocuments();
const documents = await firestore.getAll(documentRefs);

const existingDocs = documents.filter(doc => doc.exists);
const missingDocs = documents.filter(doc => !doc.exists);

expect(existingDocs.map(doc => doc.id)).to.have.members(['a', 'c']);
expect(missingDocs.map(doc => doc.id)).to.have.members(['b']);
});
});

describe('DocumentReference class', function() {
Expand Down
25 changes: 24 additions & 1 deletion dev/test/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {expect} from 'chai';
import Firestore = require('../src');

import {DocumentReference} from '../src/reference';
import {COLLECTION_ROOT, createInstance, DATABASE_ROOT} from './util/helpers';
import {createInstance, DATABASE_ROOT, document} from './util/helpers';

// Change the argument to 'console.log' to enable debug output.
Firestore.setLogFunction(() => {});
Expand Down Expand Up @@ -133,6 +133,29 @@ describe('Collection interface', () => {
});
});

it('has list() method', () => {
const overrides = {
listDocuments: (request, options, callback) => {
expect(request).to.deep.eq({
parent: `${DATABASE_ROOT}/documents/a/b`,
collectionId: 'c',
showMissing: true,
mask: {fieldPaths: []}
});

callback(null, [document('first'), document('second')]);
}
};

return createInstance(overrides).then(firestore => {
return firestore.collection('a/b/c').listDocuments().then(
documentRefs => {
expect(documentRefs[0].id).to.eq('first');
expect(documentRefs[1].id).to.eq('second');
});
});
});

it('has isEqual() method', () => {
const coll1 = firestore.collection('coll1');
const coll1Equals = firestore.collection('coll1');
Expand Down
20 changes: 11 additions & 9 deletions dev/test/field-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,11 @@ describe('FieldValue.arrayUnion()', () => {
it('can be used with set()', () => {
const overrides: ApiOverride = {
commit: (request, options, callback) => {
const expectedRequest = commitRequest(set(document('foo', 'bar'), [
arrayTransform('field', 'appendMissingElements', 'foo', 'bar'),
arrayTransform('map.field', 'appendMissingElements', 'foo', 'bar')
]));
const expectedRequest =
commitRequest(set(document('documentId', 'foo', 'bar'), [
arrayTransform('field', 'appendMissingElements', 'foo', 'bar'),
arrayTransform('map.field', 'appendMissingElements', 'foo', 'bar')
]));

expect(request).to.deep.equal(expectedRequest);

Expand Down Expand Up @@ -133,10 +134,11 @@ describe('FieldValue.arrayRemove()', () => {
it('can be used with set()', () => {
const overrides: ApiOverride = {
commit: (request, options, callback) => {
const expectedRequest = commitRequest(set(document('foo', 'bar'), [
arrayTransform('field', 'removeAllFromArray', 'foo', 'bar'),
arrayTransform('map.field', 'removeAllFromArray', 'foo', 'bar')
]));
const expectedRequest =
commitRequest(set(document('documentId', 'foo', 'bar'), [
arrayTransform('field', 'removeAllFromArray', 'foo', 'bar'),
arrayTransform('map.field', 'removeAllFromArray', 'foo', 'bar')
]));
expect(request).to.deep.equal(expectedRequest);

callback(null, writeResult(2));
Expand Down Expand Up @@ -167,7 +169,7 @@ describe('FieldValue.serverTimestamp()', () => {
const overrides: ApiOverride = {
commit: (request, options, callback) => {
const expectedRequest = commitRequest(
set(document('foo', 'bar'),
set(document('documentId', 'foo', 'bar'),
[serverTimestamp('field'), serverTimestamp('map.field')]));
expect(request).to.deep.equal(expectedRequest);

Expand Down
1 change: 1 addition & 0 deletions dev/test/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ xdescribe('firestore.d.ts', function() {
const docRef2: DocumentReference = collRef.doc('doc');
collRef.add(documentData).then((docRef: DocumentReference) => {
});
const list: Promise<DocumentReference[]> = collRef.listDocuments();
const equals: boolean = collRef.isEqual(collRef);
});

Expand Down
7 changes: 4 additions & 3 deletions dev/test/util/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export type ApiOverride = {
commit?: (request, options, callback) => void;
rollback?: (request, options, callback) => void;
listCollectionIds?: (request, options, callback) => void;
listDocuments?: (request, options, callback) => void;
batchGetDocuments?: (request) => NodeJS.ReadableStream;
runQuery?: (request) => NodeJS.ReadableStream;
listen?: () => NodeJS.ReadWriteStream;
Expand Down Expand Up @@ -153,16 +154,16 @@ function value(value: string|api.IValue): api.IValue {
}

export function document(
field?: string, value?: string|api.IValue,
id: string, field?: string, value?: string|api.IValue,
...fieldOrValue: Array<string|api.IValue>): api.IDocument {
const document: api.IDocument = {
name: `${DATABASE_ROOT}/documents/collectionId/documentId`,
name: `${DATABASE_ROOT}/documents/collectionId/${id}`,
fields: {},
createTime: {seconds: 1, nanos: 2},
updateTime: {seconds: 3, nanos: 4},
};

for (let i = 0; i < arguments.length; i += 2) {
for (let i = 1; i < arguments.length; i += 2) {
const field: string = arguments[i];
const value: string|api.Value = arguments[i + 1];

Expand Down
14 changes: 14 additions & 0 deletions types/firestore.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,20 @@ declare namespace FirebaseFirestore {
*/
readonly path: string;

/**
* Retrieves the list of documents in this collection.
*
* The document references returned may include references to "missing
* documents", i.e. document locations that have no document present but
* which contain subcollections with documents. Attempting to read such a
* document reference (e.g. via `.get()` or `.onSnapshot()`) will return a
* `DocumentSnapshot` whose `.exists` property is false.
*
* @return {Promise<DocumentReference[]>} The list of documents in this
* collection.
*/
listDocuments(): Promise<DocumentReference[]>;

/**
* Get a `DocumentReference` for the document within the collection at the
* specified path. If no path is specified, an automatically-generated
Expand Down

0 comments on commit b752fef

Please sign in to comment.