Skip to content

Commit 52f8e54

Browse files
authored
Merge branch 'master' into mrschmidt-fixallthethings
2 parents a7b3a79 + e125588 commit 52f8e54

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1840
-194
lines changed

.github/CODEOWNERS

+3
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ packages/auth-types @bojeil-google @wti806
2929

3030
# Testing Code
3131
packages/testing @tonymeng @ryanpbrewster
32+
33+
# RxFire Code
34+
packages/rxfire @davideast

packages/firebase/index.d.ts

+5-8
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ declare namespace firebase {
125125
declare namespace firebase.app {
126126
interface App {
127127
auth(): firebase.auth.Auth;
128-
database(): firebase.database.Database;
128+
database(url?: string): firebase.database.Database;
129129
delete(): Promise<any>;
130130
messaging(): firebase.messaging.Messaging;
131131
name: string;
@@ -1559,10 +1559,9 @@ declare namespace firebase.firestore {
15591559

15601560
/**
15611561
* Filter conditions in a `Query.where()` clause are specified using the
1562-
* strings '<', '<=', '==', '>=', and '>'.
1562+
* strings '<', '<=', '==', '>=', '>', and 'array-contains'.
15631563
*/
1564-
// TODO(array-features): Add 'array-contains' once backend support lands.
1565-
export type WhereFilterOp = '<' | '<=' | '==' | '>=' | '>';
1564+
export type WhereFilterOp = '<' | '<=' | '==' | '>=' | '>' | 'array-contains';
15661565

15671566
/**
15681567
* A `Query` refers to a Query which you can read or listen to. You can also
@@ -1942,8 +1941,7 @@ declare namespace firebase.firestore {
19421941
* @param elements The elements to union into the array.
19431942
* @return The FieldValue sentinel for use in a call to set() or update().
19441943
*/
1945-
// TODO(array-features): Expose this once backend support lands.
1946-
//static arrayUnion(...elements: any[]): FieldValue;
1944+
static arrayUnion(...elements: any[]): FieldValue;
19471945

19481946
/**
19491947
* Returns a special value that can be used with set() or update() that tells
@@ -1955,8 +1953,7 @@ declare namespace firebase.firestore {
19551953
* @param elements The elements to remove from the array.
19561954
* @return The FieldValue sentinel for use in a call to set() or update().
19571955
*/
1958-
// TODO(array-features): Expose this once backend support lands.
1959-
//static arrayRemove(...elements: any[]): FieldValue;
1956+
static arrayRemove(...elements: any[]): FieldValue;
19601957

19611958
/**
19621959
* Returns true if this `FieldValue` is equal to the provided one.

packages/firestore-types/index.d.ts

+4-7
Original file line numberDiff line numberDiff line change
@@ -847,10 +847,9 @@ export type OrderByDirection = 'desc' | 'asc';
847847

848848
/**
849849
* Filter conditions in a `Query.where()` clause are specified using the
850-
* strings '<', '<=', '==', '>=', and '>'.
850+
* strings '<', '<=', '==', '>=', '>', and 'array-contains'.
851851
*/
852-
// TODO(array-features): Add 'array-contains' once backend support lands.
853-
export type WhereFilterOp = '<' | '<=' | '==' | '>=' | '>';
852+
export type WhereFilterOp = '<' | '<=' | '==' | '>=' | '>' | 'array-contains';
854853

855854
/**
856855
* A `Query` refers to a Query which you can read or listen to. You can also
@@ -1226,8 +1225,7 @@ export class FieldValue {
12261225
* @param elements The elements to union into the array.
12271226
* @return The FieldValue sentinel for use in a call to set() or update().
12281227
*/
1229-
// TODO(array-features): Expose this once backend support lands.
1230-
//static arrayUnion(...elements: any[]): FieldValue;
1228+
static arrayUnion(...elements: any[]): FieldValue;
12311229

12321230
/**
12331231
* Returns a special value that can be used with set() or update() that tells
@@ -1239,8 +1237,7 @@ export class FieldValue {
12391237
* @param elements The elements to remove from the array.
12401238
* @return The FieldValue sentinel for use in a call to set() or update().
12411239
*/
1242-
// TODO(array-features): Expose this once backend support lands.
1243-
//static arrayRemove(...elements: any[]): FieldValue;
1240+
static arrayRemove(...elements: any[]): FieldValue;
12441241

12451242
/**
12461243
* Returns true if this `FieldValue` is equal to the provided one.

packages/firestore/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
# Unreleased
2+
- [feature] Added `firebase.firestore.FieldValue.arrayUnion()` and
3+
`firebase.firestore.FieldValue.arrayRemove()` to atomically add and remove
4+
elements from an array field in a document.
5+
- [feature] Added `'array-contains'` query operator for use with `.where()` to
6+
find documents where an array field contains a specific element.
27

38
# 0.5.0
49
- [changed] Merged the `includeQueryMetadataChanges` and

packages/firestore/src/api/database.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import { FirestoreClient } from '../core/firestore_client';
2525
import {
2626
Bound,
2727
Direction,
28-
fieldFilter,
2928
Filter,
3029
OrderBy,
3130
Query as InternalQuery,
@@ -1333,7 +1332,7 @@ export class Query implements firestore.Query {
13331332
value
13341333
);
13351334
}
1336-
const filter = fieldFilter(fieldPath, relationOp, fieldValue);
1335+
const filter = Filter.create(fieldPath, relationOp, fieldValue);
13371336
this.validateNewFilter(filter);
13381337
return new Query(this._query.addFilter(filter), this.firestore);
13391338
}

packages/firestore/src/api/field_value.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,14 @@ export abstract class FieldValueImpl implements firestore.FieldValue {
3636
return ServerTimestampFieldValueImpl.instance;
3737
}
3838

39-
// TODO(array-features): Expose this once backend support lands.
40-
static _arrayUnion(...elements: AnyJs[]): FieldValueImpl {
39+
static arrayUnion(...elements: AnyJs[]): FieldValueImpl {
4140
validateAtLeastNumberOfArgs('FieldValue.arrayUnion', arguments, 1);
4241
// NOTE: We don't actually parse the data until it's used in set() or
4342
// update() since we need access to the Firestore instance.
4443
return new ArrayUnionFieldValueImpl(elements);
4544
}
4645

47-
// TODO(array-features): Expose this once backend support lands.
48-
static _arrayRemove(...elements: AnyJs[]): FieldValueImpl {
46+
static arrayRemove(...elements: AnyJs[]): FieldValueImpl {
4947
validateAtLeastNumberOfArgs('FieldValue.arrayRemove', arguments, 1);
5048
// NOTE: We don't actually parse the data until it's used in set() or
5149
// update() since we need access to the Firestore instance.

packages/firestore/src/core/query.ts

+41-39
Original file line numberDiff line numberDiff line change
@@ -390,10 +390,35 @@ export class Query {
390390
}
391391
}
392392

393-
export interface Filter {
394-
matches(doc: Document): boolean;
395-
canonicalId(): string;
396-
isEqual(filter: Filter): boolean;
393+
export abstract class Filter {
394+
abstract matches(doc: Document): boolean;
395+
abstract canonicalId(): string;
396+
abstract isEqual(filter: Filter): boolean;
397+
398+
/**
399+
* Creates a filter based on the provided arguments.
400+
*/
401+
static create(field: FieldPath, op: RelationOp, value: FieldValue): Filter {
402+
if (value.isEqual(NullValue.INSTANCE)) {
403+
if (op !== RelationOp.EQUAL) {
404+
throw new FirestoreError(
405+
Code.INVALID_ARGUMENT,
406+
'Invalid query. You can only perform equals comparisons on null.'
407+
);
408+
}
409+
return new NullFilter(field);
410+
} else if (value.isEqual(DoubleValue.NAN)) {
411+
if (op !== RelationOp.EQUAL) {
412+
throw new FirestoreError(
413+
Code.INVALID_ARGUMENT,
414+
'Invalid query. You can only perform equals comparisons on NaN.'
415+
);
416+
}
417+
return new NanFilter(field);
418+
} else {
419+
return new RelationFilter(field, op, value);
420+
}
421+
}
397422
}
398423

399424
export class RelationOp {
@@ -434,12 +459,14 @@ export class RelationOp {
434459
}
435460
}
436461

437-
export class RelationFilter implements Filter {
462+
export class RelationFilter extends Filter {
438463
constructor(
439464
public field: FieldPath,
440465
public op: RelationOp,
441466
public value: FieldValue
442-
) {}
467+
) {
468+
super();
469+
}
443470

444471
matches(doc: Document): boolean {
445472
if (this.field.isKeyField()) {
@@ -528,8 +555,10 @@ export class RelationFilter implements Filter {
528555
/**
529556
* Filter that matches 'null' values.
530557
*/
531-
export class NullFilter implements Filter {
532-
constructor(public field: FieldPath) {}
558+
export class NullFilter extends Filter {
559+
constructor(public field: FieldPath) {
560+
super();
561+
}
533562

534563
matches(doc: Document): boolean {
535564
const val = doc.field(this.field);
@@ -556,8 +585,10 @@ export class NullFilter implements Filter {
556585
/**
557586
* Filter that matches 'NaN' values.
558587
*/
559-
export class NanFilter implements Filter {
560-
constructor(public field: FieldPath) {}
588+
export class NanFilter extends Filter {
589+
constructor(public field: FieldPath) {
590+
super();
591+
}
561592

562593
matches(doc: Document): boolean {
563594
const val = doc.field(this.field).value();
@@ -581,35 +612,6 @@ export class NanFilter implements Filter {
581612
}
582613
}
583614

584-
/**
585-
* Creates a filter based on the provided arguments.
586-
*/
587-
export function fieldFilter(
588-
field: FieldPath,
589-
op: RelationOp,
590-
value: FieldValue
591-
): Filter {
592-
if (value.isEqual(NullValue.INSTANCE)) {
593-
if (op !== RelationOp.EQUAL) {
594-
throw new FirestoreError(
595-
Code.INVALID_ARGUMENT,
596-
'Invalid query. You can only perform equals ' + 'comparisons on null.'
597-
);
598-
}
599-
return new NullFilter(field);
600-
} else if (value.isEqual(DoubleValue.NAN)) {
601-
if (op !== RelationOp.EQUAL) {
602-
throw new FirestoreError(
603-
Code.INVALID_ARGUMENT,
604-
'Invalid query. You can only perform equals ' + 'comparisons on NaN.'
605-
);
606-
}
607-
return new NanFilter(field);
608-
} else {
609-
return new RelationFilter(field, op, value);
610-
}
611-
}
612-
613615
/**
614616
* The direction of sorting in an order by.
615617
*/

packages/firestore/src/platform_node/grpc_connection.ts

+25-33
Original file line numberDiff line numberDiff line change
@@ -48,45 +48,32 @@ type UnaryRpc<Req, Resp> = (
4848
callback: (err?: grpc.ServiceError, resp?: Resp) => void
4949
) => grpc.ClientUnaryCall;
5050

51-
function createHeaders(databaseInfo: DatabaseInfo, token: Token | null): {} {
51+
function createMetadata(
52+
databaseInfo: DatabaseInfo,
53+
token: Token | null
54+
): grpc.Metadata {
5255
assert(
5356
token === null || token.type === 'OAuth',
5457
'If provided, token must be OAuth'
5558
);
5659

57-
const channelCredentials = databaseInfo.ssl
58-
? grpc.credentials.createSsl()
59-
: grpc.credentials.createInsecure();
60-
61-
const callCredentials = grpc.credentials.createFromMetadataGenerator(
62-
(
63-
context: { service_url: string },
64-
cb: (error: Error | null, metadata?: grpc.Metadata) => void
65-
) => {
66-
const metadata = new grpc.Metadata();
67-
if (token) {
68-
for (const header in token.authHeaders) {
69-
if (token.authHeaders.hasOwnProperty(header)) {
70-
metadata.set(header, token.authHeaders[header]);
71-
}
72-
}
60+
const metadata = new grpc.Metadata();
61+
if (token) {
62+
for (const header in token.authHeaders) {
63+
if (token.authHeaders.hasOwnProperty(header)) {
64+
metadata.set(header, token.authHeaders[header]);
7365
}
74-
metadata.set('x-goog-api-client', X_GOOG_API_CLIENT_VALUE);
75-
// This header is used to improve routing and project isolation by the
76-
// backend.
77-
metadata.set(
78-
'google-cloud-resource-prefix',
79-
`projects/${databaseInfo.databaseId.projectId}/` +
80-
`databases/${databaseInfo.databaseId.database}`
81-
);
82-
cb(null, metadata);
8366
}
67+
}
68+
metadata.set('x-goog-api-client', X_GOOG_API_CLIENT_VALUE);
69+
// This header is used to improve routing and project isolation by the
70+
// backend.
71+
metadata.set(
72+
'google-cloud-resource-prefix',
73+
`projects/${databaseInfo.databaseId.projectId}/` +
74+
`databases/${databaseInfo.databaseId.database}`
8475
);
85-
86-
return grpc.credentials.combineChannelCredentials(
87-
channelCredentials,
88-
callCredentials
89-
);
76+
return metadata;
9077
}
9178

9279
// The type of these stubs is dynamically generated by the GRPC runtime
@@ -122,7 +109,9 @@ export class GrpcConnection implements Connection {
122109
private ensureActiveStub(token: Token | null): GeneratedGrpcStub {
123110
if (!this.cachedStub || !this.sameToken(this.cachedStub.token, token)) {
124111
log.debug(LOG_TAG, 'Creating Firestore stub.');
125-
const credentials = createHeaders(this.databaseInfo, token);
112+
const credentials = this.databaseInfo.ssl
113+
? grpc.credentials.createSsl()
114+
: grpc.credentials.createInsecure();
126115
this.cachedStub = {
127116
stub: new this.firestore.Firestore(this.databaseInfo.host, credentials),
128117
token
@@ -142,7 +131,10 @@ export class GrpcConnection implements Connection {
142131
const stub = this.ensureActiveStub(token);
143132
const rpc = stub[rpcMethod];
144133
assert(rpc != null, 'Unknown RPC: ' + rpcName);
145-
return rpc.bind(stub);
134+
135+
const metadata = createMetadata(this.databaseInfo, token);
136+
const f = rpc.bind(stub);
137+
return (...args) => f(...args, metadata);
146138
}
147139

148140
invokeRPC<Req, Resp>(

0 commit comments

Comments
 (0)