2017-09-26 13:57:25 +00:00
|
|
|
/**
|
|
|
|
* @flow
|
|
|
|
* DocumentReference representation wrapper
|
|
|
|
*/
|
|
|
|
import CollectionReference from './CollectionReference';
|
2017-11-17 14:22:46 +00:00
|
|
|
import DocumentSnapshot from './DocumentSnapshot';
|
2018-01-12 11:49:45 +00:00
|
|
|
import FieldPath from './FieldPath';
|
|
|
|
import { mergeFieldPathData } from './utils';
|
2017-10-10 14:36:08 +00:00
|
|
|
import { buildNativeMap } from './utils/serialize';
|
2017-12-22 15:24:31 +00:00
|
|
|
import { getAppEventName, SharedEventEmitter } from '../../utils/events';
|
2017-12-27 15:29:38 +00:00
|
|
|
import { getLogger } from '../../utils/log';
|
2017-10-10 14:36:08 +00:00
|
|
|
import { firestoreAutoId, isFunction, isObject, isString } from '../../utils';
|
2018-01-05 17:20:02 +00:00
|
|
|
import { getNativeModule } from '../../utils/native';
|
2017-09-26 13:57:25 +00:00
|
|
|
|
2017-11-17 11:07:52 +00:00
|
|
|
import type Firestore from './';
|
2018-01-25 18:25:39 +00:00
|
|
|
import type {
|
2018-02-14 13:00:19 +00:00
|
|
|
DocumentListenOptions,
|
|
|
|
NativeDocumentSnapshot,
|
|
|
|
SetOptions,
|
|
|
|
} from './types';
|
2017-11-17 11:07:52 +00:00
|
|
|
import type Path from './Path';
|
|
|
|
|
2018-01-25 18:25:39 +00:00
|
|
|
type ObserverOnError = Object => void;
|
|
|
|
type ObserverOnNext = DocumentSnapshot => void;
|
2017-11-17 11:07:52 +00:00
|
|
|
|
2017-10-06 11:00:40 +00:00
|
|
|
type Observer = {
|
2017-11-17 11:07:52 +00:00
|
|
|
error?: ObserverOnError,
|
|
|
|
next: ObserverOnNext,
|
2018-01-25 18:25:39 +00:00
|
|
|
};
|
2017-10-06 11:00:40 +00:00
|
|
|
|
2017-11-17 11:07:52 +00:00
|
|
|
/**
|
2017-09-26 13:57:25 +00:00
|
|
|
* @class DocumentReference
|
|
|
|
*/
|
|
|
|
export default class DocumentReference {
|
|
|
|
_documentPath: Path;
|
2017-11-17 11:07:52 +00:00
|
|
|
_firestore: Firestore;
|
2017-09-26 13:57:25 +00:00
|
|
|
|
2017-11-17 11:07:52 +00:00
|
|
|
constructor(firestore: Firestore, documentPath: Path) {
|
2017-09-26 13:57:25 +00:00
|
|
|
this._documentPath = documentPath;
|
|
|
|
this._firestore = firestore;
|
|
|
|
}
|
|
|
|
|
2017-11-17 11:07:52 +00:00
|
|
|
get firestore(): Firestore {
|
2017-09-26 13:57:25 +00:00
|
|
|
return this._firestore;
|
|
|
|
}
|
|
|
|
|
|
|
|
get id(): string | null {
|
|
|
|
return this._documentPath.id;
|
|
|
|
}
|
|
|
|
|
2017-10-05 09:18:24 +00:00
|
|
|
get parent(): CollectionReference {
|
2017-09-26 13:57:25 +00:00
|
|
|
const parentPath = this._documentPath.parent();
|
2017-11-17 11:07:52 +00:00
|
|
|
if (!parentPath) {
|
|
|
|
throw new Error('Invalid document path');
|
|
|
|
}
|
2017-10-05 09:18:24 +00:00
|
|
|
return new CollectionReference(this._firestore, parentPath);
|
2017-09-26 13:57:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
get path(): string {
|
|
|
|
return this._documentPath.relativeName;
|
|
|
|
}
|
|
|
|
|
|
|
|
collection(collectionPath: string): CollectionReference {
|
|
|
|
const path = this._documentPath.child(collectionPath);
|
|
|
|
if (!path.isCollection) {
|
|
|
|
throw new Error('Argument "collectionPath" must point to a collection.');
|
|
|
|
}
|
|
|
|
|
|
|
|
return new CollectionReference(this._firestore, path);
|
|
|
|
}
|
|
|
|
|
2017-10-05 09:18:24 +00:00
|
|
|
delete(): Promise<void> {
|
2018-01-25 18:25:39 +00:00
|
|
|
return getNativeModule(this._firestore).documentDelete(this.path);
|
2017-09-26 13:57:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
get(): Promise<DocumentSnapshot> {
|
2018-01-05 17:20:02 +00:00
|
|
|
return getNativeModule(this._firestore)
|
2017-09-27 11:57:53 +00:00
|
|
|
.documentGet(this.path)
|
2017-09-26 13:57:25 +00:00
|
|
|
.then(result => new DocumentSnapshot(this._firestore, result));
|
|
|
|
}
|
|
|
|
|
2017-10-06 11:00:40 +00:00
|
|
|
onSnapshot(
|
2018-01-25 18:25:39 +00:00
|
|
|
optionsOrObserverOrOnNext:
|
|
|
|
| DocumentListenOptions
|
|
|
|
| Observer
|
|
|
|
| ObserverOnNext,
|
2017-11-17 11:07:52 +00:00
|
|
|
observerOrOnNextOrOnError?: Observer | ObserverOnNext | ObserverOnError,
|
2018-01-25 18:25:39 +00:00
|
|
|
onError?: ObserverOnError
|
2017-10-06 11:00:40 +00:00
|
|
|
) {
|
2018-01-05 17:20:02 +00:00
|
|
|
let observer: Observer;
|
2017-10-06 11:00:40 +00:00
|
|
|
let docListenOptions = {};
|
|
|
|
// Called with: onNext, ?onError
|
|
|
|
if (isFunction(optionsOrObserverOrOnNext)) {
|
|
|
|
if (observerOrOnNextOrOnError && !isFunction(observerOrOnNextOrOnError)) {
|
2018-01-25 18:25:39 +00:00
|
|
|
throw new Error(
|
|
|
|
'DocumentReference.onSnapshot failed: Second argument must be a valid function.'
|
|
|
|
);
|
2017-10-06 11:00:40 +00:00
|
|
|
}
|
2018-02-17 12:55:19 +00:00
|
|
|
// $FlowExpectedError: Not coping with the overloaded method signature
|
2018-01-05 17:20:02 +00:00
|
|
|
observer = {
|
|
|
|
next: optionsOrObserverOrOnNext,
|
|
|
|
error: observerOrOnNextOrOnError,
|
|
|
|
};
|
2018-01-25 18:25:39 +00:00
|
|
|
} else if (
|
|
|
|
optionsOrObserverOrOnNext &&
|
|
|
|
isObject(optionsOrObserverOrOnNext)
|
|
|
|
) {
|
2017-10-06 11:00:40 +00:00
|
|
|
// Called with: Observer
|
|
|
|
if (optionsOrObserverOrOnNext.next) {
|
|
|
|
if (isFunction(optionsOrObserverOrOnNext.next)) {
|
2018-01-25 18:25:39 +00:00
|
|
|
if (
|
|
|
|
optionsOrObserverOrOnNext.error &&
|
|
|
|
!isFunction(optionsOrObserverOrOnNext.error)
|
|
|
|
) {
|
|
|
|
throw new Error(
|
|
|
|
'DocumentReference.onSnapshot failed: Observer.error must be a valid function.'
|
|
|
|
);
|
2017-10-06 11:00:40 +00:00
|
|
|
}
|
2018-02-17 12:55:19 +00:00
|
|
|
// $FlowExpectedError: Not coping with the overloaded method signature
|
2018-01-05 17:20:02 +00:00
|
|
|
observer = {
|
|
|
|
next: optionsOrObserverOrOnNext.next,
|
|
|
|
error: optionsOrObserverOrOnNext.error,
|
|
|
|
};
|
2017-10-06 11:00:40 +00:00
|
|
|
} else {
|
2018-01-25 18:25:39 +00:00
|
|
|
throw new Error(
|
|
|
|
'DocumentReference.onSnapshot failed: Observer.next must be a valid function.'
|
|
|
|
);
|
2017-10-06 11:00:40 +00:00
|
|
|
}
|
2018-01-25 18:25:39 +00:00
|
|
|
} else if (
|
|
|
|
Object.prototype.hasOwnProperty.call(
|
|
|
|
optionsOrObserverOrOnNext,
|
|
|
|
'includeMetadataChanges'
|
|
|
|
)
|
|
|
|
) {
|
2017-10-06 11:00:40 +00:00
|
|
|
docListenOptions = optionsOrObserverOrOnNext;
|
|
|
|
// Called with: Options, onNext, ?onError
|
|
|
|
if (isFunction(observerOrOnNextOrOnError)) {
|
|
|
|
if (onError && !isFunction(onError)) {
|
2018-01-25 18:25:39 +00:00
|
|
|
throw new Error(
|
|
|
|
'DocumentReference.onSnapshot failed: Third argument must be a valid function.'
|
|
|
|
);
|
2017-10-06 11:00:40 +00:00
|
|
|
}
|
2018-02-17 12:55:19 +00:00
|
|
|
// $FlowExpectedError: Not coping with the overloaded method signature
|
2018-01-05 17:20:02 +00:00
|
|
|
observer = {
|
|
|
|
next: observerOrOnNextOrOnError,
|
|
|
|
error: onError,
|
|
|
|
};
|
2018-01-25 18:25:39 +00:00
|
|
|
// Called with Options, Observer
|
|
|
|
} else if (
|
|
|
|
observerOrOnNextOrOnError &&
|
|
|
|
isObject(observerOrOnNextOrOnError) &&
|
|
|
|
observerOrOnNextOrOnError.next
|
|
|
|
) {
|
2017-10-06 11:00:40 +00:00
|
|
|
if (isFunction(observerOrOnNextOrOnError.next)) {
|
2018-01-25 18:25:39 +00:00
|
|
|
if (
|
|
|
|
observerOrOnNextOrOnError.error &&
|
|
|
|
!isFunction(observerOrOnNextOrOnError.error)
|
|
|
|
) {
|
|
|
|
throw new Error(
|
|
|
|
'DocumentReference.onSnapshot failed: Observer.error must be a valid function.'
|
|
|
|
);
|
2017-10-06 11:00:40 +00:00
|
|
|
}
|
2018-01-05 17:20:02 +00:00
|
|
|
observer = {
|
|
|
|
next: observerOrOnNextOrOnError.next,
|
|
|
|
error: observerOrOnNextOrOnError.error,
|
|
|
|
};
|
2017-10-06 11:00:40 +00:00
|
|
|
} else {
|
2018-01-25 18:25:39 +00:00
|
|
|
throw new Error(
|
|
|
|
'DocumentReference.onSnapshot failed: Observer.next must be a valid function.'
|
|
|
|
);
|
2017-10-06 11:00:40 +00:00
|
|
|
}
|
|
|
|
} else {
|
2018-01-25 18:25:39 +00:00
|
|
|
throw new Error(
|
|
|
|
'DocumentReference.onSnapshot failed: Second argument must be a function or observer.'
|
|
|
|
);
|
2017-10-06 11:00:40 +00:00
|
|
|
}
|
|
|
|
} else {
|
2018-01-25 18:25:39 +00:00
|
|
|
throw new Error(
|
|
|
|
'DocumentReference.onSnapshot failed: First argument must be a function, observer or options.'
|
|
|
|
);
|
2017-10-06 11:00:40 +00:00
|
|
|
}
|
|
|
|
} else {
|
2018-01-25 18:25:39 +00:00
|
|
|
throw new Error(
|
|
|
|
'DocumentReference.onSnapshot failed: Called with invalid arguments.'
|
|
|
|
);
|
2017-10-06 11:00:40 +00:00
|
|
|
}
|
2017-10-03 09:12:25 +00:00
|
|
|
const listenerId = firestoreAutoId();
|
|
|
|
|
2018-02-14 13:00:19 +00:00
|
|
|
const listener = (nativeDocumentSnapshot: NativeDocumentSnapshot) => {
|
2018-01-25 18:25:39 +00:00
|
|
|
const documentSnapshot = new DocumentSnapshot(
|
|
|
|
this.firestore,
|
|
|
|
nativeDocumentSnapshot
|
|
|
|
);
|
2017-10-06 11:00:40 +00:00
|
|
|
observer.next(documentSnapshot);
|
2017-10-03 09:12:25 +00:00
|
|
|
};
|
2017-10-02 12:11:38 +00:00
|
|
|
|
|
|
|
// Listen to snapshot events
|
2017-12-22 15:24:31 +00:00
|
|
|
SharedEventEmitter.addListener(
|
|
|
|
getAppEventName(this._firestore, `onDocumentSnapshot:${listenerId}`),
|
2018-01-25 18:25:39 +00:00
|
|
|
listener
|
2017-10-02 12:11:38 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
// Listen for snapshot error events
|
2017-10-06 11:00:40 +00:00
|
|
|
if (observer.error) {
|
2017-12-22 15:24:31 +00:00
|
|
|
SharedEventEmitter.addListener(
|
2018-01-25 18:25:39 +00:00
|
|
|
getAppEventName(
|
|
|
|
this._firestore,
|
|
|
|
`onDocumentSnapshotError:${listenerId}`
|
|
|
|
),
|
|
|
|
observer.error
|
2017-10-02 12:11:38 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the native listener
|
2018-01-25 18:25:39 +00:00
|
|
|
getNativeModule(this._firestore).documentOnSnapshot(
|
|
|
|
this.path,
|
|
|
|
listenerId,
|
|
|
|
docListenOptions
|
|
|
|
);
|
2017-10-02 12:11:38 +00:00
|
|
|
|
|
|
|
// Return an unsubscribe method
|
2017-10-03 09:12:25 +00:00
|
|
|
return this._offDocumentSnapshot.bind(this, listenerId, listener);
|
2017-09-26 13:57:25 +00:00
|
|
|
}
|
|
|
|
|
2018-02-14 13:00:19 +00:00
|
|
|
set(data: Object, options?: SetOptions): Promise<void> {
|
2017-10-10 14:36:08 +00:00
|
|
|
const nativeData = buildNativeMap(data);
|
2018-01-25 18:25:39 +00:00
|
|
|
return getNativeModule(this._firestore).documentSet(
|
|
|
|
this.path,
|
|
|
|
nativeData,
|
2018-02-14 13:00:19 +00:00
|
|
|
options
|
2018-01-25 18:25:39 +00:00
|
|
|
);
|
2017-09-26 13:57:25 +00:00
|
|
|
}
|
|
|
|
|
2017-12-15 11:46:37 +00:00
|
|
|
update(...args: any[]): Promise<void> {
|
2017-10-06 11:36:41 +00:00
|
|
|
let data = {};
|
|
|
|
if (args.length === 1) {
|
|
|
|
if (!isObject(args[0])) {
|
2018-01-25 18:25:39 +00:00
|
|
|
throw new Error(
|
|
|
|
'DocumentReference.update failed: If using a single argument, it must be an object.'
|
|
|
|
);
|
2017-10-06 11:36:41 +00:00
|
|
|
}
|
2018-01-25 18:25:39 +00:00
|
|
|
// eslint-disable-next-line prefer-destructuring
|
2017-10-06 11:36:41 +00:00
|
|
|
data = args[0];
|
|
|
|
} else if (args.length % 2 === 1) {
|
2018-01-25 18:25:39 +00:00
|
|
|
throw new Error(
|
|
|
|
'DocumentReference.update failed: Must have either a single object argument, or equal numbers of key/value pairs.'
|
|
|
|
);
|
2017-10-06 11:36:41 +00:00
|
|
|
} else {
|
|
|
|
for (let i = 0; i < args.length; i += 2) {
|
|
|
|
const key = args[i];
|
|
|
|
const value = args[i + 1];
|
2018-01-12 11:49:45 +00:00
|
|
|
if (isString(key)) {
|
|
|
|
data[key] = value;
|
|
|
|
} else if (key instanceof FieldPath) {
|
|
|
|
data = mergeFieldPathData(data, key._segments, value);
|
|
|
|
} else {
|
2018-01-25 18:25:39 +00:00
|
|
|
throw new Error(
|
|
|
|
`DocumentReference.update failed: Argument at index ${i} must be a string or FieldPath`
|
|
|
|
);
|
2017-10-06 11:36:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-10-10 14:36:08 +00:00
|
|
|
const nativeData = buildNativeMap(data);
|
2018-01-25 18:25:39 +00:00
|
|
|
return getNativeModule(this._firestore).documentUpdate(
|
|
|
|
this.path,
|
|
|
|
nativeData
|
|
|
|
);
|
2017-09-26 13:57:25 +00:00
|
|
|
}
|
|
|
|
|
2017-10-08 18:40:46 +00:00
|
|
|
/**
|
|
|
|
* INTERNALS
|
|
|
|
*/
|
|
|
|
|
2017-09-26 13:57:25 +00:00
|
|
|
/**
|
2017-10-03 09:12:25 +00:00
|
|
|
* Remove document snapshot listener
|
2017-10-02 12:11:38 +00:00
|
|
|
* @param listener
|
2017-09-26 13:57:25 +00:00
|
|
|
*/
|
2017-11-17 11:07:52 +00:00
|
|
|
_offDocumentSnapshot(listenerId: string, listener: Function) {
|
2017-12-27 15:29:38 +00:00
|
|
|
getLogger(this._firestore).info('Removing onDocumentSnapshot listener');
|
2018-01-25 18:25:39 +00:00
|
|
|
SharedEventEmitter.removeListener(
|
|
|
|
getAppEventName(this._firestore, `onDocumentSnapshot:${listenerId}`),
|
|
|
|
listener
|
|
|
|
);
|
|
|
|
SharedEventEmitter.removeListener(
|
|
|
|
getAppEventName(this._firestore, `onDocumentSnapshotError:${listenerId}`),
|
|
|
|
listener
|
|
|
|
);
|
|
|
|
getNativeModule(this._firestore).documentOffSnapshot(this.path, listenerId);
|
2017-09-26 13:57:25 +00:00
|
|
|
}
|
|
|
|
}
|