Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add List() API #368

Merged
merged 11 commits into from
Oct 2, 2018
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}`);
* }
* }

This comment was marked as spam.

This comment was marked as spam.

* });
*/
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