Skip to content

Commit

Permalink
feat(firestore): types for collection, audit trail, state, and snapsh…
Browse files Browse the repository at this point in the history
…ot changes (#1644)

* feat(fs): types for collection, audit log, state, and snapshot changes

* Add exists vs. not interfaces for type guards

* Clean up the types some more
  • Loading branch information
jamesdaniels authored and davideast committed May 14, 2018
1 parent 2c2fe02 commit dff8ddf
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 56 deletions.
8 changes: 4 additions & 4 deletions src/firestore/collection/changes.ts
Expand Up @@ -10,24 +10,24 @@ import { DocumentChangeAction, Action } from '../interfaces';
* order of occurence.
* @param query
*/
export function docChanges(query: Query): Observable<DocumentChangeAction[]> {
export function docChanges<T>(query: Query): Observable<DocumentChangeAction<T>[]> {
return fromCollectionRef(query)
.pipe(
map(action =>
action.payload.docChanges()
.map(change => ({ type: change.type, payload: change }))));
.map(change => ({ type: change.type, payload: change } as DocumentChangeAction<T>))));
}

/**
* Return a stream of document changes on a query. These results are in sort order.
* @param query
*/
export function sortedChanges(query: Query, events: DocumentChangeType[]): Observable<DocumentChangeAction[]> {
export function sortedChanges<T>(query: Query, events: DocumentChangeType[]): Observable<DocumentChangeAction<T>[]> {
return fromCollectionRef(query)
.pipe(
map(changes => changes.payload.docChanges()),
scan((current, changes) => combineChanges(current, changes, events), []),
map(changes => changes.map(c => ({ type: c.type, payload: c }))));
map(changes => changes.map(c => ({ type: c.type, payload: c } as DocumentChangeAction<T>))));
}

