Skip to content

Commit d134c39

Browse files
authored
Merge pull request #933 from firebase/rxfire-infra
WIP: RxFire
2 parents 189bcea + fb759b4 commit d134c39

18 files changed

+909
-0
lines changed

packages/rxfire/.gitignore

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/rxfire*.js
2+
/rxfire*.map
3+
/rxfire*.gz
4+
/rxfire*.tgz
5+
6+
# generated declaration files
7+
# declaration files currently break testing builds
8+
# TODO: Fix TypeScript build setup before release
9+
auth/index.d.ts
10+
firestore/collection/index.d.ts
11+
firestore/document/index.d.ts
12+
firestore/fromRef.d.ts
13+
firestore/index.d.ts
14+
functions/index.d.ts
15+
index.d.ts
16+
storage/index.d.ts
17+
test/index.d.ts
18+
test/firestore.test.d.ts

packages/rxfire/auth/index.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Copyright 2018 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { auth, User } from 'firebase/app';
18+
import { Observable, from, of } from 'rxjs';
19+
import { switchMap } from 'rxjs/operators';
20+
21+
/**
22+
* Create an observable of authentication state. The observer is only
23+
* triggered on sign-in or sign-out.
24+
* @param auth firebase.auth.Auth
25+
*/
26+
export function authState(auth: auth.Auth): Observable<User> {
27+
return new Observable(subscriber => {
28+
const unsubscribe = auth.onAuthStateChanged(subscriber);
29+
return { unsubscribe };
30+
});
31+
}
32+
33+
/**
34+
* Create an observable of user state. The observer is triggered for sign-in,
35+
* sign-out, and token refresh events
36+
* @param auth firebase.auth.Auth
37+
*/
38+
export function user(auth: auth.Auth): Observable<User> {
39+
return new Observable(subscriber => {
40+
const unsubscribe = auth.onIdTokenChanged(subscriber);
41+
return { unsubscribe };
42+
});
43+
}
44+
45+
/**
46+
* Create an observable of idToken state. The observer is triggered for sign-in,
47+
* sign-out, and token refresh events
48+
* @param auth firebase.auth.Auth
49+
*/
50+
export function idToken(auth: auth.Auth) {
51+
return user(auth).pipe(
52+
switchMap(user => (user ? from(user.getIdToken()) : of(null)))
53+
);
54+
}

