Skip to content

Commit a8e3b5a

Browse files
authored
RxFire Realtime Database (#997)
* initial database code * test setup * database tests * auditTrail and database tests * [AUTOMATED]: Prettier Code Styling * [AUTOMATED]: License Headers * Josh's comments. Database docs * [AUTOMATED]: Prettier Code Styling * Firestore docs * auth docs * declaration fixes * switch to peerDeps * [AUTOMATED]: Prettier Code Styling * test config
1 parent d7cc609 commit a8e3b5a

26 files changed

+1700
-15
lines changed

packages/rxfire/auth/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { auth, User } from 'firebase/app';
17+
import { auth, User } from 'firebase';
1818
import { Observable, from, of } from 'rxjs';
1919
import { switchMap } from 'rxjs/operators';
2020

@@ -47,7 +47,7 @@ export function user(auth: auth.Auth): Observable<User> {
4747
* sign-out, and token refresh events
4848
* @param auth firebase.auth.Auth
4949
*/
50-
export function idToken(auth: auth.Auth) {
50+
export function idToken(auth: auth.Auth): Observable<string | null> {
5151
return user(auth).pipe(
5252
switchMap(user => (user ? from(user.getIdToken()) : of(null)))
5353
);

packages/rxfire/auth/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"name": "rxfire/auth",
33
"main": "dist/index.cjs.js",
4-
"module": "dist/index.esm.js"
4+
"module": "dist/index.esm.js",
5+
"typings": "dist/auth/index.d.ts"
56
}

packages/rxfire/database/fromRef.d.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
import { database } from 'firebase';
17+
import { Observable } from 'rxjs';
18+
import { ListenEvent, QueryChange } from './interfaces';
19+
/**
20+
* Create an observable from a Database Reference or Database Query.
21+
* @param ref Database Reference
22+
* @param event Listen event type ('value', 'added', 'changed', 'removed', 'moved')
23+
*/
24+
export declare function fromRef(
25+
ref: database.Query,
26+
event: ListenEvent
27+
): Observable<QueryChange>;

packages/rxfire/database/fromRef.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 { database } from 'firebase';
18+
import { Observable } from 'rxjs';
19+
import { map, delay, share } from 'rxjs/operators';
20+
import { ListenEvent, QueryChange } from './interfaces';
21+
22+
/**
23+
* Create an observable from a Database Reference or Database Query.
24+
* @param ref Database Reference
25+
* @param event Listen event type ('value', 'added', 'changed', 'removed', 'moved')
26+
*/
27+
export function fromRef(
28+
ref: database.Query,
29+
event: ListenEvent
30+
): Observable<QueryChange> {
31+
return new Observable<QueryChange>(subscriber => {
32+
const fn = ref.on(
33+
event,
34+
(snapshot, prevKey) => {
35+
subscriber.next({ snapshot, prevKey, event });
36+
},
37+
subscriber.error.bind(subscriber)
38+
);
39+
return {
40+
unsubscribe() {
41+
ref.off(event, fn);
42+
}
43+
};
44+
}).pipe(
45+
// Ensures subscribe on observable is async. This handles
46+
// a quirk in the SDK where on/once callbacks can happen
47+
// synchronously.
48+
delay(0)
49+
);
50+
}

packages/rxfire/database/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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 './fromRef';
18+
export * from './interfaces';
19+
export * from './list';
20+
export * from './object';
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
import { database } from 'firebase';
17+
export declare enum ListenEvent {
18+
added = 'child_added',
19+
removed = 'child_removed',
20+
changed = 'child_changed',
21+
moved = 'child_moved',
22+
value = 'value'
23+
}
24+
export interface QueryChange {
25+
snapshot: database.DataSnapshot;
26+
prevKey: string | null | undefined;
27+
event: ListenEvent;
28+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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 { database } from 'firebase';
18+
19+
export enum ListenEvent {
20+
added = 'child_added',
21+
removed = 'child_removed',
22+
changed = 'child_changed',
23+
moved = 'child_moved',
24+
value = 'value'
25+
}
26+
27+
export interface QueryChange {
28+
snapshot: database.DataSnapshot;
29+
prevKey: string | null | undefined;
30+
event: ListenEvent;
31+
}
Lines changed: 22 additions & 0 deletions
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+
import { database } from 'firebase';
17+
import { Observable } from 'rxjs';
18+
import { QueryChange, ListenEvent } from '../interfaces';
19+
export declare function auditTrail(
20+
query: database.Query,
21+
events?: ListenEvent[]
22+
): Observable<QueryChange[]>;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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 { database } from 'firebase';
18+
import { Observable } from 'rxjs';
19+
import { QueryChange, ListenEvent } from '../interfaces';
20+
import { fromRef } from '../fromRef';
21+
import { map, withLatestFrom, scan, skipWhile } from 'rxjs/operators';
22+
import { stateChanges } from './index';
23+
24+
interface LoadedMetadata {
25+
data: QueryChange;
26+
lastKeyToLoad: any;
27+
}
28+
29+
export function auditTrail(
30+
query: database.Query,
31+
events?: ListenEvent[]
32+
): Observable<QueryChange[]> {
33+
const auditTrail$ = stateChanges(query, events).pipe(
34+
scan<QueryChange>((current, changes) => [...current, changes], [])
35+
);
36+
return waitForLoaded(query, auditTrail$);
37+
}
38+
39+
function loadedData(query: database.Query): Observable<LoadedMetadata> {
40+
// Create an observable of loaded values to retrieve the
41+
// known dataset. This will allow us to know what key to
42+
// emit the "whole" array at when listening for child events.
43+
return fromRef(query, ListenEvent.value).pipe(
44+
map(data => {
45+
// Store the last key in the data set
46+
let lastKeyToLoad;
47+
// Loop through loaded dataset to find the last key
48+
data.snapshot.forEach(child => {
49+
lastKeyToLoad = child.key;
50+
return false;
51+
});
52+
// return data set and the current last key loaded
53+
return { data, lastKeyToLoad };
54+
})
55+
);
56+
}
57+
58+
function waitForLoaded(
59+
query: database.Query,
60+
snap$: Observable<QueryChange[]>
61+
) {
62+
const loaded$ = loadedData(query);
63+
return loaded$.pipe(
64+
withLatestFrom(snap$),
65+
// Get the latest values from the "loaded" and "child" datasets
66+
// We can use both datasets to form an array of the latest values.
67+
map(([loaded, changes]) => {
68+
// Store the last key in the data set
69+
let lastKeyToLoad = loaded.lastKeyToLoad;
70+
// Store all child keys loaded at this point
71+
const loadedKeys = changes.map(change => change.snapshot.key);
72+
return { changes, lastKeyToLoad, loadedKeys };
73+
}),
74+
// This is the magical part, only emit when the last load key
75+
// in the dataset has been loaded by a child event. At this point
76+
// we can assume the dataset is "whole".
77+
skipWhile(meta => meta.loadedKeys.indexOf(meta.lastKeyToLoad) === -1),
78+
// Pluck off the meta data because the user only cares
79+
// to iterate through the snapshots
80+
map(meta => meta.changes)
81+
);
82+
}

0 commit comments

Comments
 (0)