/**
Expand Down
22 changes: 11 additions & 11 deletions src/firestore/collection/collection.ts
@@ -1,11 +1,11 @@
import { DocumentChangeType, CollectionReference, Query, DocumentReference } from '@firebase/firestore-types';
import { DocumentChangeType, CollectionReference, Query, DocumentReference, DocumentData } from '@firebase/firestore-types';
import { Observable, Subscriber } from 'rxjs';
import { fromCollectionRef } from '../observable/fromRef';
import { map, filter, scan } from 'rxjs/operators';

import { Injectable } from '@angular/core';

import { QueryFn, AssociatedReference, DocumentChangeAction } from '../interfaces';
import { QueryFn, AssociatedReference, DocumentChangeAction, DocumentChange } from '../interfaces';
import { docChanges, sortedChanges } from './changes';
import { AngularFirestoreDocument } from '../document/document';
import { AngularFirestore } from '../firestore';
Expand Down Expand Up @@ -40,7 +40,7 @@ export function validateEventsArray(events?: DocumentChangeType[]) {
* // Subscribe to changes as snapshots. This provides you data updates as well as delta updates.
* fakeStock.valueChanges().subscribe(value => console.log(value));
*/
export class AngularFirestoreCollection<T> {
export class AngularFirestoreCollection<T=DocumentData> {
/**
* The constructor takes in a CollectionReference and Query to provide wrapper methods
* for data operations and data streaming.
Expand All @@ -62,17 +62,17 @@ export class AngularFirestoreCollection<T> {
* your own data structure.
* @param events
*/
stateChanges(events?: DocumentChangeType[]): Observable<DocumentChangeAction[]> {
stateChanges(events?: DocumentChangeType[]): Observable<DocumentChangeAction<T>[]> {
if(!events || events.length === 0) {
return this.afs.scheduler.keepUnstableUntilFirst(
this.afs.scheduler.runOutsideAngular(
docChanges(this.query)
docChanges<T>(this.query)
)
);
}
return this.afs.scheduler.keepUnstableUntilFirst(
this.afs.scheduler.runOutsideAngular(
docChanges(this.query)
docChanges<T>(this.query)
)
)
.pipe(
Expand All @@ -86,7 +86,7 @@ export class AngularFirestoreCollection<T> {
* but it collects each event in an array over time.
* @param events
*/
auditTrail(events?: DocumentChangeType[]): Observable<DocumentChangeAction[]> {
auditTrail(events?: DocumentChangeType[]): Observable<DocumentChangeAction<T>[]> {
return this.stateChanges(events).pipe(scan((current, action) => [...current, ...action], []));
}

Expand All @@ -95,9 +95,9 @@ export class AngularFirestoreCollection<T> {
* query order.
* @param events
*/
snapshotChanges(events?: DocumentChangeType[]): Observable<DocumentChangeAction[]> {
snapshotChanges(events?: DocumentChangeType[]): Observable<DocumentChangeAction<T>[]> {
const validatedEvents = validateEventsArray(events);
const sortedChanges$ = sortedChanges(this.query, validatedEvents);
const sortedChanges$ = sortedChanges<T>(this.query, validatedEvents);
const scheduledSortedChanges$ = this.afs.scheduler.runOutsideAngular(sortedChanges$);
return this.afs.scheduler.keepUnstableUntilFirst(scheduledSortedChanges$);
}
Expand All @@ -106,11 +106,11 @@ export class AngularFirestoreCollection<T> {
* Listen to all documents in the collection and its possible query as an Observable.
*/
valueChanges(): Observable<T[]> {
const fromCollectionRef$ = fromCollectionRef(this.query);
const fromCollectionRef$ = fromCollectionRef<T>(this.query);
const scheduled$ = this.afs.scheduler.runOutsideAngular(fromCollectionRef$);
return this.afs.scheduler.keepUnstableUntilFirst(scheduled$)
.pipe(
map(actions => actions.payload.docs.map(a => a.data()) as T[])
map(actions => actions.payload.docs.map(a => a.data()))
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/firestore/document/document.spec.ts
Expand Up @@ -57,7 +57,7 @@ describe('AngularFirestoreDocument', () => {
const stock = new AngularFirestoreDocument<Stock>(ref, afs);
await stock.set(FAKE_STOCK_DATA);
const obs$ = stock.valueChanges();
obs$.pipe(take(1)).subscribe(async (data: Stock) => {
obs$.pipe(take(1)).subscribe(async data => {
expect(JSON.stringify(data)).toBe(JSON.stringify(FAKE_STOCK_DATA));
stock.delete().then(done).catch(done.fail);
});
Expand Down
18 changes: 9 additions & 9 deletions src/firestore/document/document.ts
@@ -1,6 +1,6 @@
import { DocumentReference, SetOptions, DocumentSnapshot } from '@firebase/firestore-types';
import { DocumentReference, SetOptions, DocumentData } from '@firebase/firestore-types';
import { Observable, Subscriber } from 'rxjs';
import { QueryFn, AssociatedReference, Action } from '../interfaces';
import { QueryFn, AssociatedReference, Action, DocumentSnapshot } from '../interfaces';
import { fromDocRef } from '../observable/fromRef';
import { map } from 'rxjs/operators';

Expand Down Expand Up @@ -31,7 +31,7 @@ import { AngularFirestoreCollection } from '../collection/collection';
* // OR! Transform using Observable.from() and the data is unwrapped for you
* Observable.from(fakeStock).subscribe(value => console.log(value));
*/
export class AngularFirestoreDocument<T> {
export class AngularFirestoreDocument<T=DocumentData> {

/**
* The contstuctor takes in a DocumentReference to provide wrapper methods
Expand Down Expand Up @@ -70,28 +70,28 @@ export class AngularFirestoreDocument<T> {
* @param path
* @param queryFn
*/
collection<T>(path: string, queryFn?: QueryFn): AngularFirestoreCollection<T> {
collection<R>(path: string, queryFn?: QueryFn): AngularFirestoreCollection<R> {
const collectionRef = this.ref.collection(path);
const { ref, query } = associateQuery(collectionRef, queryFn);
return new AngularFirestoreCollection<T>(ref, query, this.afs);
return new AngularFirestoreCollection<R>(ref, query, this.afs);
}

/**
* Listen to snapshot updates from the document.
*/
snapshotChanges(): Observable<Action<DocumentSnapshot>> {
const fromDocRef$ = fromDocRef(this.ref);
snapshotChanges(): Observable<Action<DocumentSnapshot<T>>> {
const fromDocRef$ = fromDocRef<T>(this.ref);
const scheduledFromDocRef$ = this.afs.scheduler.runOutsideAngular(fromDocRef$);
return this.afs.scheduler.keepUnstableUntilFirst(scheduledFromDocRef$);
}

/**
* Listen to unwrapped snapshot updates from the document.
*/
valueChanges(): Observable<T|null> {
valueChanges(): Observable<T|undefined> {
return this.snapshotChanges().pipe(
map(action => {
return action.payload.exists ? action.payload.data() as T : null;
return action.payload.data();
})
);
}
Expand Down
31 changes: 28 additions & 3 deletions src/firestore/interfaces.ts
@@ -1,9 +1,34 @@
import { Subscriber } from 'rxjs';
import { DocumentChangeType, DocumentChange, CollectionReference, Query } from '@firebase/firestore-types';
import { DocumentChangeType, QuerySnapshot as _QuerySnapshot, FieldPath, DocumentSnapshot as _DocumentSnapshot, SnapshotOptions, QueryDocumentSnapshot as _QueryDocumentSnapshot, DocumentChange as _DocumentChange, CollectionReference, Query } from '@firebase/firestore-types';

export interface DocumentChangeAction {
export interface DocumentSnapshotExists<T> extends _DocumentSnapshot {
readonly exists: true;
data(options?: SnapshotOptions): T;
}

export interface DocumentSnapshotDoesNotExist extends _DocumentSnapshot {
readonly exists: false;
data(options?: SnapshotOptions): undefined;
get(fieldPath: string | FieldPath, options?: SnapshotOptions): undefined;
}

export type DocumentSnapshot<T> = DocumentSnapshotExists<T> | DocumentSnapshotDoesNotExist;

export interface QueryDocumentSnapshot<T> extends _QueryDocumentSnapshot {
data(options?: SnapshotOptions): T;
}

export interface QuerySnapshot<T> extends _QuerySnapshot {
readonly docs: QueryDocumentSnapshot<T>[];
}

export interface DocumentChange<T> extends _DocumentChange {
readonly doc: QueryDocumentSnapshot<T>;
}

export interface DocumentChangeAction<T> {
type: DocumentChangeType;
payload: DocumentChange;
payload: DocumentChange<T>;
}

export interface Action<T> {
Expand Down
12 changes: 6 additions & 6 deletions src/firestore/observable/fromRef.ts
@@ -1,6 +1,6 @@
import { DocumentReference, Query, QuerySnapshot, DocumentSnapshot } from '@firebase/firestore-types';
import { DocumentReference, Query } from '@firebase/firestore-types';
import { Observable, Subscriber } from 'rxjs';
import { Action, Reference } from '../interfaces';
import { Action, Reference, DocumentSnapshot, QuerySnapshot } from '../interfaces';
import { map, share } from 'rxjs/operators';

function _fromRef<T, R>(ref: Reference<T>): Observable<R> {
Expand All @@ -14,13 +14,13 @@ export function fromRef<R>(ref: DocumentReference | Query) {
return _fromRef<typeof ref, R>(ref).pipe(share());
}

export function fromDocRef(ref: DocumentReference): Observable<Action<DocumentSnapshot>>{
return fromRef<DocumentSnapshot>(ref)
export function fromDocRef<T>(ref: DocumentReference): Observable<Action<DocumentSnapshot<T>>>{
return fromRef<DocumentSnapshot<T>>(ref)
.pipe(
map(payload => ({ payload, type: 'value' }))
);
}

export function fromCollectionRef(ref: Query): Observable<Action<QuerySnapshot>> {
return fromRef<QuerySnapshot>(ref).pipe(map(payload => ({ payload, type: 'query' })));
export function fromCollectionRef<T>(ref: Query): Observable<Action<QuerySnapshot<T>>> {
return fromRef<QuerySnapshot<T>>(ref).pipe(map(payload => ({ payload, type: 'query' })));
}
23 changes: 1 addition & 22 deletions yarn.lock
Expand Up @@ -73,7 +73,7 @@
dependencies:
"@firebase/auth-types" "0.3.2"

"@firebase/database-types@0.3.1", "@firebase/database-types@^0.3.1":
"@firebase/database-types@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-0.3.1.tgz#4a15423f3b2cb3bed111f5a353c5c1bb2e2787ba"

Expand Down Expand Up @@ -1825,12 +1825,6 @@ fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"

faye-websocket@0.11.1:
version "0.11.1"
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38"
dependencies:
websocket-driver ">=0.5.1"

file-uri-to-path@1:
version "1.0.0"
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
Expand Down Expand Up @@ -2594,10 +2588,6 @@ http-errors@~1.6.2:
setprototypeof "1.1.0"
statuses ">= 1.4.0 < 2"

http-parser-js@>=0.4.0:
version "0.4.12"
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.12.tgz#b9cfbf4a2cf26f0fc34b10ca1489a27771e3474f"

http-proxy-agent@1:
version "1.0.0"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-1.0.0.tgz#cc1ce38e453bf984a0f7702d2dd59c73d081284a"
Expand Down Expand Up @@ -5780,17 +5770,6 @@ void-elements@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"

websocket-driver@>=0.5.1:
version "0.7.0"
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb"
dependencies:
http-parser-js ">=0.4.0"
websocket-extensions ">=0.1.1"

websocket-extensions@>=0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"

whatwg-fetch@2.0.4, whatwg-fetch@>=0.10.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
Expand Down

0 comments on commit dff8ddf

Please sign in to comment.