Skip to content

Commit 27bed90

Browse files
Merge
2 parents 25f01e8 + 3361a63 commit 27bed90

File tree

11 files changed

+103
-94
lines changed

11 files changed

+103
-94
lines changed

packages/firestore/exp/index.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,13 @@ export interface SnapshotMetadata {
5252
isEqual(other: SnapshotMetadata): boolean;
5353
}
5454

55-
export type LogLevel = 'debug' | 'error' | 'silent';
55+
export type LogLevel =
56+
| 'debug'
57+
| 'error'
58+
| 'silent'
59+
| 'warn'
60+
| 'info'
61+
| 'verbose';
5662

5763
export function setLogLevel(logLevel: LogLevel): void;
5864

packages/firestore/lite/index.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,13 @@ export interface Settings {
3333
ignoreUndefinedProperties?: boolean;
3434
}
3535

36-
export type LogLevel = 'debug' | 'error' | 'silent';
36+
export type LogLevel =
37+
| 'debug'
38+
| 'error'
39+
| 'silent'
40+
| 'warn'
41+
| 'info'
42+
| 'verbose';
3743

3844
export function setLogLevel(logLevel: LogLevel): void;
3945

packages/firestore/lite/index.node.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,6 @@ export {
3838
getDoc
3939
} from './src/api/reference';
4040

