From 31fc14d6025e73049807d08b9e86ab3665819c4c Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Thu, 8 Oct 2015 01:52:37 -0700 Subject: [PATCH 1/3] Support (de)serialization of objects in RPC Everything is a dictionary with either a `value` key or an `id` key. If it's a value, then it will recursively be (de)serialized. --- ReactNative/RealmRPC.mm | 58 ++++++++++++++++++++++++++++++++++++--- lib/lists.js | 9 ++++--- lib/objects.js | 7 +++-- lib/realm.js | 3 ++- lib/results.js | 7 ++--- lib/rpc.js | 60 +++++++++++++++++++++++++++++++++++------ lib/util.js | 2 ++ 7 files changed, 122 insertions(+), 24 deletions(-) diff --git a/ReactNative/RealmRPC.mm b/ReactNative/RealmRPC.mm index ea60f0c7..c27d46e7 100644 --- a/ReactNative/RealmRPC.mm +++ b/ReactNative/RealmRPC.mm @@ -181,10 +181,11 @@ static JSGlobalContextRef s_context; JSValueRef exception = NULL; NSArray *arguments = dict[@"arguments"]; - JSValueRef argumentValues[arguments.count]; - JSContext *context = [JSContext contextWithJSGlobalContextRef:s_context]; - for (int i = 0; i < arguments.count; i++) { - argumentValues[i] = [JSValue valueWithObject:arguments[i] inContext:context].JSValueRef; + NSUInteger count = arguments.count; + JSValueRef argumentValues[count]; + + for (NSUInteger i = 0; i < count; i++) { + argumentValues[i] = [self valueFromDictionary:arguments[i]]; } JSValueRef ret; @@ -302,4 +303,53 @@ static JSGlobalContextRef s_context; }; } ++ (JSValueRef)valueFromDictionary:(NSDictionary *)dict { + RPCObjectID oid = [dict[@"id"] longValue]; + if (oid) { + return s_objects[oid]; + } + + id value = dict[@"value"]; + if (!value) { + return JSValueMakeUndefined(s_context); + } + else if ([value isKindOfClass:[NSNull class]]) { + return JSValueMakeNull(s_context); + } + else if ([value isKindOfClass:[@YES class]]) { + return JSValueMakeBoolean(s_context, [value boolValue]); + } + else if ([value isKindOfClass:[NSNumber class]]) { + return JSValueMakeNumber(s_context, [value doubleValue]); + } + else if ([value isKindOfClass:[NSString class]]) { + return RJSValueForString(s_context, std::string([value UTF8String])); + } + else if ([value isKindOfClass:[NSArray class]]) { + NSUInteger count = [value count]; + std::vector jsValues(count); + + for (NSUInteger i = 0; i < count; i++) { + jsValues[i] = [self valueFromDictionary:value[i]]; + } + + return JSObjectMakeArray(s_context, count, jsValues.data(), NULL); + } + else if ([value isKindOfClass:[NSDictionary class]]) { + JSObjectRef jsObject = JSObjectMake(s_context, NULL, NULL); + + for (NSString *key in value) { + JSValueRef jsValue = [self valueFromDictionary:value[key]]; + JSStringRef jsKey = JSStringCreateWithCFString((__bridge CFStringRef)key); + + JSObjectSetProperty(s_context, jsObject, jsKey, jsValue, 0, NULL); + JSStringRelease(jsKey); + } + + return jsObject; + } + + return JSValueMakeUndefined(s_context); +} + @end diff --git a/lib/lists.js b/lib/lists.js index d127e815..ccb15e43 100644 --- a/lib/lists.js +++ b/lib/lists.js @@ -3,8 +3,9 @@ let rpc = require('./rpc'); let util = require('./util'); -let idKey = Symbol(); -let realmKey = Symbol(); +let idKey = util.idKey; +let realmKey = util.realmKey; +let resizeListKey = util.resizeListKey; let prototype = {}; exports.create = create; @@ -26,7 +27,7 @@ exports.create = create; } let result = rpc.callListMethod(realmId, listId, name, Array.from(arguments)); - this[util.resizeListKey](); + this[resizeListKey](); return result; } @@ -40,7 +41,7 @@ function create(realmId, info) { list[realmKey] = realmId; list[idKey] = info.id; - list[util.resizeListKey](size); + list[resizeListKey](size); return list; } diff --git a/lib/objects.js b/lib/objects.js index 3ca11f1b..9783fc67 100644 --- a/lib/objects.js +++ b/lib/objects.js @@ -1,10 +1,10 @@ 'use strict'; let rpc = require('./rpc'); +let util = require('./util'); -let idKey = Symbol(); -let realmKey = Symbol(); -let schemaKey = Symbol(); +let idKey = util.idKey; +let realmKey = util.realmKey; let registeredConstructors = {}; exports.create = create; @@ -18,7 +18,6 @@ function create(realmId, info) { object[realmKey] = realmId; object[idKey] = info.id; - object[schemaKey] = schema; for (let prop of schema.properties) { let name = prop.name; diff --git a/lib/realm.js b/lib/realm.js index 3e067cec..0ad42a65 100644 --- a/lib/realm.js +++ b/lib/realm.js @@ -5,8 +5,9 @@ let objects = require('./objects'); let results = require('./results'); let rpc = require('./rpc'); let types = require('./types'); +let util = require('./util'); -let realmKey = Symbol(); +let realmKey = util.realmKey; // TODO: DATA rpc.registerTypeConverter(types.DATE, (_, info) => new Date(info.value)); diff --git a/lib/results.js b/lib/results.js index d71ec514..c5f3f7e4 100644 --- a/lib/results.js +++ b/lib/results.js @@ -3,8 +3,9 @@ let rpc = require('./rpc'); let util = require('./util'); -let idKey = Symbol(); -let realmKey = Symbol(); +let idKey = util.idKey; +let realmKey = util.realmKey; +let resizeListKey = util.resizeListKey; exports.create = create; @@ -15,7 +16,7 @@ function create(realmId, info) { results[realmKey] = realmId; results[idKey] = info.resultsId; - results[util.resizeListKey](size); + results[resizeListKey](size); return results; } diff --git a/lib/rpc.js b/lib/rpc.js index fdaf30c6..ef830671 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -1,7 +1,11 @@ 'use strict'; +let util = require('./util'); + let DEVICE_HOST = 'localhost:8082'; +let idKey = util.idKey; +let realmKey = util.realmKey; let typeConverters = {}; let XMLHttpRequest = window.XMLHttpRequest; @@ -52,7 +56,7 @@ function getObjects(realmId, type, predicate) { function getObjectProperty(realmId, objectId, name) { let result = sendRequest('get_property', {realmId, objectId, name}); - return convert(realmId, result); + return deserialize(realmId, result); } function setObjectProperty(realmId, objectId, name, value) { @@ -61,7 +65,7 @@ function setObjectProperty(realmId, objectId, name, value) { function getListItem(realmId, listId, index) { let result = sendRequest('get_list_item', {realmId, listId, index}); - return convert(realmId, result); + return deserialize(realmId, result); } function setListItem(realmId, listId, index, value) { @@ -73,13 +77,17 @@ function getListSize(realmId, listId) { } function callListMethod(realmId, listId, name, args) { + if (args) { + args = args.map((arg) => serialize(realmId, arg)); + } + let result = sendRequest('call_list_method', {realmId, listId, name, arguments: args}); - return convert(realmId, result); + return deserialize(realmId, result); } function getResultsItem(realmId, resultsId, index) { let result = sendRequest('get_results_item', {realmId, resultsId, index}); - return convert(realmId, result); + return deserialize(realmId, result); } function getResultsSize(realmId, resultsId) { @@ -98,9 +106,45 @@ function commitTransaction(realmId) { sendRequest('commit_transaction', {realmId}); } -function convert(realmId, info) { - let handler = typeConverters[info.type]; - return handler ? handler(realmId, info) : info.value; +function serialize(realmId, value) { + if (!value || typeof value != 'object') { + return {value: value}; + } + + let id = value[idKey]; + if (id) { + if (value[realmKey] != realmId) { + throw new Error('Unable to serialize value from another Realm'); + } + + return {id: id}; + } + + if (Array.isArray(value)) { + let array = value.map((item) => serialize(realmId, item)); + return {value: array}; + } + + let object = {}; + for (let key in value) { + object[key] = serialize(realmId, value[key]); + } + return {value: object}; +} + +function deserialize(realmId, info) { + let type = info.type; + let handler = type && typeConverters[type]; + if (handler) { + return handler(realmId, info); + } + + let value = info.value; + if (value && Array.isArray(value)) { + return value.map((item) => deserialize(realmId, item)); + } + + return value; } function sendRequest(command, data) { @@ -116,7 +160,7 @@ function sendRequest(command, data) { } let response = JSON.parse(request.responseText); - + if (!response || response.error) { throw new Error((response && response.error) || 'Invalid response for "' + command + '"'); } diff --git a/lib/util.js b/lib/util.js index 116ffdb0..bd18c4ba 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,5 +1,7 @@ 'use strict'; +let idKey = exports.idKey = Symbol(); +let realmKey = exports.realmKey = Symbol(); let resizeListKey = exports.resizeListKey = Symbol(); exports.createList = createList; From ebb400ed4131b283563ea269882c92a1ad4633d1 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Thu, 8 Oct 2015 01:53:22 -0700 Subject: [PATCH 2/3] Support delete and deleteAll from RPC --- ReactNative/RealmRPC.mm | 29 ++++++++++++++++++++++++++++- lib/realm.js | 8 ++++---- lib/rpc.js | 11 +++++++++++ src/RJSRealm.hpp | 2 ++ 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/ReactNative/RealmRPC.mm b/ReactNative/RealmRPC.mm index c27d46e7..5c7d40d1 100644 --- a/ReactNative/RealmRPC.mm +++ b/ReactNative/RealmRPC.mm @@ -73,7 +73,34 @@ static JSGlobalContextRef s_context; inContext:[JSContext contextWithJSGlobalContextRef:s_context]] JSValueRef]; JSValueRef exception = NULL; RPCObjectID oid = [self storeObject:(JSObjectRef)RealmCreateObject(s_context, NULL, s_objects[realmId], 1, &value, &exception)]; - return @{@"result": @{@"type": @"PropTypesOBJECT", @"id": @(oid)}}; + + if (exception) { + return @{@"error": @(RJSStringForValue(s_context, exception).c_str())}; + } + return @{@"result": @{@"type": @(RJSTypeGet(realm::PropertyTypeObject).c_str()), @"id": @(oid)}}; + }; + s_requests["/delete_object"] = [=](NSDictionary *dict) { + RPCObjectID realmId = [dict[@"realmId"] longValue]; + JSValueRef jsObject = [self valueFromDictionary:dict[@"object"]]; + JSValueRef exception = NULL; + + RealmDelete(s_context, NULL, s_objects[realmId], 1, &jsObject, &exception); + + if (exception) { + return @{@"error": @(RJSStringForValue(s_context, exception).c_str())}; + } + return @{}; + }; + s_requests["/delete_all"] = [=](NSDictionary *dict) { + RPCObjectID realmId = [dict[@"realmId"] longValue]; + JSValueRef exception = NULL; + + RealmDeleteAll(s_context, NULL, s_objects[realmId], 0, NULL, &exception); + + if (exception) { + return @{@"error": @(RJSStringForValue(s_context, exception).c_str())}; + } + return @{}; }; s_requests["/dispose_realm"] = [=](NSDictionary *dict) { RPCObjectID realmId = [dict[@"realmId"] longValue]; diff --git a/lib/realm.js b/lib/realm.js index 0ad42a65..c7961d7d 100644 --- a/lib/realm.js +++ b/lib/realm.js @@ -44,13 +44,13 @@ class Realm { return objects.create(realmId, info); } - + delete(object) { - // TODO + rpc.deleteObject(this[realmKey], object); } - + deleteAll() { - // TODO + rpc.deleteAll(this[realmKey]); } objects(type, predicate) { diff --git a/lib/rpc.js b/lib/rpc.js index ef830671..80395a51 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -21,6 +21,8 @@ exports.registerTypeConverter = registerTypeConverter; exports.createRealm = createRealm; exports.createObject = createObject; +exports.deleteObject = deleteObject; +exports.deleteAllObjects = deleteAllObjects; exports.getObjects = getObjects; exports.getObjectProperty = getObjectProperty; @@ -50,6 +52,15 @@ function createObject(realmId, type, values) { return sendRequest('create_object', {realmId, type, values}); } +function deleteObject(realmId, object) { + object = serialize(realmId, object); + sendRequest('delete_object', {realmId, object}); +} + +function deleteAllObjects(realmId) { + sendRequest('delete_all', {realmId}); +} + function getObjects(realmId, type, predicate) { return sendRequest('get_objects', {realmId, type, predicate}); } diff --git a/src/RJSRealm.hpp b/src/RJSRealm.hpp index f5090ec8..5082d5d8 100644 --- a/src/RJSRealm.hpp +++ b/src/RJSRealm.hpp @@ -27,4 +27,6 @@ void RJSSetDefaultPath(std::string path); JSObjectRef RealmConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException); JSValueRef RealmCreateObject(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException); +JSValueRef RealmDelete(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException); +JSValueRef RealmDeleteAll(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException); JSValueRef RealmObjects(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException); \ No newline at end of file From 3c9fb1a32341716391dab51307a51f083abfbc5f Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Thu, 8 Oct 2015 02:00:10 -0700 Subject: [PATCH 3/3] Fix for setting properties through RPC --- ReactNative/RealmRPC.mm | 8 ++++---- lib/rpc.js | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ReactNative/RealmRPC.mm b/ReactNative/RealmRPC.mm index 5c7d40d1..98c619c4 100644 --- a/ReactNative/RealmRPC.mm +++ b/ReactNative/RealmRPC.mm @@ -122,11 +122,11 @@ static JSGlobalContextRef s_context; return @{@"result": [self resultForJSValue:propertyValue]}; }; s_requests["/set_property"] = [=](NSDictionary *dict) { - JSValueRef exception = NULL; JSStringRef propString = RJSStringForString([dict[@"name"] UTF8String]); - RPCObjectID realmId = [dict[@"realmId"] longValue]; - JSValueRef value = [[JSValue valueWithObject:dict[@"value"] - inContext:[JSContext contextWithJSGlobalContextRef:s_context]] JSValueRef]; + RPCObjectID realmId = [dict[@"objectId"] longValue]; + JSValueRef value = [self valueFromDictionary:dict[@"value"]]; + JSValueRef exception = NULL; + ObjectSetProperty(s_context, s_objects[realmId], propString, value, &exception); JSStringRelease(propString); diff --git a/lib/rpc.js b/lib/rpc.js index 80395a51..f5850c60 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -71,6 +71,7 @@ function getObjectProperty(realmId, objectId, name) { } function setObjectProperty(realmId, objectId, name, value) { + value = serialize(realmId, value); sendRequest('set_property', {realmId, objectId, name, value}); }