Merge branch 'firestore-transactions' of https://github.com/invertase/react-native-firebase into firestore-transactions
This commit is contained in:
commit
f5836d0325
|
@ -1,7 +1,9 @@
|
||||||
package io.invertase.firebase.firestore;
|
package io.invertase.firebase.firestore;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
import com.facebook.react.bridge.Arguments;
|
import com.facebook.react.bridge.Arguments;
|
||||||
import com.facebook.react.bridge.Promise;
|
import com.facebook.react.bridge.Promise;
|
||||||
|
@ -12,8 +14,11 @@ import com.facebook.react.bridge.ReadableArray;
|
||||||
import com.facebook.react.bridge.ReadableMap;
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
import com.facebook.react.bridge.WritableMap;
|
import com.facebook.react.bridge.WritableMap;
|
||||||
import com.google.android.gms.tasks.OnCompleteListener;
|
import com.google.android.gms.tasks.OnCompleteListener;
|
||||||
|
import com.google.android.gms.tasks.OnFailureListener;
|
||||||
|
import com.google.android.gms.tasks.OnSuccessListener;
|
||||||
import com.google.android.gms.tasks.Task;
|
import com.google.android.gms.tasks.Task;
|
||||||
import com.google.firebase.FirebaseApp;
|
import com.google.firebase.FirebaseApp;
|
||||||
|
import com.google.firebase.firestore.Transaction;
|
||||||
import com.google.firebase.firestore.DocumentReference;
|
import com.google.firebase.firestore.DocumentReference;
|
||||||
import com.google.firebase.firestore.FieldValue;
|
import com.google.firebase.firestore.FieldValue;
|
||||||
import com.google.firebase.firestore.FirebaseFirestore;
|
import com.google.firebase.firestore.FirebaseFirestore;
|
||||||
|
@ -26,11 +31,12 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import io.invertase.firebase.ErrorUtils;
|
import io.invertase.firebase.ErrorUtils;
|
||||||
|
import io.invertase.firebase.Utils;
|
||||||
|
|
||||||
|
|
||||||
public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
||||||
private static final String TAG = "RNFirebaseFirestore";
|
private static final String TAG = "RNFirebaseFirestore";
|
||||||
// private SparseArray<RNFirebaseTransactionHandler> transactionHandlers = new SparseArray<>();
|
private SparseArray<RNFirebaseFirestoreTransactionHandler> transactionHandlers = new SparseArray<>();
|
||||||
|
|
||||||
RNFirebaseFirestore(ReactApplicationContext reactContext) {
|
RNFirebaseFirestore(ReactApplicationContext reactContext) {
|
||||||
super(reactContext);
|
super(reactContext);
|
||||||
|
@ -92,7 +98,7 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
||||||
break;
|
break;
|
||||||
case "SET":
|
case "SET":
|
||||||
Map<String, Object> options = (Map) write.get("options");
|
Map<String, Object> options = (Map) write.get("options");
|
||||||
if (options != null && options.containsKey("merge") && (boolean)options.get("merge")) {
|
if (options != null && options.containsKey("merge") && (boolean) options.get("merge")) {
|
||||||
batch = batch.set(ref, data, SetOptions.merge());
|
batch = batch.set(ref, data, SetOptions.merge());
|
||||||
} else {
|
} else {
|
||||||
batch = batch.set(ref, data);
|
batch = batch.set(ref, data);
|
||||||
|
@ -113,7 +119,7 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
||||||
promise.resolve(null);
|
promise.resolve(null);
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "documentBatch:onComplete:failure", task.getException());
|
Log.e(TAG, "documentBatch:onComplete:failure", task.getException());
|
||||||
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException());
|
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException) task.getException());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -160,6 +166,199 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
||||||
ref.update(data, promise);
|
ref.update(data, promise);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try clean up previous transactions on reload
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onCatalystInstanceDestroy() {
|
||||||
|
for (int i = 0, size = transactionHandlers.size(); i < size; i++) {
|
||||||
|
RNFirebaseFirestoreTransactionHandler transactionHandler = transactionHandlers.get(i);
|
||||||
|
if (transactionHandler != null) {
|
||||||
|
transactionHandler.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionHandlers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transaction Methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the internal Firestore Transaction classes instance .get(ref) method and resolves with
|
||||||
|
* the DocumentSnapshot.
|
||||||
|
*
|
||||||
|
* @param appName
|
||||||
|
* @param transactionId
|
||||||
|
* @param path
|
||||||
|
* @param promise
|
||||||
|
*/
|
||||||
|
@ReactMethod
|
||||||
|
public void transactionGetDocument(String appName, int transactionId, String path, final Promise promise) {
|
||||||
|
RNFirebaseFirestoreTransactionHandler handler = transactionHandlers.get(transactionId);
|
||||||
|
|
||||||
|
if (handler == null) {
|
||||||
|
promise.reject(
|
||||||
|
"internal-error",
|
||||||
|
"An internal error occurred whilst attempting to find a native transaction by id."
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
DocumentReference ref = getDocumentForAppPath(appName, path).getRef();
|
||||||
|
handler.getDocument(ref, promise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aborts any pending signals and deletes the transaction handler.
|
||||||
|
*
|
||||||
|
* @param appName
|
||||||
|
* @param transactionId
|
||||||
|
*/
|
||||||
|
@ReactMethod
|
||||||
|
public void transactionDispose(String appName, int transactionId) {
|
||||||
|
RNFirebaseFirestoreTransactionHandler handler = transactionHandlers.get(transactionId);
|
||||||
|
|
||||||
|
if (handler != null) {
|
||||||
|
handler.abort();
|
||||||
|
transactionHandlers.delete(transactionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals to transactionHandler that the command buffer is ready.
|
||||||
|
*
|
||||||
|
* @param appName
|
||||||
|
* @param transactionId
|
||||||
|
* @param commandBuffer
|
||||||
|
*/
|
||||||
|
@ReactMethod
|
||||||
|
public void transactionApplyBuffer(String appName, int transactionId, ReadableArray commandBuffer) {
|
||||||
|
RNFirebaseFirestoreTransactionHandler handler = transactionHandlers.get(transactionId);
|
||||||
|
|
||||||
|
if (handler != null) {
|
||||||
|
handler.signalBufferReceived(commandBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin a new transaction via AsyncTask 's
|
||||||
|
*
|
||||||
|
* @param appName
|
||||||
|
* @param transactionId
|
||||||
|
*/
|
||||||
|
@ReactMethod
|
||||||
|
public void transactionBegin(final String appName, int transactionId) {
|
||||||
|
final RNFirebaseFirestoreTransactionHandler transactionHandler = new RNFirebaseFirestoreTransactionHandler(appName, transactionId);
|
||||||
|
transactionHandlers.put(transactionId, transactionHandler);
|
||||||
|
|
||||||
|
AsyncTask.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
getFirestoreForApp(appName)
|
||||||
|
.runTransaction(new Transaction.Function<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void apply(@NonNull Transaction transaction) throws FirebaseFirestoreException {
|
||||||
|
transactionHandler.setFirestoreTransaction(transaction);
|
||||||
|
|
||||||
|
// emit the update cycle to JS land using an async task
|
||||||
|
// otherwise it gets blocked by the pending lock await
|
||||||
|
AsyncTask.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
WritableMap eventMap = transactionHandler.createEventMap(null, "update");
|
||||||
|
Utils.sendEvent(getReactApplicationContext(), "firestore_transaction_event", eventMap);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// wait for a signal to be received from JS land code
|
||||||
|
transactionHandler.await();
|
||||||
|
|
||||||
|
// exit early if aborted - has to throw an exception otherwise will just keep trying ...
|
||||||
|
if (transactionHandler.aborted) {
|
||||||
|
throw new FirebaseFirestoreException("abort", FirebaseFirestoreException.Code.ABORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// exit early if timeout from bridge - has to throw an exception otherwise will just keep trying ...
|
||||||
|
if (transactionHandler.timeout) {
|
||||||
|
throw new FirebaseFirestoreException("timeout", FirebaseFirestoreException.Code.DEADLINE_EXCEEDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// process any buffered commands from JS land
|
||||||
|
ReadableArray buffer = transactionHandler.getCommandBuffer();
|
||||||
|
|
||||||
|
// exit early if no commands
|
||||||
|
if (buffer == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0, size = buffer.size(); i < size; i++) {
|
||||||
|
ReadableMap data;
|
||||||
|
ReadableMap command = buffer.getMap(i);
|
||||||
|
String path = command.getString("path");
|
||||||
|
String type = command.getString("type");
|
||||||
|
RNFirebaseFirestoreDocumentReference documentReference = getDocumentForAppPath(appName, path);
|
||||||
|
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case "set":
|
||||||
|
data = command.getMap("data");
|
||||||
|
|
||||||
|
ReadableMap options = command.getMap("options");
|
||||||
|
Map<String, Object> setData = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data);
|
||||||
|
|
||||||
|
if (options != null && options.hasKey("merge") && options.getBoolean("merge")) {
|
||||||
|
transaction.set(documentReference.getRef(), setData, SetOptions.merge());
|
||||||
|
} else {
|
||||||
|
transaction.set(documentReference.getRef(), setData);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "update":
|
||||||
|
data = command.getMap("data");
|
||||||
|
|
||||||
|
Map<String, Object> updateData = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data);
|
||||||
|
transaction.update(documentReference.getRef(), updateData);
|
||||||
|
break;
|
||||||
|
case "delete":
|
||||||
|
transaction.delete(documentReference.getRef());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown command type at index " + i + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addOnSuccessListener(new OnSuccessListener<Void>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Void aVoid) {
|
||||||
|
if (!transactionHandler.aborted) {
|
||||||
|
Log.d(TAG, "Transaction onSuccess!");
|
||||||
|
WritableMap eventMap = transactionHandler.createEventMap(null, "complete");
|
||||||
|
Utils.sendEvent(getReactApplicationContext(), "firestore_transaction_event", eventMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addOnFailureListener(new OnFailureListener() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Exception e) {
|
||||||
|
if (!transactionHandler.aborted) {
|
||||||
|
Log.w(TAG, "Transaction onFailure.", e);
|
||||||
|
WritableMap eventMap = transactionHandler.createEventMap((FirebaseFirestoreException) e, "error");
|
||||||
|
Utils.sendEvent(getReactApplicationContext(), "firestore_transaction_event", eventMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* INTERNALS/UTILS
|
* INTERNALS/UTILS
|
||||||
*/
|
*/
|
||||||
|
@ -197,7 +396,7 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
||||||
* @param filters
|
* @param filters
|
||||||
* @param orders
|
* @param orders
|
||||||
* @param options
|
* @param options
|
||||||
* @param path @return
|
* @param path @return
|
||||||
*/
|
*/
|
||||||
private RNFirebaseFirestoreCollectionReference getCollectionForAppPath(String appName, String path,
|
private RNFirebaseFirestoreCollectionReference getCollectionForAppPath(String appName, String path,
|
||||||
ReadableArray filters,
|
ReadableArray filters,
|
||||||
|
|
|
@ -145,6 +145,10 @@ public class RNFirebaseFirestoreDocumentReference {
|
||||||
* INTERNALS/UTILS
|
* INTERNALS/UTILS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
public DocumentReference getRef() {
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
boolean hasListeners() {
|
boolean hasListeners() {
|
||||||
return !documentSnapshotListeners.isEmpty();
|
return !documentSnapshotListeners.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
package io.invertase.firebase.firestore;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.Promise;
|
||||||
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.google.firebase.firestore.DocumentReference;
|
||||||
|
import com.google.firebase.firestore.DocumentSnapshot;
|
||||||
|
import com.google.firebase.firestore.FirebaseFirestoreException;
|
||||||
|
import com.google.firebase.firestore.Transaction;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
|
class RNFirebaseFirestoreTransactionHandler {
|
||||||
|
private String appName;
|
||||||
|
private long timeoutAt;
|
||||||
|
private int transactionId;
|
||||||
|
private final ReentrantLock lock;
|
||||||
|
private final Condition condition;
|
||||||
|
private ReadableArray commandBuffer;
|
||||||
|
private Transaction firestoreTransaction;
|
||||||
|
|
||||||
|
boolean aborted = false;
|
||||||
|
boolean timeout = false;
|
||||||
|
|
||||||
|
RNFirebaseFirestoreTransactionHandler(String app, int id) {
|
||||||
|
appName = app;
|
||||||
|
transactionId = id;
|
||||||
|
updateInternalTimeout();
|
||||||
|
lock = new ReentrantLock();
|
||||||
|
condition = lock.newCondition();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* -------------
|
||||||
|
* PACKAGE API
|
||||||
|
* -------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abort the currently in progress transaction if any.
|
||||||
|
*/
|
||||||
|
void abort() {
|
||||||
|
aborted = true;
|
||||||
|
safeUnlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep a ref to the Transaction instance.
|
||||||
|
*
|
||||||
|
* @param firestoreTransaction
|
||||||
|
*/
|
||||||
|
void setFirestoreTransaction(Transaction firestoreTransaction) {
|
||||||
|
this.firestoreTransaction = firestoreTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal that the transaction buffer has been received and needs to be processed.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
*/
|
||||||
|
void signalBufferReceived(ReadableArray buffer) {
|
||||||
|
lock.lock();
|
||||||
|
|
||||||
|
try {
|
||||||
|
commandBuffer = buffer;
|
||||||
|
condition.signalAll();
|
||||||
|
} finally {
|
||||||
|
safeUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for signalBufferReceived to signal condition
|
||||||
|
*
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
void await() {
|
||||||
|
lock.lock();
|
||||||
|
|
||||||
|
updateInternalTimeout();
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (!aborted && !timeout && !condition.await(10, TimeUnit.MILLISECONDS)) {
|
||||||
|
if (System.currentTimeMillis() > timeoutAt) timeout = true;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
// should never be interrupted
|
||||||
|
} finally {
|
||||||
|
safeUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current pending command buffer.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
ReadableArray getCommandBuffer() {
|
||||||
|
return commandBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get and resolve a DocumentSnapshot from transaction.get(ref);
|
||||||
|
*
|
||||||
|
* @param ref
|
||||||
|
* @param promise
|
||||||
|
*/
|
||||||
|
void getDocument(DocumentReference ref, Promise promise) {
|
||||||
|
updateInternalTimeout();
|
||||||
|
|
||||||
|
try {
|
||||||
|
DocumentSnapshot documentSnapshot = firestoreTransaction.get(ref);
|
||||||
|
WritableMap writableMap = FirestoreSerialize.snapshotToWritableMap(documentSnapshot);
|
||||||
|
promise.resolve(writableMap);
|
||||||
|
} catch (FirebaseFirestoreException firestoreException) {
|
||||||
|
WritableMap jsError = RNFirebaseFirestore.getJSError(firestoreException);
|
||||||
|
promise.reject(jsError.getString("code"), jsError.getString("message"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event map for `firestore_transaction_event` events.
|
||||||
|
*
|
||||||
|
* @param error
|
||||||
|
* @param type
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
WritableMap createEventMap(@Nullable FirebaseFirestoreException error, String type) {
|
||||||
|
WritableMap eventMap = Arguments.createMap();
|
||||||
|
|
||||||
|
eventMap.putInt("id", transactionId);
|
||||||
|
eventMap.putString("appName", appName);
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
eventMap.putString("type", "error");
|
||||||
|
eventMap.putMap("error", RNFirebaseFirestore.getJSError(error));
|
||||||
|
} else {
|
||||||
|
eventMap.putString("type", type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* -------------
|
||||||
|
* INTERNAL API
|
||||||
|
* -------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
private void safeUnlock() {
|
||||||
|
if (lock.isLocked()) {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateInternalTimeout() {
|
||||||
|
timeoutAt = System.currentTimeMillis() + 15000;
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,14 +21,13 @@ const generateTransactionId = (): number => transactionId++;
|
||||||
*/
|
*/
|
||||||
export default class TransactionHandler {
|
export default class TransactionHandler {
|
||||||
_database: Database;
|
_database: Database;
|
||||||
_transactionListener: Function;
|
|
||||||
_transactions: { [number]: Object };
|
_transactions: { [number]: Object };
|
||||||
|
|
||||||
constructor(database: Database) {
|
constructor(database: Database) {
|
||||||
this._transactions = {};
|
this._transactions = {};
|
||||||
this._database = database;
|
this._database = database;
|
||||||
|
|
||||||
this._transactionListener = SharedEventEmitter.addListener(
|
SharedEventEmitter.addListener(
|
||||||
getAppEventName(this._database, 'database_transaction_event'),
|
getAppEventName(this._database, 'database_transaction_event'),
|
||||||
this._handleTransactionEvent.bind(this)
|
this._handleTransactionEvent.bind(this)
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { buildNativeMap } from './utils/serialize';
|
||||||
import type Firestore from './';
|
import type Firestore from './';
|
||||||
import type { TransactionMeta } from './TransactionHandler';
|
import type { TransactionMeta } from './TransactionHandler';
|
||||||
import type DocumentReference from './DocumentReference';
|
import type DocumentReference from './DocumentReference';
|
||||||
import type DocumentSnapshot from './DocumentSnapshot';
|
import DocumentSnapshot from './DocumentSnapshot';
|
||||||
import { isObject, isString } from '../../utils';
|
import { isObject, isString } from '../../utils';
|
||||||
import FieldPath from './FieldPath';
|
import FieldPath from './FieldPath';
|
||||||
import { getNativeModule } from '../../utils/native';
|
import { getNativeModule } from '../../utils/native';
|
||||||
|
@ -24,6 +24,9 @@ type SetOptions = {
|
||||||
merge: boolean,
|
merge: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO docs state all get requests must be made FIRST before any modifications
|
||||||
|
// TODO so need to validate that
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Transaction
|
* @class Transaction
|
||||||
*/
|
*/
|
||||||
|
@ -72,10 +75,9 @@ export default class Transaction {
|
||||||
*/
|
*/
|
||||||
get(documentRef: DocumentReference): Promise<DocumentSnapshot> {
|
get(documentRef: DocumentReference): Promise<DocumentSnapshot> {
|
||||||
// todo validate doc ref
|
// todo validate doc ref
|
||||||
return getNativeModule(this._firestore).transactionGetDocument(
|
return getNativeModule(this._firestore)
|
||||||
this._meta.id,
|
.transactionGetDocument(this._meta.id, documentRef.path)
|
||||||
documentRef.path
|
.then(result => new DocumentSnapshot(this._firestore, result));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -102,7 +104,7 @@ export default class Transaction {
|
||||||
type: 'set',
|
type: 'set',
|
||||||
path: documentRef.path,
|
path: documentRef.path,
|
||||||
data: buildNativeMap(data),
|
data: buildNativeMap(data),
|
||||||
options,
|
options: options || {},
|
||||||
});
|
});
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -37,13 +37,12 @@ type TransactionEvent = {
|
||||||
*/
|
*/
|
||||||
export default class TransactionHandler {
|
export default class TransactionHandler {
|
||||||
_firestore: Firestore;
|
_firestore: Firestore;
|
||||||
_transactionListener: Function;
|
|
||||||
_pending: { [number]: TransactionMeta };
|
_pending: { [number]: TransactionMeta };
|
||||||
|
|
||||||
constructor(firestore: Firestore) {
|
constructor(firestore: Firestore) {
|
||||||
this._pending = {};
|
this._pending = {};
|
||||||
this._firestore = firestore;
|
this._firestore = firestore;
|
||||||
this._transactionListener = SharedEventEmitter.addListener(
|
SharedEventEmitter.addListener(
|
||||||
getAppEventName(this._firestore, 'firestore_transaction_event'),
|
getAppEventName(this._firestore, 'firestore_transaction_event'),
|
||||||
this._handleTransactionEvent.bind(this)
|
this._handleTransactionEvent.bind(this)
|
||||||
);
|
);
|
||||||
|
@ -68,7 +67,10 @@ export default class TransactionHandler {
|
||||||
reject: null,
|
reject: null,
|
||||||
resolve: null,
|
resolve: null,
|
||||||
updateFunction,
|
updateFunction,
|
||||||
stack: new Error().stack.slice(1),
|
stack: new Error().stack
|
||||||
|
.split('\n')
|
||||||
|
.slice(4)
|
||||||
|
.join('\n'),
|
||||||
};
|
};
|
||||||
|
|
||||||
meta.transaction = new Transaction(this._firestore, meta);
|
meta.transaction = new Transaction(this._firestore, meta);
|
||||||
|
@ -92,14 +94,11 @@ export default class TransactionHandler {
|
||||||
* Destroys a local instance of a transaction meta
|
* Destroys a local instance of a transaction meta
|
||||||
*
|
*
|
||||||
* @param id
|
* @param id
|
||||||
* @param pendingAbort Notify native that there's still an transaction in
|
|
||||||
* progress that needs aborting - this is to handle a JS side
|
|
||||||
* exception
|
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_remove(id, pendingAbort = false) {
|
_remove(id) {
|
||||||
// todo confirm pending arg no longer needed
|
// todo confirm pending arg no longer needed
|
||||||
getNativeModule(this._firestore).transactionDispose(id, pendingAbort);
|
getNativeModule(this._firestore).transactionDispose(id);
|
||||||
// TODO may need delaying to next event loop
|
// TODO may need delaying to next event loop
|
||||||
delete this._pending[id];
|
delete this._pending[id];
|
||||||
}
|
}
|
||||||
|
@ -169,11 +168,15 @@ export default class TransactionHandler {
|
||||||
pendingResult = await possiblePromise;
|
pendingResult = await possiblePromise;
|
||||||
}
|
}
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
updateFailed = true; // in case the user rejects with nothing
|
// exception can still be falsey if user `Promise.reject();` 's with no args
|
||||||
|
// so we track the exception with a updateFailed boolean to ensure no fall-through
|
||||||
|
updateFailed = true;
|
||||||
finalError = exception;
|
finalError = exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
// reject the final promise and remove from native
|
// reject the final promise and remove from native
|
||||||
|
// update is failed when either the users updateFunction
|
||||||
|
// throws an error or rejects a promise
|
||||||
if (updateFailed) {
|
if (updateFailed) {
|
||||||
return reject(finalError);
|
return reject(finalError);
|
||||||
}
|
}
|
||||||
|
@ -184,7 +187,7 @@ export default class TransactionHandler {
|
||||||
transaction._pendingResult = pendingResult;
|
transaction._pendingResult = pendingResult;
|
||||||
|
|
||||||
// send the buffered update/set/delete commands for native to process
|
// send the buffered update/set/delete commands for native to process
|
||||||
return getNativeModule(this._firestore).transactionProcessUpdateResponse(
|
return getNativeModule(this._firestore).transactionApplyBuffer(
|
||||||
id,
|
id,
|
||||||
transaction._commandBuffer
|
transaction._commandBuffer
|
||||||
);
|
);
|
||||||
|
|
|
@ -154,6 +154,7 @@ export default class Firestore extends ModuleBase {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
enableNetwork(): void {
|
enableNetwork(): void {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
||||||
|
@ -162,6 +163,7 @@ export default class Firestore extends ModuleBase {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
disableNetwork(): void {
|
disableNetwork(): void {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
||||||
|
@ -180,6 +182,7 @@ export default class Firestore extends ModuleBase {
|
||||||
enablePersistence(): Promise<void> {
|
enablePersistence(): Promise<void> {
|
||||||
throw new Error('Persistence is enabled by default on the Firestore SDKs');
|
throw new Error('Persistence is enabled by default on the Firestore SDKs');
|
||||||
}
|
}
|
||||||
|
|
||||||
settings(): void {
|
settings(): void {
|
||||||
throw new Error('firebase.firestore().settings() coming soon');
|
throw new Error('firebase.firestore().settings() coming soon');
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ export const buildNativeArray = (array: Object[]): FirestoreTypeMap[] => {
|
||||||
|
|
||||||
export const buildTypeMap = (value: any): FirestoreTypeMap | null => {
|
export const buildTypeMap = (value: any): FirestoreTypeMap | null => {
|
||||||
const type = typeOf(value);
|
const type = typeOf(value);
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined || Number.isNaN(value)) {
|
||||||
return {
|
return {
|
||||||
type: 'null',
|
type: 'null',
|
||||||
value: null,
|
value: null,
|
||||||
|
|
Loading…
Reference in New Issue