41-
export { FieldPath } from './src/api/field_path';
42-
43-
// TOOD(firestorelite): Add tests when Queries are usable
44-
export { documentId } from './src/api/field_path';
45-
46-
export { DocumentSnapshot, QueryDocumentSnapshot } from './src/api/snapshot';
47-
4841
// TOOD(firestorelite): Add tests when setDoc() is available
4942
export {
5043
FieldValue,
@@ -55,6 +48,10 @@ export {
5548
serverTimestamp
5649
} from './src/api/field_value';
5750

51+
export { DocumentSnapshot, QueryDocumentSnapshot } from './src/api/snapshot';
52+
53+
export { setLogLevel } from '../src/util/log';
54+
5855
export function registerFirestore(): void {
5956
_registerComponent(
6057
new Component(

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import * as firestore from '../../index';
1919

20-
import { tryCast } from './util';
20+
import { cast } from './util';
2121
import {
2222
DOCUMENT_KEY_NAME,
2323
FieldPath as InternalFieldPath
@@ -54,21 +54,20 @@ export class FieldPath implements firestore.FieldPath {
5454
1
5555
);
5656

57-
for (let i = 0; i < fieldNames.length; ++i) {
58-
if (fieldNames[i].length === 0) {
59-
throw new FirestoreError(
60-
Code.INVALID_ARGUMENT,
61-
`Invalid field name at argument $(i + 1). ` +
62-
'Field names must not be empty.'
63-
);
64-
}
57+
const emptyElement = fieldNames.indexOf('');
58+
if (emptyElement !== -1) {
59+
throw new FirestoreError(
60+
Code.INVALID_ARGUMENT,
61+
`Invalid field name at argument $(i + 1). ` +
62+
'Field names must not be empty.'
63+
);
6564
}
6665

6766
this._internalPath = new InternalFieldPath(fieldNames);
6867
}
6968

7069
isEqual(other: firestore.FieldPath): boolean {
71-
const path = tryCast(other, FieldPath);
70+
const path = cast(other, FieldPath);
7271
return this._internalPath.isEqual(path._internalPath);
7372
}
7473
}

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

Lines changed: 17 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,16 @@ import { DocumentKeyReference } from '../../../src/api/user_data_reader';
2424
import { Query as InternalQuery } from '../../../src/core/query';
2525
import { FirebaseFirestore, FirestoreDataConverter } from '../../index';
2626
import { ResourcePath } from '../../../src/model/path';
27-
import { Code, FirestoreError } from '../../../src/util/error';
2827
import { AutoId } from '../../../src/util/misc';
29-
import { tryCast } from './util';
3028
import { DocumentSnapshot } from './snapshot';
3129
import { invokeBatchGetDocumentsRpc } from '../../../src/remote/datastore';
3230
import { hardAssert } from '../../../src/util/assert';
31+
import { cast } from './util';
32+
import {
33+
validateArgType,
34+
validateCollectionPath,
35+
validateDocumentPath
36+
} from '../../../src/util/input_validation';
3337

3438
/**
3539
* A reference to a particular document in a collection in the database.
@@ -170,31 +174,15 @@ export function collection(
170174
parent: firestore.FirebaseFirestore | firestore.DocumentReference<unknown>,
171175
relativePath: string
172176
): CollectionReference<firestore.DocumentData> {
173-
if (relativePath.length === 0) {
174-
throw new FirestoreError(
175-
Code.INVALID_ARGUMENT,
176-
`Invalid path (${relativePath}). Empty paths are not supported.`
177-
);
178-
}
179-
177+
validateArgType('doc', 'non-empty string', 2, relativePath);
180178
const path = ResourcePath.fromString(relativePath);
181179
if (parent instanceof Firestore) {
182-
if (DocumentKey.isDocumentKey(path)) {
183-
throw new FirestoreError(
184-
Code.INVALID_ARGUMENT,
185-
`Invalid path (${path}). Path points to a document.`
186-
);
187-
}
180+
validateCollectionPath(path);
188181
return new CollectionReference(parent, path);
189182
} else {
190-
const doc = tryCast(parent, DocumentReference);
183+
const doc = cast(parent, DocumentReference);
191184
const absolutePath = doc._key.path.child(path);
192-
if (DocumentKey.isDocumentKey(absolutePath)) {
193-
throw new FirestoreError(
194-
Code.INVALID_ARGUMENT,
195-
`Invalid path (${absolutePath}). Path points to a document.`
196-
);
197-
}
185+
validateCollectionPath(absolutePath);
198186
return new CollectionReference(doc.firestore, absolutePath);
199187
}
200188
}
@@ -216,32 +204,15 @@ export function doc<T>(
216204
if (arguments.length === 1) {
217205
relativePath = AutoId.newId();
218206
}
219-
220-
if (!relativePath) {
221-
throw new FirestoreError(
222-
Code.INVALID_ARGUMENT,
223-
`Invalid path (${relativePath}). Empty paths are not supported.`
224-
);
225-
}
226-
227-
const path = ResourcePath.fromString(relativePath);
207+
validateArgType('doc', 'non-empty string', 2, relativePath);
208+
const path = ResourcePath.fromString(relativePath!);
228209
if (parent instanceof Firestore) {
229-
if (!DocumentKey.isDocumentKey(path)) {
230-
throw new FirestoreError(
231-
Code.INVALID_ARGUMENT,
232-
`Invalid path (${path}). Path points to a collection.`
233-
);
234-
}
210+
validateDocumentPath(path);
235211
return new DocumentReference(parent, new DocumentKey(path));
236212
} else {
237-
const coll = tryCast(parent, CollectionReference);
213+
const coll = cast(parent, CollectionReference);
238214
const absolutePath = coll._path.child(path);
239-
if (!DocumentKey.isDocumentKey(absolutePath)) {
240-
throw new FirestoreError(
241-
Code.INVALID_ARGUMENT,
242-
`Invalid path (${absolutePath}). Path points to a collection.`
243-
);
244-
}
215+
validateDocumentPath(absolutePath);
245216
return new DocumentReference(
246217
coll.firestore,
247218
new DocumentKey(absolutePath),
@@ -270,7 +241,7 @@ export function parent<T>(
270241
);
271242
}
272243
} else {
273-
const doc = tryCast(child, DocumentReference) as DocumentReference<T>;
244+
const doc = cast<DocumentReference<T>>(child, DocumentReference);
274245
return new CollectionReference<T>(
275246
doc.firestore,
276247
doc._key.path.popLast(),
@@ -282,7 +253,7 @@ export function parent<T>(
282253
export function getDoc<T>(
283254
reference: firestore.DocumentReference<T>
284255
): Promise<firestore.DocumentSnapshot<T>> {
285-
const ref = tryCast(reference, DocumentReference) as DocumentReference<T>;
256+
const ref = cast(reference, DocumentReference) as DocumentReference<T>;
286257
return ref.firestore._ensureClientConfigured().then(async datastore => {
287258
const result = await invokeBatchGetDocumentsRpc(datastore, [ref._key]);
288259
hardAssert(result.length === 1, 'Expected a single document result');

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

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import * as firestore from '../../index';
2020
import { Firestore } from './database';
2121
import { DocumentReference } from './reference';
2222
import { FieldPath } from './field_path';
23-
import { tryCast } from './util';
23+
import { cast } from './util';
2424
import { DocumentKey } from '../../../src/model/document_key';
2525
import { Document } from '../../../src/model/document';
2626
import { UserDataWriter } from '../../../src/api/user_data_writer';
@@ -60,25 +60,23 @@ export class DocumentSnapshot<T = firestore.DocumentData>
6060
data(): T | undefined {
6161
if (!this._document) {
6262
return undefined;
63-
} else {
63+
} else if (this._converter) {
6464
// We only want to use the converter and create a new DocumentSnapshot
6565
// if a converter has been provided.
66-
if (this._converter) {
67-
const snapshot = new QueryDocumentSnapshot(
68-
this._firestore,
69-
this._key,
70-
this._document
71-
);
72-
return this._converter.fromFirestore(snapshot);
73-
} else {
74-
const userDataWriter = new UserDataWriter(
75-
this._firestore._databaseId,
76-
/* timestampsInSnapshots= */ false,
77-
/* serverTimestampBehavior=*/ 'none',
78-
key => new DocumentReference(this._firestore, key)
79-
);
80-
return userDataWriter.convertValue(this._document.toProto()) as T;
81-
}
66+
const snapshot = new QueryDocumentSnapshot(
67+
this._firestore,
68+
this._key,
69+
this._document
70+
);
71+
return this._converter.fromFirestore(snapshot);
72+
} else {
73+
const userDataWriter = new UserDataWriter(
74+
this._firestore._databaseId,
75+
/* timestampsInSnapshots= */ false,
76+
/* serverTimestampBehavior=*/ 'none',
77+
key => new DocumentReference(this._firestore, key)
78+
);
79+
return userDataWriter.convertValue(this._document.toProto()) as T;
8280
}
8381
}
8482

