Skip to content

Commit 5b7d812

Browse files
Add support for collection(coll) and doc(doc) (#3403)
1 parent 38a12bb commit 5b7d812

File tree

4 files changed

+118
-20
lines changed

4 files changed

+118
-20
lines changed

packages/firestore/exp/index.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ export function collection(
109109
firestore: FirebaseFirestore,
110110
collectionPath: string
111111
): CollectionReference<DocumentData>;
112+
export function collection(
113+
reference: CollectionReference<unknown>,
114+
collectionPath: string
115+
): CollectionReference<DocumentData>;
112116
export function collection(
113117
reference: DocumentReference,
114118
collectionPath: string
@@ -121,6 +125,10 @@ export function doc<T>(
121125
reference: CollectionReference<T>,
122126
documentPath?: string
123127
): DocumentReference<T>;
128+
export function doc(
129+
reference: DocumentReference<unknown>,
130+
documentPath: string
131+
): DocumentReference<DocumentData>;
124132
export function parent(
125133
reference: CollectionReference<unknown>
126134
): DocumentReference<DocumentData> | null;

packages/firestore/lite/index.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ export function collection(
7070
firestore: FirebaseFirestore,
7171
collectionPath: string
7272
): CollectionReference<DocumentData>;
73+
export function collection(
74+
reference: CollectionReference<unknown>,
75+
collectionPath: string
76+
): CollectionReference<DocumentData>;
7377
export function collection(
7478
reference: DocumentReference,
7579
collectionPath: string
@@ -82,6 +86,10 @@ export function doc<T>(
8286
reference: CollectionReference<T>,
8387
documentPath?: string
8488
): DocumentReference<T>;
89+
export function doc(
90+
reference: DocumentReference<unknown>,
91+
documentPath: string
92+
): DocumentReference<DocumentData>;
8593
export function parent(
8694
reference: CollectionReference<unknown>
8795
): DocumentReference<DocumentData> | null;

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

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -289,25 +289,43 @@ export function collection(
289289
firestore: firestore.FirebaseFirestore,
290290
collectionPath: string
291291
): CollectionReference<firestore.DocumentData>;
292+
export function collection(
293+
reference: firestore.CollectionReference<unknown>,
294+
collectionPath: string
295+
): CollectionReference<firestore.DocumentData>;
292296
export function collection(
293297
reference: firestore.DocumentReference,
294298
collectionPath: string
295299
): CollectionReference<firestore.DocumentData>;
296300
export function collection(
297-
parent: firestore.FirebaseFirestore | firestore.DocumentReference<unknown>,
301+
parent:
302+
| firestore.FirebaseFirestore
303+
| firestore.DocumentReference<unknown>
304+
| firestore.CollectionReference<unknown>,
298305
relativePath: string
299306
): CollectionReference<firestore.DocumentData> {
300307
validateArgType('collection', 'non-empty string', 2, relativePath);
301-
const path = ResourcePath.fromString(relativePath);
302308
if (parent instanceof Firestore) {
303-
validateCollectionPath(path);
304-
return new CollectionReference(parent, path, /* converter= */ null);
309+
const absolutePath = ResourcePath.fromString(relativePath);
310+
validateCollectionPath(absolutePath);
311+
return new CollectionReference(parent, absolutePath, /* converter= */ null);
305312
} else {
306-
const doc = cast(parent, DocumentReference);
307-
const absolutePath = doc._key.path.child(path);
313+
if (
314+
!(parent instanceof DocumentReference) &&
315+
!(parent instanceof CollectionReference)
316+
) {
317+
throw new FirestoreError(
318+
Code.INVALID_ARGUMENT,
319+
'Expected first argument to collection() to be a CollectionReference, ' +
320+
'a DocumentReference or FirebaseFirestore'
321+
);
322+
}
323+
const absolutePath = ResourcePath.fromString(
324+
`${parent.path}/${relativePath}`
325+
);
308326
validateCollectionPath(absolutePath);
309327
return new CollectionReference(
310-
doc.firestore,
328+
parent.firestore,
311329
absolutePath,
312330
/* converter= */ null
313331
);
@@ -346,8 +364,15 @@ export function doc<T>(
346364
reference: firestore.CollectionReference<T>,
347365
documentPath?: string
348366
): DocumentReference<T>;
367+
export function doc(
368+
reference: firestore.DocumentReference<unknown>,
369+
documentPath: string
370+
): DocumentReference<firestore.DocumentData>;
349371
export function doc<T>(
350-
parent: firestore.FirebaseFirestore | firestore.CollectionReference<T>,
372+
parent:
373+
| firestore.FirebaseFirestore
374+
| firestore.CollectionReference<T>
375+
| firestore.DocumentReference<unknown>,
351376
relativePath?: string
352377
): DocumentReference {
353378
// We allow omission of 'pathString' but explicitly prohibit passing in both
@@ -356,22 +381,34 @@ export function doc<T>(
356381
relativePath = AutoId.newId();
357382
}
358383
validateArgType('doc', 'non-empty string', 2, relativePath);
359-
const path = ResourcePath.fromString(relativePath!);
384+
360385
if (parent instanceof Firestore) {
361-
validateDocumentPath(path);
386+
const absolutePath = ResourcePath.fromString(relativePath!);
387+
validateDocumentPath(absolutePath);
362388
return new DocumentReference(
363389
parent,
364-
new DocumentKey(path),
390+
new DocumentKey(absolutePath),
365391
/* converter= */ null
366392
);
367393
} else {
368-
const coll = cast(parent, CollectionReference);
369-
const absolutePath = coll._path.child(path);
394+
if (
395+
!(parent instanceof DocumentReference) &&
396+
!(parent instanceof CollectionReference)
397+
) {
398+
throw new FirestoreError(
399+
Code.INVALID_ARGUMENT,
400+
'Expected first argument to collection() to be a CollectionReference, ' +
401+
'a DocumentReference or FirebaseFirestore'
402+
);
403+
}
404+
const absolutePath = ResourcePath.fromString(
405+
`${parent.path}/${relativePath}`
406+
);
370407
validateDocumentPath(absolutePath);
371408
return new DocumentReference(
372-
coll.firestore,
409+
parent.firestore,
373410
new DocumentKey(absolutePath),
374-
coll._converter
411+
parent instanceof CollectionReference ? parent._converter : null
375412
);
376413
}
377414
}

packages/firestore/lite/test/integration.test.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ describe('Firestore', () => {
143143
});
144144

145145
describe('doc', () => {
146-
it('can provide name', () => {
146+
it('can be used relative to Firestore root', () => {
147147
return withTestDb(db => {
148148
const result = doc(db, 'coll/doc');
149149
expect(result).to.be.an.instanceOf(DocumentReference);
@@ -152,6 +152,24 @@ describe('doc', () => {
152152
});
153153
});
154154

155+
it('can be used relative to collection', () => {
156+
return withTestDb(db => {
157+
const result = doc(collection(db, 'coll'), 'doc');
158+
expect(result).to.be.an.instanceOf(DocumentReference);
159+
expect(result.id).to.equal('doc');
160+
expect(result.path).to.equal('coll/doc');
161+
});
162+
});
163+
164+
it('can be relative to doc', () => {
165+
return withTestDb(db => {
166+
const result = doc(doc(db, 'coll/doc'), 'subcoll/subdoc');
167+
expect(result).to.be.an.instanceOf(DocumentReference);
168+
expect(result.id).to.equal('subdoc');
169+
expect(result.path).to.equal('coll/doc/subcoll/subdoc');
170+
});
171+
});
172+
155173
it('validates path', () => {
156174
return withTestDb(db => {
157175
expect(() => doc(db, 'coll')).to.throw(
@@ -179,7 +197,7 @@ describe('doc', () => {
179197
});
180198

181199
describe('collection', () => {
182-
it('can provide name', () => {
200+
it('can be used relative to Firestore root', () => {
183201
return withTestDb(db => {
184202
const result = collection(db, 'coll/doc/subcoll');
185203
expect(result).to.be.an.instanceOf(CollectionReference);
@@ -188,6 +206,24 @@ describe('collection', () => {
188206
});
189207
});
190208

209+
it('can be used relative to collection', () => {
210+
return withTestDb(db => {
211+
const result = collection(collection(db, 'coll'), 'doc/subcoll');
212+
expect(result).to.be.an.instanceOf(CollectionReference);
213+
expect(result.id).to.equal('subcoll');
214+
expect(result.path).to.equal('coll/doc/subcoll');
215+
});
216+
});
217+
218+
it('can be used relative to doc', () => {
219+
return withTestDb(db => {
220+
const result = collection(doc(db, 'coll/doc'), 'subcoll');
221+
expect(result).to.be.an.instanceOf(CollectionReference);
222+
expect(result.id).to.equal('subcoll');
223+
expect(result.path).to.equal('coll/doc/subcoll');
224+
});
225+
});
226+
191227
it('validates path', () => {
192228
return withTestDb(db => {
193229
expect(() => collection(db, 'coll/doc')).to.throw(
@@ -196,7 +232,7 @@ describe('collection', () => {
196232
// TODO(firestorelite): Explore returning a more helpful message
197233
// (e.g. "Empty document paths are not supported.")
198234
expect(() => collection(doc(db, 'coll/doc'), '')).to.throw(
199-
'Function doc() requires its second argument to be of type non-empty string, but it was: ""'
235+
'Function collection() requires its second argument to be of type non-empty string, but it was: ""'
200236
);
201237
expect(() => collection(doc(db, 'coll/doc'), 'coll/doc')).to.throw(
202238
'Invalid collection path (coll/doc/coll/doc). Path points to a document.'
@@ -945,8 +981,7 @@ describe('equality', () => {
945981
const query1a = collRef.orderBy('foo');
946982
const query1b = collRef.orderBy('foo', 'asc');
947983
const query2 = collRef.orderBy('foo', 'desc');
948-
// TODO(firestorelite): Should we allow `collectionRef(collRef, 'a/b')?
949-
const query3 = collection(doc(collRef, 'a'), 'b').orderBy('foo');
984+
const query3 = collection(collRef, 'a/b').orderBy('foo');
950985

951986
expect(queryEqual(query1a, query1b)).to.be.true;
952987
expect(queryEqual(query1a, query2)).to.be.false;
@@ -1029,6 +1064,16 @@ describe('withConverter() support', () => {
10291064
});
10301065
});
10311066

1067+
it('keeps the converter when calling parent() with a DocumentReference', () => {
1068+
return withTestDb(async db => {
1069+
const coll = doc(db, 'root/doc').withConverter(postConverter);
1070+
const typedColl = parent(coll)!;
1071+
expect(
1072+
refEqual(typedColl, collection(db, 'root').withConverter(postConverter))
1073+
).to.be.true;
1074+
});
1075+
});
1076+
10321077
it('drops the converter when calling parent() with a CollectionReference', () => {
10331078
return withTestDb(async db => {
10341079
const coll = collection(db, 'root/doc/parent').withConverter(

0 commit comments

Comments
 (0)