Skip to content

Commit

Permalink
refactor: flatten document structure so user data is top level
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelSolati committed Jun 9, 2020
1 parent 0470311 commit e2ad5e5
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 73 deletions.
4 changes: 1 addition & 3 deletions firestore.rules
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ service cloud.firestore {
&& request.resource.data.d.count is number;
}
match /tests/{key} {
allow create: if request.resource.data.size() <= 3;
allow update: if request.resource.data.size() <= 3;
allow delete: if true;
allow read, write: if false;
}
}
}
10 changes: 5 additions & 5 deletions src/GeoCollectionReference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,20 @@ export class GeoCollectionReference extends GeoQuery {
/**
* Add a new document to this collection with the specified data, assigning it a document ID automatically.
*
* @param data An Object containing the data for the new document.
* @param documentData An Object containing the data for the new document.
* @param customKey The key of the document to use as the location. Otherwise we default to `coordinates`.
* @return A Promise resolved with a `GeoDocumentReference` pointing to the newly created document after it has been written to the
* backend.
*/
add(
data: GeoFirestoreTypes.DocumentData,
documentData: GeoFirestoreTypes.DocumentData,
customKey?: string
): Promise<GeoDocumentReference> {
if (Object.prototype.toString.call(data) === '[object Object]') {
const location = findCoordinates(data, customKey);
if (Object.prototype.toString.call(documentData) === '[object Object]') {
const location = findCoordinates(documentData, customKey);
const geohash: string = encodeGeohash(location);
return (this._collection as GeoFirestoreTypes.cloud.CollectionReference)
.add(encodeGeoDocument(location, geohash, data))
.add(encodeGeoDocument(location, geohash, documentData))
.then(doc => new GeoDocumentReference(doc));
} else {
throw new Error('document must be an object');
Expand Down
9 changes: 6 additions & 3 deletions src/GeoDocumentReference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,16 +169,19 @@ export class GeoDocumentReference {
* Writes to the document referred to by this `GeoDocumentReference`. If the document does not yet exist, it will be created. If you pass
* `SetOptions`, the provided data can be merged into an existing document.
*
* @param data A map of the fields and values for the document.
* @param documentData A map of the fields and values for the document.
* @param options An object to configure the set behavior. Includes custom key for location in document.
* @return A Promise resolved once the data has been successfully written to the backend (Note it won't resolve while you're offline).
*/
set(
data: GeoFirestoreTypes.DocumentData,
documentData: GeoFirestoreTypes.DocumentData,
options?: GeoFirestoreTypes.SetOptions
): Promise<void> {
return (this._document as GeoFirestoreTypes.web.DocumentReference)
.set(encodeSetDocument(data, options), sanitizeSetOptions(options))
.set(
encodeSetDocument(documentData, options),
sanitizeSetOptions(options)
)
.then(() => null);
}

Expand Down
7 changes: 3 additions & 4 deletions src/GeoDocumentSnapshot.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {GeoFirestoreTypes} from './GeoFirestoreTypes';
import {GeoDocumentReference} from './GeoDocumentReference';
import {decodeGeoDocumentData} from './utils';

/**
* A `GeoDocumentSnapshot` contains data read from a document in your Firestore database. The data can be extracted with `.data()` or
Expand Down Expand Up @@ -68,14 +67,14 @@ export class GeoDocumentSnapshot {
*/
data(
options?: GeoFirestoreTypes.SnapshotOptions
): GeoFirestoreTypes.DocumentData | undefined {
const d =
): GeoFirestoreTypes.GeoDocumentData | undefined {
const documentData =
this._isWeb && options
? (this._snapshot as GeoFirestoreTypes.web.DocumentSnapshot).data(
options
)
: this._snapshot.data();
return d ? decodeGeoDocumentData(d as GeoFirestoreTypes.Document) : null;
return documentData as GeoFirestoreTypes.GeoDocumentData;
}

/**
Expand Down
18 changes: 9 additions & 9 deletions src/GeoFirestoreTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import {firestore as webfirestore} from 'firebase/app';
import '@types/node';

export namespace GeoFirestoreTypes {
export interface Document {
g: string;
l: web.GeoPoint | cloud.GeoPoint;
d: DocumentData;
export interface GeoDocumentData extends DocumentData {
'.g'?: {
coordinates: web.GeoPoint | cloud.GeoPoint;
geohash: string;
};
}
export interface DocumentData {
[field: string]: any;
}
export type DocumentData =
| {[field: string]: any; coordinates?: cloud.GeoPoint | web.GeoPoint}
| undefined;
export interface DocumentChange {
doc: QueryDocumentSnapshot;
newIndex: number;
Expand All @@ -26,7 +27,7 @@ export namespace GeoFirestoreTypes {
export interface QueryDocumentSnapshot {
exists: boolean;
id: string;
data: () => DocumentData | any;
data: () => GeoDocumentData | any;
distance: number;
}
export interface SetOptions {
Expand All @@ -37,7 +38,6 @@ export namespace GeoFirestoreTypes {
export type SnapshotOptions = webfirestore.SnapshotOptions;
export interface UpdateData {
[fieldPath: string]: any;
coordinates?: cloud.GeoPoint | web.GeoPoint;
}
export type WhereFilterOp =
| '<'
Expand Down
6 changes: 3 additions & 3 deletions src/GeoTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class GeoTransaction {
* the provided data can be merged into the existing document.
*
* @param documentRef A reference to the document to be set.
* @param data An object of the fields and values for the document.
* @param documentData An object of the fields and values for the document.
* @param options An object to configure the set behavior. Includes custom key for location in document.
* @return This `GeoTransaction` instance. Used for chaining method calls.
*/
Expand All @@ -88,15 +88,15 @@ export class GeoTransaction {
| GeoDocumentReference
| GeoFirestoreTypes.cloud.DocumentReference
| GeoFirestoreTypes.web.DocumentReference,
data: GeoFirestoreTypes.DocumentData,
documentData: GeoFirestoreTypes.DocumentData,
options?: GeoFirestoreTypes.SetOptions
): GeoTransaction {
const ref = (documentRef instanceof GeoDocumentReference
? documentRef['_document']
: documentRef) as GeoFirestoreTypes.cloud.DocumentReference;
(this._transaction as GeoFirestoreTypes.cloud.Transaction).set(
ref,
encodeSetDocument(data, options),
encodeSetDocument(documentData, options),
sanitizeSetOptions(options)
);
return this;
Expand Down
6 changes: 3 additions & 3 deletions src/GeoWriteBatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class GeoWriteBatch {
* it will be created. If you pass `SetOptions`, the provided data can be merged into the existing document.
*
* @param documentRef A reference to the document to be set.
* @param data An object of the fields and values for the document.
* @param documentData An object of the fields and values for the document.
* @param options An object to configure the set behavior. Includes custom key for location in document.
* @return This `GeoWriteBatch` instance. Used for chaining method calls.
*/
Expand All @@ -52,15 +52,15 @@ export class GeoWriteBatch {
| GeoDocumentReference
| GeoFirestoreTypes.cloud.DocumentReference
| GeoFirestoreTypes.web.DocumentReference,
data: GeoFirestoreTypes.DocumentData,
documentData: GeoFirestoreTypes.DocumentData,
options?: GeoFirestoreTypes.SetOptions
): GeoWriteBatch {
const ref = (documentRef instanceof GeoDocumentReference
? documentRef['_document']
: documentRef) as GeoFirestoreTypes.cloud.DocumentReference;
(this._writeBatch as GeoFirestoreTypes.cloud.WriteBatch).set(
ref,
encodeSetDocument(data, options),
encodeSetDocument(documentData, options),
sanitizeSetOptions(options)
);
return this;
Expand Down
84 changes: 41 additions & 43 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,6 @@ export function calculateDistance(
return radius * c;
}

/**
* Decodes the GeoDocument data. Returns non-decoded data if decoding fails.
*
* @param data The data encoded as a GeoDocument object.
* @return The decoded Firestore document or non-decoded data if decoding fails.
*/
export function decodeGeoDocumentData(
data: GeoFirestoreTypes.Document
): GeoFirestoreTypes.DocumentData {
return validateGeoDocument(data, true) ? data.d : data;
}

/**
* Decodes the GeoDocument data. Returns non-decoded data if decoding fails.
*
Expand All @@ -140,12 +128,14 @@ export function decodeGeoDocumentData(
* @return The decoded Firestore document or non-decoded data if decoding fails in an object including distance from origin.
*/
export function decodeGeoQueryDocumentSnapshotData(
data: GeoFirestoreTypes.Document,
data: GeoFirestoreTypes.GeoDocumentData,
center?: GeoFirestoreTypes.web.GeoPoint | GeoFirestoreTypes.cloud.GeoPoint
): {data: () => GeoFirestoreTypes.DocumentData; distance: number} {
): {data: () => GeoFirestoreTypes.GeoDocumentData; distance: number} {
if (validateGeoDocument(data, true)) {
const distance = center ? calculateDistance(data.l, center) : null;
return {data: () => data.d, distance};
const distance = center
? calculateDistance(data['.g'].coordinates, center)
: null;
return {data: () => data, distance};
}
return {data: () => data, distance: null};
}
Expand Down Expand Up @@ -230,18 +220,24 @@ export function encodeGeohash(
/**
* Encodes a location and geohash as a GeoDocument.
*
* @param location The location as a Firestore GeoPoint.
* @param coordinates The location as a Firestore GeoPoint.
* @param geohash The geohash of the location.
* @return The document encoded as GeoDocument object.
*/
export function encodeGeoDocument(
location: GeoFirestoreTypes.cloud.GeoPoint | GeoFirestoreTypes.web.GeoPoint,
coordinates:
| GeoFirestoreTypes.cloud.GeoPoint
| GeoFirestoreTypes.web.GeoPoint,
geohash: string,
document: GeoFirestoreTypes.DocumentData
): GeoFirestoreTypes.Document {
validateLocation(location);
): GeoFirestoreTypes.GeoDocumentData {
validateLocation(coordinates);
validateGeohash(geohash);
return {g: geohash, l: location, d: document};
document['.g'] = {
coordinates,
geohash,
};
return document;
}

/**
Expand All @@ -261,28 +257,26 @@ export function sanitizeSetOptions(
/**
* Encodes a Document used by GeoWriteBatch.set as a GeoDocument.
*
* @param data The document being set.
* @param documentData The document being set.
* @param customKey The key of the document to use as the location. Otherwise we default to `coordinates`.
* @return The document encoded as GeoDocument object.
*/
export function encodeSetDocument(
data: GeoFirestoreTypes.DocumentData,
documentData: GeoFirestoreTypes.DocumentData,
options?: GeoFirestoreTypes.SetOptions
): GeoFirestoreTypes.Document {
if (Object.prototype.toString.call(data) === '[object Object]') {
): GeoFirestoreTypes.GeoDocumentData {
if (Object.prototype.toString.call(documentData) === '[object Object]') {
const customKey = options ? options.customKey : null;
const unparsed: GeoFirestoreTypes.DocumentData =
'd' in data ? data.d : data;
const location = findCoordinates(
unparsed,
const coordinates = findCoordinates(
documentData,
customKey,
options && (options.merge || !!options.mergeFields)
);
if (location) {
const geohash: string = encodeGeohash(location);
return encodeGeoDocument(location, geohash, unparsed);
if (coordinates) {
const geohash: string = encodeGeohash(coordinates);
return encodeGeoDocument(coordinates, geohash, documentData);
}
return {d: unparsed} as GeoFirestoreTypes.Document;
return documentData;
} else {
throw new Error('document must be an object');
}
Expand Down Expand Up @@ -377,7 +371,7 @@ export function generateGeoQueryDocumentSnapshot(
center?: GeoFirestoreTypes.web.GeoPoint | GeoFirestoreTypes.cloud.GeoPoint
): GeoFirestoreTypes.QueryDocumentSnapshot {
const decoded = decodeGeoQueryDocumentSnapshotData(
snapshot.data() as GeoFirestoreTypes.Document,
snapshot.data() as GeoFirestoreTypes.GeoDocumentData,
center
);
return {
Expand Down Expand Up @@ -529,23 +523,27 @@ export function toGeoPoint(
/**
* Validates the inputted GeoDocument object and throws an error, or returns boolean, if it is invalid.
*
* @param data The GeoDocument object to be validated.
* @param documentData The GeoDocument object to be validated.
* @param flag Tells function to send up boolean if valid instead of throwing an error.
* @return Flag if data is valid
*/
export function validateGeoDocument(
data: GeoFirestoreTypes.Document,
documentData: GeoFirestoreTypes.GeoDocumentData,
flag = false
): boolean {
let error: string;

error = !validateGeohash(data.g, true) ? 'invalid geohash on object' : null;
error = !validateLocation(data.l, true)
? 'invalid location on object'
: error;

if (!data || !('d' in data) || typeof data.d !== 'object') {
error = 'no valid document found';
if (!documentData) {
error = 'no document found';
} else if ('.g' in documentData) {
error = !validateGeohash(documentData['.g'].geohash, true)
? 'invalid geohash on object'
: null;
error = !validateLocation(documentData['.g'].coordinates, true)
? 'invalid location on object'
: error;
} else {
error = 'no `.g` field found in object';
}

if (error && !flag) {
Expand Down

0 comments on commit e2ad5e5

Please sign in to comment.