Skip to content

Commit ca44064

Browse files
Add CollectionReference
1 parent 084fdcf commit ca44064

File tree

5 files changed

+417
-1
lines changed

5 files changed

+417
-1
lines changed

packages/firestore/lite/index.node.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,22 @@ import { Firestore } from './src/api/database';
2020
import { version } from '../package.json';
2121
import { Component, ComponentType } from '@firebase/component';
2222

23+
import '../src/platform_node/node_init';
24+
2325
export {
2426
Firestore,
2527
initializeFirestore,
2628
getFirestore
2729
} from './src/api/database';
2830

29-
export { DocumentReference } from './src/api/reference';
31+
export {
32+
DocumentReference,
33+
Query,
34+
CollectionReference,
35+
collection,
36+
doc,
37+
parent
38+
} from './src/api/reference';
3039

3140
export function registerFirestore(): void {
3241
_registerComponent(

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

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ import * as firestore from '../../index';
2020
import { DocumentKey } from '../../../src/model/document_key';
2121
import { Firestore } from './database';
2222
import { DocumentKeyReference } from '../../../src/api/user_data_reader';
23+
import { Query as InternalQuery } from '../../../src/core/query';
24+
import { FirebaseFirestore, FirestoreDataConverter } from '../../index';
25+
import { ResourcePath } from '../../../src/model/path';
26+
import { Code, FirestoreError } from '../../../src/util/error';
27+
import { AutoId } from '../../../src/util/misc';
28+
import { tryCast } from './util';
2329

2430
/**
2531
* A reference to a particular document in a collection in the database.
@@ -49,3 +55,222 @@ export class DocumentReference<T = firestore.DocumentData>
4955
return new DocumentReference<U>(this.firestore, this._key, converter);
5056
}
5157
}
58+
59+
export class Query<T = firestore.DocumentData> implements firestore.Query<T> {
60+
constructor(
61+
readonly firestore: Firestore,
62+
readonly _query: InternalQuery,
63+
readonly _converter?: FirestoreDataConverter<T>
64+
) {}
65+
66+
where(
67+
fieldPath: string | firestore.FieldPath,
68+
opStr: firestore.WhereFilterOp,
69+
value: unknown
70+
): firestore.Query<T> {
71+
// TODO(firestorelite): Implement
72+
throw new Error('Not implemented');
73+
}
74+
75+
orderBy(
76+
fieldPath: string | firestore.FieldPath,
77+
directionStr?: firestore.OrderByDirection
78+
): firestore.Query<T> {
79+
// TODO(firestorelite): Implement
80+
throw new Error('Not implemented');
81+
}
82+
83+
limit(limit: number): firestore.Query<T> {
84+
// TODO(firestorelite): Implement
85+
throw new Error('Not implemented');
86+
}
87+
88+
limitToLast(limit: number): firestore.Query<T> {
89+
// TODO(firestorelite): Implement
90+
throw new Error('Not implemented');
91+
}
92+
93+
startAfter(
94+
docOrField: unknown | firestore.DocumentSnapshot<unknown>,
95+
...fields: unknown[]
96+
): firestore.Query<T> {
97+
// TODO(firestorelite): Implement
98+
throw new Error('Not implemented');
99+
}
100+
101+
startAt(
102+
docOrField: unknown | firestore.DocumentSnapshot<unknown>,
103+
...fields: unknown[]
104+
): firestore.Query<T> {
105+
// TODO(firestorelite): Implement
106+
throw new Error('Not implemented');
107+
}
108+
109+
endAt(
110+
docOrField: unknown | firestore.DocumentSnapshot<unknown>,
111+
...fields: unknown[]
112+
): firestore.Query<T> {
113+
// TODO(firestorelite): Implement
114+
throw new Error('Not implemented');
115+
}
116+
117+
endBefore(
118+
docOrField: unknown | firestore.DocumentSnapshot<unknown>,
119+
...fields: unknown[]
120+
): firestore.Query<T> {
121+
// TODO(firestorelite): Implement
122+
throw new Error('Not implemented');
123+
}
124+
125+
withConverter<U>(
126+
converter: firestore.FirestoreDataConverter<U>
127+
): firestore.Query<U> {
128+
return new Query<U>(this.firestore, this._query, converter);
129+
}
130+
}
131+
132+
export class CollectionReference<T = firestore.DocumentData> extends Query<T>
133+
implements firestore.CollectionReference<T> {
134+
constructor(
135+
readonly firestore: Firestore,
136+
readonly _path: ResourcePath,
137+
readonly _converter?: firestore.FirestoreDataConverter<T>
138+
) {
139+
super(firestore, InternalQuery.atPath(_path), _converter);
140+
}
141+
142+
get id(): string {
143+
return this._query.path.lastSegment();
144+
}
145+
146+
get path(): string {
147+
return this._query.path.canonicalString();
148+
}
149+
150+
withConverter<U>(
151+
converter: firestore.FirestoreDataConverter<U>
152+
): firestore.CollectionReference<U> {
153+
return new CollectionReference<U>(this.firestore, this._path, converter);
154+
}
155+
}
156+
157+
export function collection(
158+
firestore: FirebaseFirestore,
159+
collectionPath: string
160+
): CollectionReference<firestore.DocumentData>;
161+
export function collection(
162+
reference: DocumentReference,
163+
collectionPath: string
164+
): CollectionReference<firestore.DocumentData>;
165+
export function collection(
166+
parent: firestore.FirebaseFirestore | firestore.DocumentReference<unknown>,
167+
relativePath: string
168+
): CollectionReference<firestore.DocumentData> {
169+
if (relativePath.length === 0) {
170+
throw new FirestoreError(
171+
Code.INVALID_ARGUMENT,
172+
`Invalid path (${relativePath}). Empty paths are not supported.`
173+
);
174+
}
175+
176+
const path = ResourcePath.fromString(relativePath);
177+
if (parent instanceof Firestore) {
178+
if (DocumentKey.isDocumentKey(path)) {
179+
throw new FirestoreError(
180+
Code.INVALID_ARGUMENT,
181+
`Invalid path (${path}). Path points to a document.`
182+
);
183+
}
184+
return new CollectionReference(parent, path);
185+
} else {
186+
const doc = tryCast(parent, DocumentReference);
187+
const absolutePath = doc._key.path.child(path);
188+
if (DocumentKey.isDocumentKey(absolutePath)) {
189+
throw new FirestoreError(
190+
Code.INVALID_ARGUMENT,
191+
`Invalid path (${absolutePath}). Path points to a document.`
192+
);
193+
}
194+
return new CollectionReference(doc.firestore, absolutePath);
195+
}
196+
}
197+
198+
export function doc(
199+
firestore: FirebaseFirestore,
200+
documentPath: string
201+
): DocumentReference<firestore.DocumentData>;
202+
export function doc<T>(
203+
reference: CollectionReference<T>,
204+
documentPath?: string
205+
): DocumentReference<T>;
206+
export function doc<T>(
207+
parent: firestore.FirebaseFirestore | firestore.CollectionReference<T>,
208+
relativePath?: string
209+
): DocumentReference {
210+
// We allow omission of 'pathString' but explicitly prohibit passing in both
211+
// 'undefined' and 'null'.
212+
if (arguments.length === 1) {
213+
relativePath = AutoId.newId();
214+
}
215+
216+
if (!relativePath) {
217+
throw new FirestoreError(
218+
Code.INVALID_ARGUMENT,
219+
`Invalid path (${relativePath}). Empty paths are not supported.`
220+
);
221+
}
222+
223+
const path = ResourcePath.fromString(relativePath);
224+
if (parent instanceof Firestore) {
225+
if (!DocumentKey.isDocumentKey(path)) {
226+
throw new FirestoreError(
227+
Code.INVALID_ARGUMENT,
228+
`Invalid path (${path}). Path points to a collection.`
229+
);
230+
}
231+
return new DocumentReference(parent, new DocumentKey(path));
232+
} else {
233+
const coll = tryCast(parent, CollectionReference);
234+
const absolutePath = coll._path.child(path);
235+
if (!DocumentKey.isDocumentKey(absolutePath)) {
236+
throw new FirestoreError(
237+
Code.INVALID_ARGUMENT,
238+
`Invalid path (${absolutePath}). Path points to a collection.`
239+
);
240+
}
241+
return new DocumentReference(
242+
coll.firestore,
243+
new DocumentKey(absolutePath),
244+
coll._converter
245+
);
246+
}
247+
}
248+
249+
export function parent(
250+
reference: CollectionReference<unknown>
251+
): DocumentReference<firestore.DocumentData> | null;
252+
export function parent<T>(
253+
reference: DocumentReference<T>
254+
): CollectionReference<T>;
255+
export function parent<T>(
256+
child: firestore.CollectionReference<unknown> | firestore.DocumentReference<T>
257+
): DocumentReference<firestore.DocumentData> | CollectionReference<T> | null {
258+
if (child instanceof CollectionReference) {
259+
const parentPath = child._path.popLast();
260+
if (parentPath.isEmpty()) {
261+
return null;
262+
} else {
263+
return new DocumentReference(
264+
child.firestore,
265+
new DocumentKey(parentPath)
266+
);
267+
}
268+
} else {
269+
const doc = tryCast(child, DocumentReference) as DocumentReference<T>;
270+
return new CollectionReference<T>(
271+
doc.firestore,
272+
doc._key.path.popLast(),
273+
doc._converter
274+
);
275+
}
276+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* @license
3+
* Copyright 2020 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { Code, FirestoreError } from '../../../src/util/error';
19+
20+
/**
21+
* Casts `obj` to `T`. Throws if `obj` is not an instance of `T`.
22+
*
23+
* This cast is used in the Lite and Full SDK to verify instance types for
24+
* arguments passed to the public API.
25+
*/
26+
export function tryCast<T>(
27+
obj: object,
28+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
29+
constructor: { new (...args: any[]): T }
30+
): T {
31+
if (!(obj instanceof constructor)) {
32+
if (constructor.name === obj.constructor.name) {
33+
throw new FirestoreError(
34+
Code.INVALID_ARGUMENT,
35+
'Type does not match the expected instance. Did you pass ' +
36+
`'${constructor.name}' from a different Firestore SDK?`
37+
);
38+
} else {
39+
throw new FirestoreError(
40+
Code.INVALID_ARGUMENT,
41+
`Expected type '${constructor.name}', but was '${obj.constructor.name}'`
42+
);
43+
}
44+
}
45+
return obj as T;
46+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @license
3+
* Copyright 2020 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { initializeApp } from '@firebase/app-exp';
19+
20+
import { Firestore, getFirestore } from '../src/api/database';
21+
22+
// eslint-disable-next-line @typescript-eslint/no-require-imports
23+
const PROJECT_CONFIG = require('../../../../config/project.json');
24+
25+
export const DEFAULT_PROJECT_ID = PROJECT_CONFIG.projectId;
26+
27+
let appCount = 0;
28+
29+
export async function withTestDb(
30+
fn: (db: Firestore) => void | Promise<void>
31+
): Promise<void> {
32+
const app = initializeApp(
33+
{ apiKey: 'fake-api-key', projectId: DEFAULT_PROJECT_ID },
34+
'test-app-' + appCount++
35+
);
36+
37+
const firestore = getFirestore(app);
38+
return fn(firestore);
39+
}

0 commit comments

Comments
 (0)