Skip to content

Commit 15e49a3

Browse files
committed
Add transaction support to Database Reference.
1 parent 9ec08b8 commit 15e49a3

File tree

5 files changed

+97
-1
lines changed

5 files changed

+97
-1
lines changed

ios/Firestack/FirestackDatabase.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
}
1919

2020
@property NSMutableDictionary *dbReferences;
21+
@property NSMutableDictionary *transactions;
2122

2223
@end
2324

ios/Firestack/FirestackDatabase.m

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ - (id) init
364364
self = [super init];
365365
if (self != nil) {
366366
_dbReferences = [[NSMutableDictionary alloc] init];
367+
_transactions = [[NSMutableDictionary alloc] init];
367368
}
368369
return self;
369370
}
@@ -455,7 +456,58 @@ - (id) init
455456
}
456457
}
457458

459+
RCT_EXPORT_METHOD(beginTransaction:(NSString *) path
460+
withIdentifier:(NSString *) identifier
461+
applyLocally:(BOOL) applyLocally
462+
onComplete:(RCTResponseSenderBlock) onComplete)
463+
{
464+
NSMutableDictionary *transactionState = [NSMutableDictionary new];
465+
[_transactions setValue:transactionState forKey:identifier];
466+
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
467+
[transactionState setObject:sema forKey:@"semaphore"];
468+
469+
FIRDatabaseReference *ref = [self getPathRef:path];
470+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
471+
[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData * _Nonnull currentData) {
472+
[self sendEventWithName:DATABASE_TRANSACTION_EVENT
473+
body:@{
474+
@"id": identifier,
475+
@"originalValue": currentData.value
476+
}];
477+
// Wait for the event handler to call tryCommitTransaction
478+
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
479+
BOOL abort = [transactionState valueForKey:@"abort"];
480+
id value = [transactionState valueForKey:@"value"];
481+
[_transactions removeObjectForKey:identifier];
482+
if (abort) {
483+
return [FIRTransactionResult abort];
484+
} else {
485+
currentData.value = value;
486+
return [FIRTransactionResult successWithValue:currentData];
487+
}
488+
} andCompletionBlock:^(NSError * _Nullable error, BOOL committed, FIRDataSnapshot * _Nullable snapshot) {
489+
[self handleCallback:@"transaction" callback:onComplete databaseError:error];
490+
} withLocalEvents:applyLocally];
491+
});
492+
}
458493

494+
RCT_EXPORT_METHOD(tryCommitTransaction:(NSString *) identifier
495+
withData:(NSDictionary *) data
496+
orAbort:(BOOL) abort)
497+
{
498+
NSMutableDictionary *transactionState = [_transactions valueForKey:identifier];
499+
if (!transactionState) {
500+
NSLog(@"tryCommitTransaction for unknown ID %@", identifier);
501+
}
502+
dispatch_semaphore_t sema = [transactionState valueForKey:@"semaphore"];
503+
if (abort) {
504+
[transactionState setValue:@true forKey:@"abort"];
505+
} else {
506+
id newValue = [data valueForKey:@"value"];
507+
[transactionState setValue:newValue forKey:@"value"];
508+
}
509+
dispatch_semaphore_signal(sema);
510+
}
459511

460512
RCT_EXPORT_METHOD(on:(NSString *) path
461513
modifiersString:(NSString *) modifiersString
@@ -610,7 +662,7 @@ - (NSString *) getDBListenerKey:(NSString *) path
610662

611663
// Not sure how to get away from this... yet
612664
- (NSArray<NSString *> *)supportedEvents {
613-
return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT];
665+
return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT, DATABASE_TRANSACTION_EVENT];
614666
}
615667

616668

ios/Firestack/FirestackEvents.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ static NSString *const DEBUG_EVENT = @"debug";
2929
// Database
3030
static NSString *const DATABASE_DATA_EVENT = @"database_event";
3131
static NSString *const DATABASE_ERROR_EVENT = @"database_error";
32+
static NSString *const DATABASE_TRANSACTION_EVENT = @"database_transaction_update";
3233

3334
static NSString *const DATABASE_VALUE_EVENT = @"value";
3435
static NSString *const DATABASE_CHILD_ADDED_EVENT = @"child_added";

lib/modules/database/index.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default class Database extends Base {
1919
constructor(firestack: Object, options: Object = {}) {
2020
super(firestack, options);
2121
this.subscriptions = {};
22+
this.transactions = {};
2223
this.serverTimeOffset = 0;
2324
this.persistenceEnabled = false;
2425
this.namespace = 'firestack:database';
@@ -33,6 +34,11 @@ export default class Database extends Base {
3334
err => this._handleDatabaseError(err)
3435
);
3536

37+
this.transactionListener = FirestackDatabaseEvt.addListener(
38+
'database_transaction_update',
39+
event => this._handleDatabaseTransaction(event)
40+
);
41+
3642
this.offsetRef = this.ref('.info/serverTimeOffset');
3743
this.offsetRef.on('value', (snapshot) => {
3844
this.serverTimeOffset = snapshot.val() || this.serverTimeOffset;
@@ -153,6 +159,37 @@ export default class Database extends Base {
153159
FirestackDatabase.goOffline();
154160
}
155161

162+
addTransaction(path, updateCallback, applyLocally, onComplete) {
163+
let id = this._generateTransactionID();
164+
this.transactions[id] = updateCallback;
165+
return new Promise((resolve, reject) => {
166+
FirestackDatabase.beginTransaction(path, id, applyLocally || false, (error, result) => {
167+
onComplete && onComplete(error);
168+
if (error)
169+
reject(error);
170+
else
171+
resolve();
172+
delete this.transactions[id];
173+
});
174+
});
175+
}
176+
177+
_generateTransactionID() {
178+
// 10 char random alphanumeric
179+
return Math.random().toString(36).substr(2, 10);
180+
}
181+
182+
_handleDatabaseTransaction(event) {
183+
const {id, originalValue} = event;
184+
const updateCallback = this.transactions[id];
185+
const newValue = updateCallback(originalValue);
186+
let abort = false;
187+
if (newValue === undefined) {
188+
abort = true;
189+
}
190+
FirestackDatabase.tryCommitTransaction(id, {value: newValue}, abort);
191+
}
192+
156193
/**
157194
* INTERNALS
158195
*/

lib/modules/database/reference.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ export default class Reference extends ReferenceBase {
123123
return this.db.off(path, modifiersString, eventName, origCB);
124124
}
125125

126+
transaction(transactionUpdate, onComplete, applyLocally) {
127+
const path = this._dbPath();
128+
return this.db.addTransaction(path, transactionUpdate, applyLocally, onComplete);
129+
}
130+
126131
/**
127132
* MODIFIERS
128133
*/

0 commit comments

Comments
 (0)