Skip to content

Commit

Permalink
fix(firebase-admin): fix docChanges prop/method issue while adding mo…
Browse files Browse the repository at this point in the history
…re extensive typing

Need to test with cloud, web, and older versions of Typescript (2.5.x?)
  • Loading branch information
MichaelSolati committed Aug 21, 2018
1 parent 56c5c12 commit 8c1d77f
Show file tree
Hide file tree
Showing 9 changed files with 2,874 additions and 2,801 deletions.
5,536 changes: 2,802 additions & 2,734 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,15 @@
"package.json",
"README.md"
],
"peerDependencies": {
"dependencies": {
"@google-cloud/firestore": "0.x.x",
"firebase": "5.x.x"
},
"devDependencies": {
"@types/chai": "^4.1.4",
"@types/mocha": "^5.2.5",
"chai": "^4.1.2",
"coveralls": "^3.0.2",
"firebase": "5.x.x",
"firebase-admin": "5.x.x",
"firebase-tools": "^4.0.2",
"generate-changelog": "^1.7.1",
"jsdom": "^11.12.0",
Expand Down
33 changes: 15 additions & 18 deletions src/geofirestore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,13 @@ import { firestore, GeoFirestoreObj, QueryCriteria } from './interfaces';
* Creates a GeoFirestore instance.
*/
export class GeoFirestore {
private _collectionRef: firestore.CollectionReference;

/**
* @param collectionRef A Firestore Collection reference where the GeoFirestore data will be stored.
*/
constructor(collectionRef: firestore.CollectionReference | firestore.cloud.CollectionReference) {
if (Object.prototype.toString.call(collectionRef) !== '[object Object]') {
constructor(private _collectionRef: firestore.CollectionReference | firestore.cloud.CollectionReference) {
if (Object.prototype.toString.call(this._collectionRef) !== '[object Object]') {
throw new Error('collectionRef must be an instance of a Firestore Collection');
}

this._collectionRef = collectionRef as firestore.CollectionReference;
}

/********************/
Expand All @@ -30,10 +26,10 @@ export class GeoFirestore {
* @param customKey The key of the document to use as the location. Otherwise we default to `coordinates`.
* @returns A promise that is fulfilled when the write is complete.
*/
public add(document: any, customKey?: string): Promise<firestore.DocumentReference> {
public add(document: any, customKey?: string): Promise<firestore.DocumentReference | firestore.cloud.DocumentReference> {
if (Object.prototype.toString.call(document) === '[object Object]') {
const locationKey: string = findCoordinatesKey(document, customKey);
const location: firestore.GeoPoint = document[locationKey];
const location: firestore.GeoPoint | firestore.cloud.GeoPoint = document[locationKey];
const geohash: string = encodeGeohash(location);
return this._collectionRef.add(encodeGeoFireObject(location, geohash, document));
} else {
Expand All @@ -49,9 +45,10 @@ export class GeoFirestore {
* @param $key The key of the location to retrieve.
* @returns A promise that is fulfilled with the document of the given key.
*/
public get($key: string): Promise<number[]> {
public get($key: string): Promise<any> {
validateKey($key);
return this._collectionRef.doc($key).get().then((documentSnapshot: firestore.DocumentSnapshot) => {
const promise = this._collectionRef.doc($key).get() as Promise<firestore.DocumentSnapshot>;
return promise.then((documentSnapshot: firestore.DocumentSnapshot) => {
if (!documentSnapshot.exists) {
return null;
} else {
Expand All @@ -66,7 +63,7 @@ export class GeoFirestore {
*
* @returns The Firestore Collection used to create this GeoFirestore instance.
*/
public ref(): firestore.CollectionReference {
public ref(): firestore.CollectionReference | firestore.cloud.CollectionReference {
return this._collectionRef;
}

Expand All @@ -78,7 +75,7 @@ export class GeoFirestore {
* @param keyOrKeys The key representing the document to remove or an array of keys to remove.
* @returns A promise that is fulfilled after the inputted key(s) is removed.
*/
public remove(keyOrKeys: string | string[]): Promise<void> {
public remove(keyOrKeys: string | string[]): Promise<any> {
if (Array.isArray(keyOrKeys)) {
const documents = {};
keyOrKeys.forEach(key => { documents[key] = null; });
Expand All @@ -98,15 +95,15 @@ export class GeoFirestore {
* @param customKey The key of the document to use as the location. Otherwise we default to `coordinates`.
* @returns A promise that is fulfilled when the write is complete.
*/
public set(keyOrDocuments: string | any, document?: any, customKey?: string): Promise<void> {
public set(keyOrDocuments: string | any, document?: any, customKey?: string): Promise<any> {
if (typeof keyOrDocuments === 'string' && keyOrDocuments.length !== 0) {
validateKey(keyOrDocuments);
if (!document) {
// Setting location to null is valid since it will remove the key
return this._collectionRef.doc(keyOrDocuments).delete();
} else {
const locationKey: string = findCoordinatesKey(document, customKey);
const location: firestore.GeoPoint = document[locationKey];
const location: firestore.GeoPoint | firestore.cloud.GeoPoint = document[locationKey];
const geohash: string = encodeGeohash(location);
return this._collectionRef.doc(keyOrDocuments).set(encodeGeoFireObject(location, geohash, document));
}
Expand All @@ -118,16 +115,16 @@ export class GeoFirestore {
throw new Error('keyOrDocuments must be a string or a mapping of key - document pairs.');
}

const batch: firestore.WriteBatch = this._collectionRef.firestore.batch();
const batch: firestore.WriteBatch = this._collectionRef.firestore.batch() as firestore.WriteBatch;
Object.keys(keyOrDocuments).forEach((key) => {
validateKey(key);
const ref = this._collectionRef.doc(key);
const ref: firestore.DocumentReference = this._collectionRef.doc(key) as firestore.DocumentReference;
const documentToUpdate: any = keyOrDocuments[key];
if (!documentToUpdate) {
batch.delete(ref);
} else {
const locationKey = findCoordinatesKey(documentToUpdate, customKey);
const location: firestore.GeoPoint = documentToUpdate[locationKey];
const location: firestore.GeoPoint | firestore.cloud.GeoPoint = documentToUpdate[locationKey];
const geohash: string = encodeGeohash(location);
batch.set(ref, encodeGeoFireObject(location, geohash, documentToUpdate), { merge: true });
}
Expand Down Expand Up @@ -157,7 +154,7 @@ export class GeoFirestore {
* @param location2 The GeoPoint of the second location.
* @returns The distance, in kilometers, between the inputted locations.
*/
public static distance(location1: firestore.GeoPoint, location2: firestore.GeoPoint) {
public static distance(location1: firestore.GeoPoint | firestore.cloud.GeoPoint, location2: firestore.GeoPoint | firestore.cloud.GeoPoint): number {
validateLocation(location1);
validateLocation(location2);

Expand Down
31 changes: 19 additions & 12 deletions src/interfaces/firestore.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import * as typesWeb from '@firebase/firestore-types';
import { firestore as typesCloud } from 'firebase-admin';

import * as FirestoreWeb from '@firebase/firestore-types';
import '@google-cloud/firestore/types/firestore';
export namespace firestore {
export interface CollectionReference extends typesWeb.CollectionReference { }
export interface DocumentChange extends typesWeb.DocumentChange { }
export interface DocumentReference extends typesWeb.DocumentReference { }
export interface DocumentSnapshot extends typesWeb.DocumentSnapshot { }
export interface GeoPoint extends typesWeb.GeoPoint { }
export interface Query extends typesWeb.Query { }
export interface QuerySnapshot extends typesWeb.QuerySnapshot { }
export interface WriteBatch extends typesWeb.WriteBatch { }
export interface CollectionReference extends FirestoreWeb.CollectionReference { }
export interface DocumentChange extends FirestoreWeb.DocumentChange { }
export interface DocumentReference extends FirestoreWeb.DocumentReference { }
export interface DocumentSnapshot extends FirestoreWeb.DocumentSnapshot { }
export interface GeoPoint extends FirestoreWeb.GeoPoint { }
export interface Query extends FirestoreWeb.Query { }
export interface QuerySnapshot extends FirestoreWeb.QuerySnapshot { }
export interface WriteBatch extends FirestoreWeb.WriteBatch { }
export namespace cloud {
export interface CollectionReference extends typesCloud.Query {}
export interface CollectionReference extends FirebaseFirestore.CollectionReference {}
export interface DocumentChange extends FirebaseFirestore.DocumentChange { }
export interface DocumentReference extends FirebaseFirestore.DocumentReference { }
export interface DocumentSnapshot extends FirebaseFirestore.DocumentSnapshot { }
export interface GeoPoint extends FirebaseFirestore.GeoPoint { }
export interface Query extends FirebaseFirestore.Query { }
export interface QuerySnapshot extends FirebaseFirestore.QuerySnapshot { }
export interface WriteBatch extends FirebaseFirestore.WriteBatch { }
export interface WriteResult extends FirebaseFirestore.WriteResult { }
}
}
2 changes: 1 addition & 1 deletion src/interfaces/geoFirestoreObj.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { firestore } from './firestore';

export interface GeoFirestoreObj {
g: string;
l: firestore.GeoPoint;
l: firestore.GeoPoint | firestore.cloud.GeoPoint;
d: any;
}
2 changes: 1 addition & 1 deletion src/interfaces/locationTracked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ export interface LocationTracked {
document: any;
geohash: string;
isInQuery: boolean;
location: firestore.GeoPoint;
location: firestore.GeoPoint | firestore.cloud.GeoPoint;
}
4 changes: 2 additions & 2 deletions src/interfaces/queryCriteria.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { firestore } from './firestore';

export interface QueryCriteria {
center?: firestore.GeoPoint;
center?: firestore.GeoPoint | firestore.cloud.GeoPoint;
radius?: number;
query?: (ref: firestore.CollectionReference) => firestore.Query;
query?: (ref: firestore.CollectionReference | firestore.cloud.CollectionReference) => firestore.Query | firestore.cloud.Query;
}
44 changes: 23 additions & 21 deletions src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ export class GeoFirestoreQuery {
private _callbacks: GeoQueryCallbacks = { ready: [], key_entered: [], key_exited: [], key_moved: [], key_modified: [] };
// Variable to track when the query is cancelled
private _cancelled = false;
private _center: firestore.GeoPoint;
private _center: firestore.GeoPoint | firestore.cloud.GeoPoint;
// A Map of geohash queries which currently have an active callbacks
private _currentGeohashesQueried: Map<string, GeoFirestoreQueryState> = new Map();
// A Map of locations that a currently active in the queries
// Note that not all of these are currently within this query
private _locationsTracked: Map<string, LocationTracked> = new Map();
private _radius: number;
private _query: firestore.Query;
private _query: firestore.Query | firestore.cloud.Query;

// Variables used to keep track of when to fire the 'ready' event
private _valueEventFired = false;
Expand All @@ -33,7 +33,7 @@ export class GeoFirestoreQuery {
* @param _collectionRef A Firestore Collection reference where the GeoFirestore data will be stored.
* @param queryCriteria The criteria which specifies the query's center and radius.
*/
constructor(private _collectionRef: firestore.CollectionReference, queryCriteria: QueryCriteria) {
constructor(private _collectionRef: firestore.CollectionReference | firestore.cloud.CollectionReference, queryCriteria: QueryCriteria) {
// Firebase reference of the GeoFirestore which created this query
if (Object.prototype.toString.call(this._collectionRef) !== '[object Object]') {
throw new Error('firebaseRef must be an instance of Firestore');
Expand Down Expand Up @@ -91,7 +91,7 @@ export class GeoFirestoreQuery {
*
* @returns The GeoPoint signifying the center of this query.
*/
public center(): firestore.GeoPoint {
public center(): firestore.GeoPoint | firestore.cloud.GeoPoint {
return this._center;
}

Expand Down Expand Up @@ -164,7 +164,7 @@ export class GeoFirestoreQuery {
*
* @returns The Firestore query.
*/
public query(): firestore.Query {
public query(): firestore.Query | firestore.cloud.Query {
return this._query;
}

Expand Down Expand Up @@ -242,10 +242,10 @@ export class GeoFirestoreQuery {
*
* @param locationDataSnapshot A snapshot of the data stored for this location.
*/
private _childAddedCallback(locationDataSnapshot: firestore.DocumentSnapshot): void {
private _childAddedCallback(locationDataSnapshot: firestore.DocumentSnapshot | firestore.cloud.DocumentSnapshot): void {
const data: GeoFirestoreObj = (locationDataSnapshot.exists) ? locationDataSnapshot.data() as GeoFirestoreObj : null;
const document: any = (data && validateGeoFirestoreObject(data)) ? data.d : null;
const location: firestore.GeoPoint = (data && validateLocation(data.l)) ? data.l : null;
const location: firestore.GeoPoint | firestore.cloud.GeoPoint = (data && validateLocation(data.l)) ? data.l : null;
this._updateLocation(geoFirestoreGetKey(locationDataSnapshot), location, document);
}

Expand All @@ -254,10 +254,10 @@ export class GeoFirestoreQuery {
*
* @param locationDataSnapshot A snapshot of the data stored for this location.
*/
private _childChangedCallback(locationDataSnapshot: firestore.DocumentSnapshot): void {
private _childChangedCallback(locationDataSnapshot: firestore.DocumentSnapshot | firestore.cloud.DocumentSnapshot): void {
const data: GeoFirestoreObj = (locationDataSnapshot.exists) ? locationDataSnapshot.data() as GeoFirestoreObj : null;
const document: any = (data && validateGeoFirestoreObject(data)) ? data.d : null;
const location: firestore.GeoPoint = (data && validateLocation(data.l)) ? data.l : null;
const location: firestore.GeoPoint | firestore.cloud.GeoPoint = (data && validateLocation(data.l)) ? data.l : null;
this._updateLocation(geoFirestoreGetKey(locationDataSnapshot), location, document, true);
}

Expand All @@ -266,19 +266,20 @@ export class GeoFirestoreQuery {
*
* @param locationDataSnapshot A snapshot of the data stored for this location.
*/
private _childRemovedCallback(locationDataSnapshot: firestore.DocumentSnapshot): void {
const key: string = geoFirestoreGetKey(locationDataSnapshot);
if (this._locationsTracked.has(key)) {
this._collectionRef.doc(key).get().then((snapshot: firestore.DocumentSnapshot) => {
private _childRemovedCallback(locationDataSnapshot: firestore.DocumentSnapshot | firestore.cloud.DocumentSnapshot): void {
const $key: string = geoFirestoreGetKey(locationDataSnapshot);
if (this._locationsTracked.has($key)) {
const promise: Promise<firestore.DocumentSnapshot> = this._collectionRef.doc($key).get() as Promise<firestore.DocumentSnapshot>;
promise.then((snapshot: firestore.DocumentSnapshot | firestore.cloud.DocumentSnapshot) => {
const data: GeoFirestoreObj = (snapshot.exists) ? snapshot.data() as GeoFirestoreObj : null;
const document: any = (data && validateGeoFirestoreObject(data)) ? data.d : null;
const location: firestore.GeoPoint = (data && validateLocation(data.l)) ? data.l : null;
const location: firestore.GeoPoint | firestore.cloud.GeoPoint = (data && validateLocation(data.l)) ? data.l : null;
const geohash: string = (location !== null) ? encodeGeohash(location) : null;
// Only notify observers if key is not part of any other geohash query or this actually might not be
// a key exited event, but a key moved or entered event. These events will be triggered by updates
// to a different query
if (!this._geohashInSomeQuery(geohash)) {
this._removeLocation(key, document);
this._removeLocation($key, document);
}
});
}
Expand Down Expand Up @@ -425,11 +426,12 @@ export class GeoFirestoreQuery {
const query: string[] = this._stringToQuery(toQueryStr);

// Create the Firebase query
const firestoreQuery: firestore.Query = this._query.orderBy('g').startAt(query[0]).endAt(query[1]);
const firestoreQuery: firestore.Query = this._query.orderBy('g').startAt(query[0]).endAt(query[1]) as firestore.Query;

// For every new matching geohash, determine if we should fire the 'key_entered' event
const childCallback = firestoreQuery.onSnapshot((snapshot: firestore.QuerySnapshot) => {
snapshot.docChanges().forEach((change: firestore.DocumentChange) => {
const childCallback = firestoreQuery.onSnapshot((snapshot: firestore.QuerySnapshot | firestore.cloud.QuerySnapshot) => {
const docChanges: firestore.DocumentChange[] = (typeof snapshot.docChanges === 'function') ? snapshot.docChanges() : snapshot.docChanges as any[] as firestore.DocumentChange[];
docChanges.forEach((change: firestore.DocumentChange | firestore.cloud.DocumentChange) => {
if (change.type === 'added') {
this._childAddedCallback(change.doc);
}
Expand Down Expand Up @@ -490,7 +492,7 @@ export class GeoFirestoreQuery {
this._locationsTracked.delete(key);
if (typeof locationMap !== 'undefined' && locationMap.isInQuery) {
const locationKey = (document) ? findCoordinatesKey(document) : null;
const location: firestore.GeoPoint = (locationKey) ? document[locationKey] : null;
const location: firestore.GeoPoint | firestore.cloud.GeoPoint = (locationKey) ? document[locationKey] : null;
const distanceFromCenter: number = (location) ? GeoFirestore.distance(location, this._center) : null;
this._fireCallbacksForKey('key_exited', key, document, distanceFromCenter);
}
Expand Down Expand Up @@ -522,12 +524,12 @@ export class GeoFirestoreQuery {
* @param document The current Document from Firestore.
* @param modified Flag for if document is a modified document/
*/
private _updateLocation(key: string, location?: firestore.GeoPoint, document?: any, modified = false): void {
private _updateLocation(key: string, location?: firestore.GeoPoint | firestore.cloud.GeoPoint, document?: any, modified = false): void {
validateLocation(location);
// Get the key and location
let distanceFromCenter: number, isInQuery;
const wasInQuery: boolean = (this._locationsTracked.has(key)) ? this._locationsTracked.get(key).isInQuery : false;
const oldLocation: firestore.GeoPoint = (this._locationsTracked.has(key)) ? this._locationsTracked.get(key).location : null;
const oldLocation: firestore.GeoPoint | firestore.cloud.GeoPoint = (this._locationsTracked.has(key)) ? this._locationsTracked.get(key).location : null;

// Determine if the location is within this query
distanceFromCenter = GeoFirestore.distance(location, this._center);
Expand Down
Loading

0 comments on commit 8c1d77f

Please sign in to comment.