Skip to content

RxFire Realtime Database #997

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jul 12, 2018
Merged
4 changes: 2 additions & 2 deletions packages/rxfire/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { auth, User } from 'firebase/app';
import { auth, User } from 'firebase';
import { Observable, from, of } from 'rxjs';
import { switchMap } from 'rxjs/operators';

Expand Down Expand Up @@ -47,7 +47,7 @@ export function user(auth: auth.Auth): Observable<User> {
* sign-out, and token refresh events
* @param auth firebase.auth.Auth
*/
export function idToken(auth: auth.Auth) {
export function idToken(auth: auth.Auth): Observable<string | null> {
return user(auth).pipe(
switchMap(user => (user ? from(user.getIdToken()) : of(null)))
);
Expand Down
3 changes: 2 additions & 1 deletion packages/rxfire/auth/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "rxfire/auth",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js"
"module": "dist/index.esm.js",
"typings": "dist/auth/index.d.ts"
}
27 changes: 27 additions & 0 deletions packages/rxfire/database/fromRef.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Copyright 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { database } from 'firebase';
import { Observable } from 'rxjs';
import { ListenEvent, QueryChange } from './interfaces';
/**
* Create an observable from a Database Reference or Database Query.
* @param ref Database Reference
* @param event Listen event type ('value', 'added', 'changed', 'removed', 'moved')
*/
export declare function fromRef(
ref: database.Query,
event: ListenEvent
): Observable<QueryChange>;
50 changes: 50 additions & 0 deletions packages/rxfire/database/fromRef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { database } from 'firebase';
import { Observable } from 'rxjs';
import { map, delay, share } from 'rxjs/operators';
import { ListenEvent, QueryChange } from './interfaces';

/**
* Create an observable from a Database Reference or Database Query.
* @param ref Database Reference
* @param event Listen event type ('value', 'added', 'changed', 'removed', 'moved')
*/
export function fromRef(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method allows users to dynamically invoke various methods off of a reference. It's kind of scary and I would rather us support only the on and have users handle the once case themselves.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

ref: database.Query,
event: ListenEvent
): Observable<QueryChange> {
return new Observable<QueryChange>(subscriber => {
const fn = ref.on(
event,
(snapshot, prevKey) => {
subscriber.next({ snapshot, prevKey, event });
},
subscriber.error.bind(subscriber)
);
return {
unsubscribe() {
ref.off(event, fn);
}
};
}).pipe(
// Ensures subscribe on observable is async. This handles
// a quirk in the SDK where on/once callbacks can happen
// synchronously.
delay(0)
);
}
20 changes: 20 additions & 0 deletions packages/rxfire/database/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export * from './fromRef';
export * from './interfaces';
export * from './list';
export * from './object';
28 changes: 28 additions & 0 deletions packages/rxfire/database/interfaces.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Copyright 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { database } from 'firebase';
export declare enum ListenEvent {
added = 'child_added',
removed = 'child_removed',
changed = 'child_changed',
moved = 'child_moved',
value = 'value'
}
export interface QueryChange {
snapshot: database.DataSnapshot;
prevKey: string | null | undefined;
event: ListenEvent;
}
31 changes: 31 additions & 0 deletions packages/rxfire/database/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { database } from 'firebase';

export enum ListenEvent {
added = 'child_added',
removed = 'child_removed',
changed = 'child_changed',
moved = 'child_moved',
value = 'value'
}

export interface QueryChange {
snapshot: database.DataSnapshot;
prevKey: string | null | undefined;
event: ListenEvent;
}
22 changes: 22 additions & 0 deletions packages/rxfire/database/list/audit-trail.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { database } from 'firebase';
import { Observable } from 'rxjs';
import { QueryChange, ListenEvent } from '../interfaces';
export declare function auditTrail(
query: database.Query,
events?: ListenEvent[]
): Observable<QueryChange[]>;
82 changes: 82 additions & 0 deletions packages/rxfire/database/list/audit-trail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Copyright 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { database } from 'firebase';
import { Observable } from 'rxjs';
import { QueryChange, ListenEvent } from '../interfaces';
import { fromRef } from '../fromRef';
import { map, withLatestFrom, scan, skipWhile } from 'rxjs/operators';
import { stateChanges } from './index';

interface LoadedMetadata {
data: QueryChange;
lastKeyToLoad: any;
}

export function auditTrail(
query: database.Query,
events?: ListenEvent[]
): Observable<QueryChange[]> {
const auditTrail$ = stateChanges(query, events).pipe(
scan<QueryChange>((current, changes) => [...current, changes], [])
);
return waitForLoaded(query, auditTrail$);
}

function loadedData(query: database.Query): Observable<LoadedMetadata> {
// Create an observable of loaded values to retrieve the
// known dataset. This will allow us to know what key to
// emit the "whole" array at when listening for child events.
return fromRef(query, ListenEvent.value).pipe(
map(data => {
// Store the last key in the data set
let lastKeyToLoad;
// Loop through loaded dataset to find the last key
data.snapshot.forEach(child => {
lastKeyToLoad = child.key;
return false;
});
// return data set and the current last key loaded
return { data, lastKeyToLoad };
})
);
}

function waitForLoaded(
query: database.Query,
snap$: Observable<QueryChange[]>
) {
const loaded$ = loadedData(query);
return loaded$.pipe(
withLatestFrom(snap$),
// Get the latest values from the "loaded" and "child" datasets
// We can use both datasets to form an array of the latest values.
map(([loaded, changes]) => {
// Store the last key in the data set
let lastKeyToLoad = loaded.lastKeyToLoad;
// Store all child keys loaded at this point
const loadedKeys = changes.map(change => change.snapshot.key);
return { changes, lastKeyToLoad, loadedKeys };
}),
// This is the magical part, only emit when the last load key
// in the dataset has been loaded by a child event. At this point
// we can assume the dataset is "whole".
skipWhile(meta => meta.loadedKeys.indexOf(meta.lastKeyToLoad) === -1),
// Pluck off the meta data because the user only cares
// to iterate through the snapshots
map(meta => meta.changes)
);
}
Loading