@@ -119,7 +117,7 @@ export function fieldPathFromArgument(
119117
if (typeof arg === 'string') {
120118
return fieldPathFromDotSeparatedString(methodName, arg);
121119
} else {
122-
const path = tryCast(arg, FieldPath);
120+
const path = cast(arg, FieldPath);
123121
return path._internalPath;
124122
}
125123
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ import { Code, FirestoreError } from '../../../src/util/error';
2323
* This cast is used in the Lite and Full SDK to verify instance types for
2424
* arguments passed to the public API.
2525
*/
26-
export function tryCast<T>(
26+
export function cast<T>(
2727
obj: object,
2828
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2929
constructor: { new (...args: any[]): T }
30-
): T {
30+
): T | never {
3131
if (!(obj instanceof constructor)) {
3232
if (constructor.name === obj.constructor.name) {
3333
throw new FirestoreError(

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,13 @@ describe('doc', () => {
8282
it('validates path', () => {
8383
return withTestDb(db => {
8484
expect(() => doc(db, 'coll')).to.throw(
85-
'Invalid path (coll). Path points to a collection.'
85+
'Invalid document path (coll). Path points to a collection.'
8686
);
8787
expect(() => doc(db, '')).to.throw(
88-
'Invalid path (). Empty paths are not supported.'
88+
'Function doc() requires its second argument to be of type non-empty string, but it was: ""'
8989
);
9090
expect(() => doc(collection(db, 'coll'), 'doc/coll')).to.throw(
91-
'Invalid path (coll/doc/coll). Path points to a collection.'
91+
'Invalid document path (coll/doc/coll). Path points to a collection.'
9292
);
9393
expect(() => doc(db, 'coll//doc')).to.throw(
9494
'Invalid path (coll//doc). Paths must not contain // in them.'
@@ -118,13 +118,15 @@ describe('collection', () => {
118118
it('validates path', () => {
119119
return withTestDb(db => {
120120
expect(() => collection(db, 'coll/doc')).to.throw(
121-
'Invalid path (coll/doc). Path points to a document.'
121+
'Invalid collection path (coll/doc). Path points to a document.'
122122
);
123+
// TODO(firestorelite): Explore returning a more helpful message
124+
// (e.g. "Empty document paths are not supported.")
123125
expect(() => collection(doc(db, 'coll/doc'), '')).to.throw(
124-
'Invalid path (). Empty paths are not supported.'
126+
'Function doc() requires its second argument to be of type non-empty string, but it was: ""'
125127
);
126128
expect(() => collection(doc(db, 'coll/doc'), 'coll/doc')).to.throw(
127-
'Invalid path (coll/doc/coll/doc). Path points to a document.'
129+
'Invalid collection path (coll/doc/coll/doc). Path points to a document.'
128130
);
129131
});
130132
});

packages/firestore/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
"author": "Firebase <[email protected]> (https://firebase.google.com/)",
99
"scripts": {
1010
"prebuild": "tsc -m es2015 --moduleResolution node scripts/*.ts ",
11+
"prebuild:release": "yarn prebuild",
1112
"build": "rollup -c rollup.config.es2017.js && rollup -c rollup.config.es5.js && yarn build:lite",
13+
"build:release": "rollup -c rollup.config.es2017.js && rollup -c rollup.config.es5.js",
1214
"build:deps": "lerna run --scope @firebase/'{app,firestore}' --include-dependencies build",
1315
"build:console": "node tools/console.build.js",
1416
"build:exp": "rollup -c rollup.config.exp.js",
@@ -35,7 +37,7 @@
3537
"test:minified": "(cd ../../integration/firestore ; yarn test)",
3638
"pretest:lite": "yarn build:lite",
3739
"test:lite": "TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'lite/test/**/*.test.ts' --file lite/index.node.ts --config ../../config/mocharc.node.js",
38-
"prepare": "yarn build"
40+
"prepare": "yarn build:release"
3941
},
4042
"main": "dist/index.node.cjs.js",
4143
"main-esm2017": "dist/index.node.esm2017.js",

packages/firestore/src/util/assert.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export function debugCast<T>(
7979
obj: object,
8080
// eslint-disable-next-line @typescript-eslint/no-explicit-any
8181
constructor: { new (...args: any[]): T }
82-
): T {
82+
): T | never {
8383
debugAssert(
8484
obj instanceof constructor,
8585
`Expected type '${constructor.name}', but was '${obj.constructor.name}'`

packages/firestore/src/util/input_validation.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import { fail } from './assert';
1818
import { Code, FirestoreError } from './error';
1919
import { Dict, forEach } from './obj';
20+
import { DocumentKey } from '../model/document_key';
21+
import { ResourcePath } from '../model/path';
2022

2123
/** Types accepted by validateType() and related methods for validation. */
2224
export type ValidationType =
@@ -317,6 +319,32 @@ export function validateStringEnum<T>(
317319
return argument as T;
318320
}
319321

322+
/**
323+
* Validates that `path` refers to a document (indicated by the fact it contains
324+
* an even numbers of segments).
325+
*/
326+
export function validateDocumentPath(path: ResourcePath): void {
327+
if (!DocumentKey.isDocumentKey(path)) {
328+
throw new FirestoreError(
329+
Code.INVALID_ARGUMENT,
330+
`Invalid document path (${path}). Path points to a collection.`
331+
);
332+
}
333+
}
334+
335+
/**
336+
* Validates that `path` refers to a collection (indicated by the fact it
337+
* contains an odd numbers of segments).
338+
*/
339+
export function validateCollectionPath(path: ResourcePath): void {
340+
if (DocumentKey.isDocumentKey(path)) {
341+
throw new FirestoreError(
342+
Code.INVALID_ARGUMENT,
343+
`Invalid collection path (${path}). Path points to a document.`
344+
);
345+
}
346+
}
347+
320348
/** Helper to validate the type of a provided input. */
321349
function validateType(
322350
functionName: string,

0 commit comments

Comments
 (0)