Skip to content

feat(rtdb): types for collection, audit trail, snapshot, and state changes #1643

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 4 commits into from
May 14, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions src/database/interfaces.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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