Skip to content

Commit

Permalink
feat(rtdb): types for collection, audit trail, snapshot, and state ch…
Browse files Browse the repository at this point in the history
…anges (#1643)
  • Loading branch information
jamesdaniels committed May 14, 2018
1 parent 31045a9 commit 2c2fe02
Show file tree
Hide file tree
Showing 13 changed files with 73 additions and 63 deletions.
25 changes: 19 additions & 6 deletions src/database/interfaces.ts
Expand Up @@ -6,9 +6,9 @@ export type FirebaseOperation = string | Reference | DataSnapshot;
export interface AngularFireList<T> {
query: DatabaseQuery;
valueChanges(events?: ChildEvent[]): Observable<T[]>;
snapshotChanges(events?: ChildEvent[]): Observable<SnapshotAction[]>;
stateChanges(events?: ChildEvent[]): Observable<SnapshotAction>;
auditTrail(events?: ChildEvent[]): Observable<SnapshotAction[]>;
snapshotChanges(events?: ChildEvent[]): Observable<SnapshotAction<T>[]>;
stateChanges(events?: ChildEvent[]): Observable<SnapshotAction<T>>;
auditTrail(events?: ChildEvent[]): Observable<SnapshotAction<T>[]>;
update(item: FirebaseOperation, data: T): Promise<void>;
set(item: FirebaseOperation, data: T): Promise<void>;
push(data: T): ThenableReference;
Expand All @@ -18,7 +18,7 @@ export interface AngularFireList<T> {
export interface AngularFireObject<T> {
query: DatabaseQuery;
valueChanges(): Observable<T | null>;
snapshotChanges(): Observable<SnapshotAction>;
snapshotChanges(): Observable<SnapshotAction<T>>;
update(data: Partial<T>): Promise<void>;
set(data: T): Promise<void>;
remove(): Promise<void>;
Expand All @@ -45,11 +45,24 @@ export interface AngularFireAction<T> extends Action<T> {
key: string | null;
}

export type SnapshotAction = AngularFireAction<DatabaseSnapshot>;
export type SnapshotAction<T> = AngularFireAction<DatabaseSnapshot<T>>;

export type Primitive = number | string | boolean;

export type DatabaseSnapshot = DataSnapshot;
export interface DatabaseSnapshotExists<T> extends DataSnapshot {
exists(): true;
val(): T;
forEach(action: (a: DatabaseSnapshot<T>) => boolean): boolean;
}

export interface DatabaseSnapshotDoesNotExist<T> extends DataSnapshot {
exists(): false;
val(): null;
forEach(action: (a: DatabaseSnapshot<T>) => boolean): boolean;
}

export type DatabaseSnapshot<T> = DatabaseSnapshotExists<T> | DatabaseSnapshotDoesNotExist<T>;

export type DatabaseReference = Reference;
export type DatabaseQuery = Query;
export type QueryReference = DatabaseReference | DatabaseQuery;
Expand Down
2 changes: 1 addition & 1 deletion src/database/list/audit-trail.spec.ts
@@ -1,5 +1,5 @@
import { Reference } from '@firebase/database-types';
import { FirebaseApp, FirebaseAppConfig, AngularFireModule } from 'angularfire2';
import { FirebaseApp, AngularFireModule } from 'angularfire2';
import { AngularFireDatabase, AngularFireDatabaseModule, auditTrail, ChildEvent } from 'angularfire2/database';
import { TestBed, inject } from '@angular/core/testing';
import { COMMON_CONFIG } from '../test-config';
Expand Down
24 changes: 8 additions & 16 deletions src/database/list/audit-trail.ts
Expand Up @@ -7,32 +7,24 @@ import { AngularFireDatabase } from '../database';

import { skipWhile, withLatestFrom, map, scan } from 'rxjs/operators';

export function createAuditTrail(query: DatabaseQuery, afDatabase: AngularFireDatabase) {
return (events?: ChildEvent[]) => afDatabase.scheduler.keepUnstableUntilFirst(
afDatabase.scheduler.runOutsideAngular(
auditTrail(query, events)
)
);
}

export function auditTrail(query: DatabaseQuery, events?: ChildEvent[]): Observable<SnapshotAction[]> {
const auditTrail$ = stateChanges(query, events)
export function auditTrail<T>(query: DatabaseQuery, events?: ChildEvent[]): Observable<SnapshotAction<T>[]> {
const auditTrail$ = stateChanges<T>(query, events)
.pipe(
scan<SnapshotAction>((current, action) => [...current, action], [])
scan<SnapshotAction<T>>((current, action) => [...current, action], [])
);
return waitForLoaded(query, auditTrail$);
return waitForLoaded<T>(query, auditTrail$);
}

interface LoadedMetadata {
data: AngularFireAction<DataSnapshot>;
lastKeyToLoad: any;
}

function loadedData(query: DatabaseQuery): Observable<LoadedMetadata> {
function loadedData<T>(query: DatabaseQuery): 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, 'value')
return fromRef<T>(query, 'value')
.pipe(
map(data => {
// Store the last key in the data set
Expand All @@ -47,8 +39,8 @@ function loadedData(query: DatabaseQuery): Observable<LoadedMetadata> {
);
}

function waitForLoaded(query: DatabaseQuery, action$: Observable<SnapshotAction[]>) {
const loaded$ = loadedData(query);
function waitForLoaded<T>(query: DatabaseQuery, action$: Observable<SnapshotAction<T>[]>) {
const loaded$ = loadedData<T>(query);
return loaded$
.pipe(
withLatestFrom(action$),
Expand Down
6 changes: 3 additions & 3 deletions src/database/list/changes.ts
Expand Up @@ -6,7 +6,7 @@ import { isNil } from '../utils';

import { switchMap, distinctUntilChanged, scan } from 'rxjs/operators';

export function listChanges<T>(ref: DatabaseQuery, events: ChildEvent[]): Observable<SnapshotAction[]> {
export function listChanges<T=any>(ref: DatabaseQuery, events: ChildEvent[]): Observable<SnapshotAction<T>[]> {
return fromRef(ref, 'value', 'once').pipe(
switchMap(snapshotAction => {
const childEvent$ = [of(snapshotAction)];
Expand All @@ -17,7 +17,7 @@ export function listChanges<T>(ref: DatabaseQuery, events: ChildEvent[]): Observ
);
}

function positionFor(changes: SnapshotAction[], key) {
function positionFor<T>(changes: SnapshotAction<T>[], key) {
const len = changes.length;
for(let i=0; i<len; i++) {
if(changes[i].payload.key === key) {
Expand All @@ -27,7 +27,7 @@ function positionFor(changes: SnapshotAction[], key) {
return -1;
}

function positionAfter(changes: SnapshotAction[], prevKey?: string) {
function positionAfter<T>(changes: SnapshotAction<T>[], prevKey?: string) {
if(isNil(prevKey)) {
return 0;
} else {
Expand Down
32 changes: 23 additions & 9 deletions src/database/list/create-reference.ts
@@ -1,37 +1,51 @@
import { DatabaseQuery, AngularFireList, ChildEvent } from '../interfaces';
import { snapshotChanges } from './snapshot-changes';
import { createStateChanges } from './state-changes';
import { createAuditTrail } from './audit-trail';
import { stateChanges } from './state-changes';
import { auditTrail } from './audit-trail';
import { createDataOperationMethod } from './data-operation';
import { createRemoveMethod } from './remove';
import { AngularFireDatabase } from '../database';
import { map } from 'rxjs/operators';

export function createListReference<T>(query: DatabaseQuery, afDatabase: AngularFireDatabase): AngularFireList<T> {
export function createListReference<T=any>(query: DatabaseQuery, afDatabase: AngularFireDatabase): AngularFireList<T> {
return {
query,
update: createDataOperationMethod<Partial<T>>(query.ref, 'update'),
set: createDataOperationMethod<T>(query.ref, 'set'),
push: (data: T) => query.ref.push(data),
remove: createRemoveMethod(query.ref),
snapshotChanges(events?: ChildEvent[]) {
const snapshotChanges$ = snapshotChanges(query, events);
const snapshotChanges$ = snapshotChanges<T>(query, events);
return afDatabase.scheduler.keepUnstableUntilFirst(
afDatabase.scheduler.runOutsideAngular(
snapshotChanges$
)
);
},
stateChanges: createStateChanges(query, afDatabase),
auditTrail: createAuditTrail(query, afDatabase),
valueChanges<T>(events?: ChildEvent[]) {
const snapshotChanges$ = snapshotChanges(query, events);
stateChanges(events?: ChildEvent[]) {
const stateChanges$ = stateChanges<T>(query, events);
return afDatabase.scheduler.keepUnstableUntilFirst(
afDatabase.scheduler.runOutsideAngular(
stateChanges$
)
);
},
auditTrail(events?: ChildEvent[]) {
const auditTrail$ = auditTrail<T>(query, events)
return afDatabase.scheduler.keepUnstableUntilFirst(
afDatabase.scheduler.runOutsideAngular(
auditTrail$
)
);
},
valueChanges(events?: ChildEvent[]) {
const snapshotChanges$ = snapshotChanges<T>(query, events);
return afDatabase.scheduler.keepUnstableUntilFirst(
afDatabase.scheduler.runOutsideAngular(
snapshotChanges$
)
).pipe(
map(actions => actions.map(a => a.payload.val()))
map(actions => actions.map(a => a.payload.val() as T))
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/database/list/data-operation.ts
Expand Up @@ -7,7 +7,7 @@ export function createDataOperationMethod<T>(ref: DatabaseReference, operation:
return checkOperationCases(item, {
stringCase: () => ref.child(<string>item)[operation](value),
firebaseCase: () => (<DatabaseReference>item)[operation](value),
snapshotCase: () => (<DatabaseSnapshot>item).ref[operation](value)
snapshotCase: () => (<DatabaseSnapshot<T>>item).ref[operation](value)
});
}
}
4 changes: 2 additions & 2 deletions src/database/list/remove.ts
Expand Up @@ -5,13 +5,13 @@ import { DataSnapshot, Reference } from '@firebase/database-types';

// TODO(davideast): Find out why TS thinks this returns firebase.Primise
// instead of Promise.
export function createRemoveMethod(ref: DatabaseReference) {
export function createRemoveMethod<T>(ref: DatabaseReference) {
return function remove(item?: FirebaseOperation): any {
if(!item) { return ref.remove(); }
return checkOperationCases(item, {
stringCase: () => ref.child(<string>item).remove(),
firebaseCase: () => (<DatabaseReference>item).remove(),
snapshotCase: () => (<DatabaseSnapshot>item).ref.remove()
snapshotCase: () => (<DatabaseSnapshot<T>>item).ref.remove()
});
}
}
4 changes: 2 additions & 2 deletions src/database/list/snapshot-changes.ts
Expand Up @@ -3,7 +3,7 @@ import { listChanges } from './changes';
import { DatabaseQuery, ChildEvent, SnapshotAction } from '../interfaces';
import { validateEventsArray } from './utils';

export function snapshotChanges(query: DatabaseQuery, events?: ChildEvent[]): Observable<SnapshotAction[]> {
export function snapshotChanges<T>(query: DatabaseQuery, events?: ChildEvent[]): Observable<SnapshotAction<T>[]> {
events = validateEventsArray(events);
return listChanges(query, events!);
return listChanges<T>(query, events!);
}
14 changes: 3 additions & 11 deletions src/database/list/state-changes.ts
Expand Up @@ -3,19 +3,11 @@ import { fromRef } from '../observable/fromRef';
import { validateEventsArray } from './utils';
import { Observable, merge } from 'rxjs';

import { DataSnapshot } from '@firebase/database-types';
import { DatabaseSnapshot } from '../interfaces';
import { AngularFireDatabase } from '../database';

export function createStateChanges(query: DatabaseQuery, afDatabase: AngularFireDatabase) {
return (events?: ChildEvent[]) => afDatabase.scheduler.keepUnstableUntilFirst(
afDatabase.scheduler.runOutsideAngular(
stateChanges(query, events)
)
);
}

export function stateChanges(query: DatabaseQuery, events?: ChildEvent[]) {
export function stateChanges<T>(query: DatabaseQuery, events?: ChildEvent[]) {
events = validateEventsArray(events)!;
const childEvent$ = events.map(event => fromRef(query, event));
const childEvent$ = events.map(event => fromRef<T>(query, event));
return merge(...childEvent$);
}
4 changes: 2 additions & 2 deletions src/database/object/create-reference.ts
Expand Up @@ -3,11 +3,11 @@ import { DatabaseQuery, AngularFireObject } from '../interfaces';
import { createObjectSnapshotChanges } from './snapshot-changes';
import { AngularFireDatabase } from '../database';

export function createObjectReference<T>(query: DatabaseQuery, afDatabase: AngularFireDatabase): AngularFireObject<T> {
export function createObjectReference<T=any>(query: DatabaseQuery, afDatabase: AngularFireDatabase): AngularFireObject<T> {
return {
query,
snapshotChanges<T>() {
const snapshotChanges$ = createObjectSnapshotChanges(query)();
const snapshotChanges$ = createObjectSnapshotChanges<T>(query)();
return afDatabase.scheduler.keepUnstableUntilFirst(
afDatabase.scheduler.runOutsideAngular(
snapshotChanges$
Expand Down
7 changes: 3 additions & 4 deletions src/database/object/snapshot-changes.ts
@@ -1,10 +1,9 @@
import { Observable } from 'rxjs';
import { fromRef } from '../observable/fromRef';
import { DatabaseQuery, AngularFireAction, SnapshotAction } from '../interfaces';
import { DataSnapshot } from '@firebase/database-types';
import { DatabaseQuery, DatabaseSnapshot, AngularFireAction, SnapshotAction } from '../interfaces';

export function createObjectSnapshotChanges(query: DatabaseQuery) {
return function snapshotChanges(): Observable<SnapshotAction> {
export function createObjectSnapshotChanges<T>(query: DatabaseQuery) {
return function snapshotChanges(): Observable<SnapshotAction<T>> {
return fromRef(query, 'value');
}
}
2 changes: 1 addition & 1 deletion src/database/observable/fromRef.spec.ts
@@ -1,5 +1,5 @@
import { Reference } from '@firebase/database-types';
import { FirebaseApp, FirebaseAppConfig, AngularFireModule } from 'angularfire2';
import { FirebaseApp, AngularFireModule } from 'angularfire2';
import { AngularFireDatabase, AngularFireDatabaseModule, fromRef } from 'angularfire2/database';
import { TestBed, inject } from '@angular/core/testing';
import { COMMON_CONFIG } from '../test-config';
Expand Down
10 changes: 5 additions & 5 deletions src/database/observable/fromRef.ts
Expand Up @@ -3,8 +3,8 @@ import { Observable } from 'rxjs';
import { FirebaseZoneScheduler } from 'angularfire2';
import { map, delay, share } from 'rxjs/operators';

interface SnapshotPrevKey {
snapshot: DatabaseSnapshot;
interface SnapshotPrevKey<T> {
snapshot: DatabaseSnapshot<T>;
prevKey: string | null | undefined;
}

Expand All @@ -13,8 +13,8 @@ interface SnapshotPrevKey {
* @param ref Database Reference
* @param event Listen event type ('value', 'added', 'changed', 'removed', 'moved')
*/
export function fromRef(ref: DatabaseQuery, event: ListenEvent, listenType = 'on'): Observable<AngularFireAction<DatabaseSnapshot>> {
return new Observable<SnapshotPrevKey>(subscriber => {
export function fromRef<T>(ref: DatabaseQuery, event: ListenEvent, listenType = 'on'): Observable<AngularFireAction<DatabaseSnapshot<T>>> {
return new Observable<SnapshotPrevKey<T>>(subscriber => {
const fn = ref[listenType](event, (snapshot, prevKey) => {
subscriber.next({ snapshot, prevKey });
if (listenType == 'once') { subscriber.complete(); }
Expand All @@ -25,7 +25,7 @@ export function fromRef(ref: DatabaseQuery, event: ListenEvent, listenType = 'on
return { unsubscribe() { } };
}
}).pipe(
map((payload: SnapshotPrevKey) => {
map(payload => {
const { snapshot, prevKey } = payload;
let key: string | null = null;
if (snapshot.exists()) { key = snapshot.key; }
Expand Down

0 comments on commit 2c2fe02

Please sign in to comment.