packages/rxfire/auth/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "rxfire/auth",
3+
"main": "dist/index.cjs.js",
4+
"module": "dist/index.esm.js"
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/**
2+
* Copyright 2018 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { firestore } from 'firebase/app';
18+
import { fromCollectionRef } from '../fromRef';
19+
import { Observable } from 'rxjs';
20+
import { map, filter, scan } from 'rxjs/operators';
21+
22+
const ALL_EVENTS: firestore.DocumentChangeType[] = [
23+
'added',
24+
'modified',
25+
'removed'
26+
];
27+
28+
/**
29+
* Create an operator that determines if a the stream of document changes
30+
* are specified by the event filter. If the document change type is not
31+
* in specified events array, it will not be emitted.
32+
*/
33+
const filterEvents = (events?: firestore.DocumentChangeType[]) =>
34+
filter((changes: firestore.DocumentChange[]) => {
35+
let hasChange = false;
36+
for (let i = 0; i < changes.length; i++) {
37+
const change = changes[i];
38+
if (events.indexOf(change.type) >= 0) {
39+
hasChange = true;
40+
break;
41+
}
42+
}
43+
return hasChange;
44+
});
45+
46+
/**
47+
* Create an operator that filters out empty changes. We provide the
48+
* ability to filter on events, which means all changes can be filtered out.
49+
* This creates an empty array and would be incorrect to emit.
50+
*/
51+
const filterEmpty = filter(
52+
(changes: firestore.DocumentChange[]) => changes.length > 0
53+
);
54+
55+
/**
56+
* Creates a new sorted array from a new change.
57+
* @param combined
58+
* @param change
59+
*/
60+
function processIndividualChange(
61+
combined: firestore.DocumentChange[],
62+
change: firestore.DocumentChange
63+
): firestore.DocumentChange[] {
64+
switch (change.type) {
65+
case 'added':
66+
if (
67+
combined[change.newIndex] &&
68+
combined[change.newIndex].doc.id == change.doc.id
69+
) {
70+
// Skip duplicate emissions. This is rare.
71+
// TODO: Investigate possible bug in SDK.
72+
} else {
73+
combined.splice(change.newIndex, 0, change);
74+
}
75+
break;
76+
case 'modified':
77+
// When an item changes position we first remove it
78+
// and then add it's new position
79+
if (change.oldIndex !== change.newIndex) {
80+
combined.splice(change.oldIndex, 1);
81+
combined.splice(change.newIndex, 0, change);
82+
} else {
83+
combined[change.newIndex] = change;
84+
}
85+
break;
86+
case 'removed':
87+
combined.splice(change.oldIndex, 1);
88+
break;
89+
}
90+
return combined;
91+
}
92+
93+
/**
94+
* Combines the total result set from the current set of changes from an incoming set
95+
* of changes.
96+
* @param current
97+
* @param changes
98+
* @param events
99+
*/
100+
function processDocumentChanges(
101+
current: firestore.DocumentChange[],
102+
changes: firestore.DocumentChange[],
103+
events: firestore.DocumentChangeType[] = ALL_EVENTS
104+
) {
105+
changes.forEach(change => {
106+
// skip unwanted change types
107+
if (events.indexOf(change.type) > -1) {
108+
current = processIndividualChange(current, change);
109+
}
110+
});
111+
return current;
112+
}
113+
114+
/**
115+
* Return a stream of document changes on a query. These results are not in sort order but in
116+
* order of occurence.
117+
* @param query
118+
*/
119+
export function docChanges(
120+
query: firestore.Query,
121+
events: firestore.DocumentChangeType[] = ALL_EVENTS
122+
) {
123+
return fromCollectionRef(query).pipe(
124+
map(snapshot => snapshot.docChanges()),
125+
filterEvents(events),
126+
filterEmpty
127+
);
128+
}
129+
130+
/**
131+
* Return a stream of document snapshots on a query. These results are in sort order.
132+
* @param query
133+
*/
134+
export function collection(query: firestore.Query) {
135+
return fromCollectionRef(query).pipe(map(changes => changes.docs));
136+
}
137+
138+
/**
139+
* Return a stream of document changes on a query. These results are in sort order.
140+
* @param query
141+
*/
142+
export function sortedChanges(
143+
query: firestore.Query,
144+
events?: firestore.DocumentChangeType[]
145+
) {
146+
return docChanges(query, events).pipe(
147+
scan(
148+
(
149+
current: firestore.DocumentChange[],
150+
changes: firestore.DocumentChange[]
151+
) => processDocumentChanges(current, changes, events),
152+
[]
153+
)
154+
);
155+
}
156+
157+
/**
158+
* Create a stream of changes as they occur it time. This method is similar
159+
* to docChanges() but it collects each event in an array over time.
160+
*/
161+
export function auditTrail(
162+
query: firestore.Query,
163+
events?: firestore.DocumentChangeType[]
164+
): Observable<firestore.DocumentChange[]> {
165+
return docChanges(query, events).pipe(
166+
scan((current, action) => [...current, ...action], [])
167+
);
168+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Copyright 2018 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { firestore } from 'firebase/app';
18+
import { fromDocRef } from '../fromRef';
19+
20+
export function doc(ref: firestore.DocumentReference) {
21+
return fromDocRef(ref);
22+
}

packages/rxfire/firestore/fromRef.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Copyright 2018 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { firestore } from 'firebase/app';
18+
import { Observable } from 'rxjs';
19+
20+
function _fromRef(ref: any): Observable<any> {
21+
return new Observable(subscriber => {
22+
const unsubscribe = ref.onSnapshot(subscriber);
23+
return { unsubscribe };
24+
});
25+
}
26+
27+
export function fromRef(ref: firestore.DocumentReference | firestore.Query) {
28+
return _fromRef(ref);
29+
}
30+
31+
export function fromDocRef(
32+
ref: firestore.DocumentReference
33+
): Observable<firestore.DocumentSnapshot> {
34+
return fromRef(ref);
35+
}
36+
37+
export function fromCollectionRef(
38+
ref: firestore.Query
39+
): Observable<firestore.QuerySnapshot> {
40+
return fromRef(ref);
41+
}

packages/rxfire/firestore/index.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Copyright 2018 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
export * from './collection';
18+
export * from './document';
19+
export * from './fromRef';
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "rxfire/firestore",
3+
"main": "dist/index.cjs.js",
4+
"module": "dist/index.esm.js"
5+
}

packages/rxfire/functions/index.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Copyright 2018 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { functions } from 'firebase/app';
18+
import { Observable, from } from 'rxjs';
19+
import { map } from 'rxjs/operators';
20+
21+
export function httpsCallable<T = any, R = any>(
22+
functions: functions.Functions,
23+
name: string
24+
) {
25+
const callable = functions.httpsCallable(name);
26+
return (data: T) => {
27+
return from(callable(data)).pipe(map(r => r.data as R));
28+
};
29+
}
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "rxfire/functions",
3+
"main": "dist/index.cjs.js",
4+
"module": "dist/index.esm.js"
5+
}

0 commit comments

Comments
 (0)