Merge pull request #63 from realm/sk-chrome-apis

Finish implementing APIs for Chrome
This commit is contained in:
Scott Kyle 2015-10-20 01:23:40 -07:00
commit b98e89db22
20 changed files with 405 additions and 369 deletions

48
lib/constants.js Normal file
View File

@ -0,0 +1,48 @@
'use strict';
let keys = {};
let objectTypes = {};
let propTypes = {};
[
'id',
'realm',
'resize',
'type',
].forEach(function(name) {
keys[name] = Symbol();
});
[
'FUNCTION',
'NOTIFICATION',
'REALM',
'RESULTS',
].forEach(function(type) {
Object.defineProperty(objectTypes, type, {
value: 'ObjectTypes' + type,
});
});
[
'BOOL',
'INT',
'FLOAT',
'DOUBLE',
'STRING',
'DATE',
'DATA',
'OBJECT',
'LIST',
].forEach(function(type) {
Object.defineProperty(propTypes, type, {
value: 'PropTypes' + type,
enumerable: true,
});
});
module.exports = {
keys,
objectTypes,
propTypes
};

View File

@ -1,63 +1,28 @@
'use strict';
let rpc = require('./rpc');
let constants = require('./constants');
let util = require('./util');
let idKey = util.idKey;
let realmKey = util.realmKey;
let resizeListKey = util.resizeListKey;
let prototype = {};
module.exports = {
create,
};
exports.create = create;
class List {}
[
util.createMethods(List.prototype, constants.propTypes.LIST, [
'pop',
'shift',
'push',
'unshift',
'splice',
].forEach(function(name) {
Object.defineProperty(prototype, name, {
value: function() {
let listId = this[idKey];
let realmId = this[realmKey];
if (!listId || !realmId) {
throw new TypeError(name + ' method was not called on a List!');
}
let result = rpc.callListMethod(realmId, listId, name, Array.from(arguments));
this[resizeListKey]();
return result;
}
});
});
], true);
function create(realmId, info) {
let list = util.createList(prototype, getterForLength, getterForIndex, setterForIndex);
let size = info.size;
let meta = util.createList(List.prototype, realmId, info, true);
let list = Object.create(meta);
list[realmKey] = realmId;
list[idKey] = info.id;
list[resizeListKey](size);
// This will make attempts at assigning to out-of-bounds indices throw an exception.
Object.preventExtensions(list);
return list;
}
function getterForLength() {
return rpc.getListSize(this[realmKey], this[idKey]);
}
function getterForIndex(index) {
return function() {
return rpc.getListItem(this[realmKey], this[idKey], index);
};
}
function setterForIndex(index) {
return function(value) {
rpc.setListItem(this[realmKey], this[idKey], index, value);
};
}

21
lib/notifications.js Normal file
View File

@ -0,0 +1,21 @@
'use strict';
let constants = require('./constants');
let {keys} = constants;
module.exports = {
create,
};
class Notification {}
function create(realmId, info) {
let notification = new Notification();
notification[keys.realm] = realmId;
notification[keys.id] = info.id;
notification[keys.type] = info.type;
return notification;
}

View File

@ -1,14 +1,15 @@
'use strict';
let rpc = require('./rpc');
let constants = require('./constants');
let util = require('./util');
let idKey = util.idKey;
let realmKey = util.realmKey;
let {keys} = constants;
let registeredConstructors = {};
exports.create = create;
exports.registerConstructors = registerConstructors;
module.exports = {
create,
registerConstructors,
};
function create(realmId, info) {
let schema = info.schema;
@ -16,15 +17,16 @@ function create(realmId, info) {
let object = constructor ? Object.create(constructor.prototype) : {};
let props = {};
object[realmKey] = realmId;
object[idKey] = info.id;
object[keys.realm] = realmId;
object[keys.id] = info.id;
object[keys.type] = info.type;
for (let prop of schema.properties) {
let name = prop.name;
props[name] = {
get: getterForProperty(name),
set: setterForProperty(name),
get: util.getterForProperty(name),
set: util.setterForProperty(name),
};
}
@ -36,15 +38,3 @@ function create(realmId, info) {
function registerConstructors(realmId, constructors) {
registeredConstructors[realmId] = constructors;
}
function getterForProperty(name) {
return function() {
return rpc.getObjectProperty(this[realmKey], this[idKey], name);
};
}
function setterForProperty(name) {
return function(value) {
rpc.setObjectProperty(this[realmKey], this[idKey], name, value);
};
}

View File

@ -1,19 +1,24 @@
'use strict';
let constants = require('./constants');
let lists = require('./lists');
let objects = require('./objects');
let notifications = require('./notifications');
let results = require('./results');
let rpc = require('./rpc');
let types = require('./types');
let util = require('./util');
let realmKey = util.realmKey;
let {keys, propTypes, objectTypes} = constants;
let notificationsKey = Symbol();
let notificationCallbackKey = Symbol();
let resultsKey = Symbol();
// TODO: DATA
rpc.registerTypeConverter(types.DATE, (_, info) => new Date(info.value));
rpc.registerTypeConverter(types.LIST, lists.create);
rpc.registerTypeConverter(types.OBJECT, objects.create);
rpc.registerTypeConverter('ObjectTypesRESULTS', results.create);
rpc.registerTypeConverter(propTypes.DATE, (_, info) => new Date(info.value));
rpc.registerTypeConverter(propTypes.LIST, lists.create);
rpc.registerTypeConverter(propTypes.OBJECT, objects.create);
rpc.registerTypeConverter(objectTypes.NOTIFICATION, notifications.create);
rpc.registerTypeConverter(objectTypes.RESULTS, results.create);
class Realm {
constructor(config) {
@ -30,17 +35,48 @@ class Realm {
}
}
let realmId = this[realmKey] = rpc.createRealm(Array.from(arguments));
let realmId = rpc.createRealm(Array.from(arguments));
objects.registerConstructors(realmId, constructors);
this[keys.id] = realmId;
this[keys.realm] = realmId;
this[keys.type] = objectTypes.REALM;
this[notificationsKey] = [];
this[resultsKey] = [];
[
'path',
'schemaVersion',
].forEach((name) => {
Object.defineProperty(this, name, {get: util.getterForProperty(name)});
});
}
addNotification(callback) {
// TODO
if (typeof callback != 'function') {
throw new Error('Realm.addNotification must be passed a function!');
}
let method = util.createMethod(objectTypes.REALM, 'addNotification');
let notification = method.apply(this, arguments);
notification[notificationCallbackKey] = callback;
this[notificationsKey].push(notification);
return notification;
}
objects() {
let method = util.createMethod(objectTypes.REALM, 'objects');
let results = method.apply(this, arguments);
this[resultsKey].push(results);
return results;
}
write(callback) {
let realmId = this[realmKey];
let realmId = this[keys.realm];
if (!realmId) {
throw new TypeError('write method was not called on a Realm object!');
@ -59,34 +95,36 @@ class Realm {
}
rpc.commitTransaction(realmId);
for (let results of this[resultsKey]) {
results[keys.resize]();
}
for (let notification of this[notificationsKey]) {
let callback = notification[notificationCallbackKey];
callback(this, 'DidChangeNotification');
}
}
}
[
util.createMethods(Realm.prototype, objectTypes.REALM, [
'close',
'create',
'delete',
'deleteAll',
'objects',
].forEach(function(name) {
Object.defineProperty(Realm.prototype, name, {
value: function() {
let realmId = this[realmKey];
]);
if (!realmId) {
throw new TypeError(name + ' method was not called on a Realm object!');
}
return rpc.callRealmMethod(realmId, name, Array.from(arguments));
}
});
});
Object.defineProperty(Realm, 'Types', {value: types});
Object.defineProperty(Realm, 'clearTestState', {
value: function() {
rpc.clearTestState();
}
Object.defineProperties(Realm, {
Types: {
value: Object.freeze(propTypes),
},
defaultPath: {
get: rpc.getDefaultPath,
set: rpc.setDefaultPath,
},
clearTestState: {
value: rpc.clearTestState,
},
});
module.exports = Realm;

View File

@ -1,32 +1,18 @@
'use strict';
let rpc = require('./rpc');
let constants = require('./constants');
let util = require('./util');
let idKey = util.idKey;
let realmKey = util.realmKey;
let resizeListKey = util.resizeListKey;
module.exports = {
create,
};
exports.create = create;
class Results {}
util.createMethods(Results.prototype, constants.objectTypes.RESULTS, [
'sortByProperty',
]);
function create(realmId, info) {
let results = util.createList(null, getterForLength, getterForIndex);
let size = info.size;
results[realmKey] = realmId;
results[idKey] = info.id;
results[resizeListKey](size);
return results;
}
function getterForLength() {
return rpc.getResultsSize(this[realmKey], this[idKey]);
}
function getterForIndex(index) {
return function() {
return rpc.getResultsItem(this[realmKey], this[idKey], index);
};
return util.createList(Results.prototype, realmId, info);
}

View File

@ -1,11 +1,10 @@
'use strict';
let util = require('./util');
let constants = require('./constants');
let DEVICE_HOST = 'localhost:8082';
let idKey = util.idKey;
let realmKey = util.realmKey;
let {keys, objectTypes, propTypes} = constants;
let typeConverters = {};
let XMLHttpRequest = window.XMLHttpRequest;
@ -17,32 +16,34 @@ if (XMLHttpRequest.__proto__ != window.XMLHttpRequestEventTarget) {
window.XMLHttpRequest = override;
}
exports.registerTypeConverter = registerTypeConverter;
module.exports = {
registerTypeConverter,
exports.createRealm = createRealm;
exports.callRealmMethod = callRealmMethod;
getDefaultPath,
setDefaultPath,
createRealm,
callMethod,
getProperty,
setProperty,
beginTransaction,
cancelTransaction,
commitTransaction,
exports.getObjectProperty = getObjectProperty;
exports.setObjectProperty = setObjectProperty;
exports.getListItem = getListItem;
exports.setListItem = setListItem;
exports.getListSize = getListSize;
exports.callListMethod = callListMethod;
exports.getResultsItem = getResultsItem;
exports.getResultsSize = getResultsSize;
exports.beginTransaction = beginTransaction;
exports.cancelTransaction = cancelTransaction;
exports.commitTransaction = commitTransaction;
exports.clearTestState = clearTestState;
clearTestState,
};
function registerTypeConverter(type, handler) {
typeConverters[type] = handler;
}
function getDefaultPath() {
return sendRequest('get_default_path');
}
function setDefaultPath(path) {
sendRequest('set_default_path', {path});
}
function createRealm(args) {
if (args) {
args = args.map((arg) => serialize(null, arg));
@ -51,54 +52,23 @@ function createRealm(args) {
return sendRequest('create_realm', {arguments: args});
}
function callRealmMethod(realmId, name, args) {
function callMethod(realmId, id, name, args) {
if (args) {
args = args.map((arg) => serialize(realmId, arg));
}
let result = sendRequest('call_realm_method', {realmId, name, arguments: args});
let result = sendRequest('call_method', {realmId, id, name, arguments: args});
return deserialize(realmId, result);
}
function getObjectProperty(realmId, objectId, name) {
let result = sendRequest('get_property', {realmId, objectId, name});
function getProperty(realmId, id, name) {
let result = sendRequest('get_property', {realmId, id, name});
return deserialize(realmId, result);
}
function setObjectProperty(realmId, objectId, name, value) {
function setProperty(realmId, id, name, value) {
value = serialize(realmId, value);
sendRequest('set_property', {realmId, objectId, name, value});
}
function getListItem(realmId, listId, index) {
let result = sendRequest('get_list_item', {realmId, listId, index});
return deserialize(realmId, result);
}
function setListItem(realmId, listId, index, value) {
sendRequest('set_list_item', {realmId, listId, index, value: serialize(realmId, value)});
}
function getListSize(realmId, listId) {
return sendRequest('get_list_size', {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 deserialize(realmId, result);
}
function getResultsItem(realmId, resultsId, index) {
let result = sendRequest('get_results_item', {realmId, resultsId, index});
return deserialize(realmId, result);
}
function getResultsSize(realmId, resultsId) {
return sendRequest('get_results_size', {realmId, resultsId});
sendRequest('set_property', {realmId, id, name, value});
}
function beginTransaction(realmId) {
@ -118,17 +88,25 @@ function clearTestState() {
}
function serialize(realmId, value) {
if (typeof value == 'function') {
return {type: objectTypes.FUNCTION};
}
if (!value || typeof value != 'object') {
return {value: value};
}
let id = value[idKey];
let id = value[keys.id];
if (id) {
if (value[realmKey] != realmId) {
if (value[keys.realm] != realmId) {
throw new Error('Unable to serialize value from another Realm');
}
return {id: id};
return {id};
}
if (value instanceof Date) {
return {type: propTypes.DATE, value: value.getTime()};
}
if (Array.isArray(value)) {

View File

@ -1,19 +0,0 @@
'use strict';
let types = {};
[
'BOOL',
'INT',
'FLOAT',
'DOUBLE',
'STRING',
'DATE',
'DATA',
'OBJECT',
'LIST',
].forEach(function(type) {
types[type] = 'PropTypes' + type;
});
module.exports = Object.freeze(types);

View File

@ -1,20 +1,27 @@
'use strict';
let idKey = exports.idKey = Symbol();
let realmKey = exports.realmKey = Symbol();
let resizeListKey = exports.resizeListKey = Symbol();
let constants = require('./constants');
let rpc = require('./rpc');
exports.createList = createList;
let {keys} = constants;
function createList(prototype, getterForLength, getterForIndex, setterForIndex) {
var list = prototype ? Object.create(prototype) : {};
var size = 0;
module.exports = {
createList,
createMethods,
createMethod,
getterForProperty,
setterForProperty,
};
Object.defineProperty(list, 'length', {get: getterForLength});
function createList(prototype, realmId, info, mutable) {
let list = Object.create(prototype);
let size = 0;
list[resizeListKey] = function(length) {
Object.defineProperty(list, 'length', {get: getterForProperty('length')});
list[keys.resize] = function(length) {
if (length == null) {
length = this.length;
length = list.length;
}
if (length == size) {
return;
@ -25,23 +32,74 @@ function createList(prototype, getterForLength, getterForIndex, setterForIndex)
for (let i = size; i < length; i++) {
props[i] = {
get: getterForIndex(i),
set: setterForIndex && setterForIndex(i),
get: getterForProperty(i),
set: mutable ? setterForProperty(i) : undefined,
enumerable: true,
configurable: true,
};
}
Object.defineProperties(this, props);
Object.defineProperties(list, props);
}
else if (length < size) {
for (let i = size - 1; i >= length; i--) {
delete this[i];
delete list[i];
}
}
size = length;
};
list[keys.realm] = realmId;
list[keys.id] = info.id;
list[keys.type] = info.type;
list[keys.resize](info.size);
return list;
}
function createMethods(prototype, type, methodNames, resize) {
let props = {};
for (let name of methodNames) {
props[name] = {
value: createMethod(type, name, resize),
};
}
Object.defineProperties(prototype, props);
}
function createMethod(type, name, resize) {
return function() {
let realmId = this[keys.realm];
let id = this[keys.id];
if (!realmId || !id) {
throw new TypeError(name + ' method was not called a Realm object!');
}
if (this[keys.type] !== type) {
throw new TypeError(name + ' method was called on an object of the wrong type!');
}
let result = rpc.callMethod(realmId, id, name, Array.from(arguments));
if (resize) {
this[keys.resize]();
}
return result;
};
}
function getterForProperty(name) {
return function() {
return rpc.getProperty(this[keys.realm], this[keys.id], name);
};
}
function setterForProperty(name) {
return function(value) {
rpc.setProperty(this[keys.realm], this[keys.id], name, value);
};
}

View File

@ -214,7 +214,7 @@ JSObjectRef RJSListCreate(JSContextRef ctx, realm::List &list) {
return RJSWrapObject<List *>(ctx, RJSListClass(), new List(list));
}
const JSStaticFunction RJSListFuncs[] = {
static const JSStaticFunction RJSListFuncs[] = {
{"push", ListPush, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},
{"pop", ListPop, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},
{"shift", ListShift, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},

View File

@ -20,9 +20,5 @@
#import "shared_realm.hpp"
#import "list.hpp"
extern const JSStaticFunction RJSListFuncs[];
JSClassRef RJSListClass();
JSObjectRef RJSListCreate(JSContextRef ctx, realm::List &list);
JSValueRef ListGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* jsException);
bool ListSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* jsException);

View File

@ -24,7 +24,3 @@ namespace realm {
JSClassRef RJSObjectClass();
JSObjectRef RJSObjectCreate(JSContextRef ctx, realm::Object object);
JSValueRef ObjectGetProperty(JSContextRef ctx, JSObjectRef jsObject, JSStringRef jsPropertyName, JSValueRef* exception);
bool ObjectSetProperty(JSContextRef ctx, JSObjectRef jsObject, JSStringRef jsPropertyName, JSValueRef value, JSValueRef* exception);

View File

@ -143,21 +143,8 @@ template<> JSValueRef RJSAccessor::from_string(JSContextRef ctx, StringData s) {
}
template<> DateTime RJSAccessor::to_datetime(JSContextRef ctx, JSValueRef &val) {
JSObjectRef object = RJSValidatedValueToObject(ctx, val, "Property must be a Date");
JSValueRef exception = NULL;
static JSStringRef utcString = JSStringCreateWithUTF8CString("getTime");
JSObjectRef utcGetter = RJSValidatedObjectProperty(ctx, object, utcString);
JSValueRef utcVal = JSObjectCallAsFunction(ctx, utcGetter, object, 0, NULL, &exception);
if (exception) {
throw RJSException(ctx, exception);
}
double utc = JSValueToNumber(ctx, utcVal, &exception);
if (exception) {
throw RJSException(ctx, exception);
}
JSObjectRef object = RJSValidatedValueToDate(ctx, val);
double utc = RJSValidatedValueToNumber(ctx, object);
return DateTime(utc);
}

View File

@ -24,8 +24,6 @@ namespace realm {
using ObjectDefaults = std::map<std::string, JSValueRef>;
}
extern const JSStaticFunction RJSRealmFuncs[];
JSClassRef RJSRealmClass();
JSClassRef RJSRealmConstructorClass();
JSClassRef RJSNotificationClass();

View File

@ -466,7 +466,7 @@ JSValueRef RealmClose(JSContextRef ctx, JSObjectRef function, JSObjectRef thisOb
return NULL;
}
const JSStaticFunction RJSRealmFuncs[] = {
static const JSStaticFunction RJSRealmFuncs[] = {
{"objects", RealmObjects, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},
{"create", RealmCreateObject, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},
{"delete", RealmDelete, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},

View File

@ -26,6 +26,3 @@ namespace realm {
JSClassRef RJSResultsClass();
JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className);
JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className, std::string query);
JSValueRef ResultsGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* jsException);

View File

@ -118,12 +118,12 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl
return RJSWrapObject<Results *>(ctx, RJSResultsClass(), new Results(realm, *object_schema, std::move(query)));
}
static const JSStaticFunction RJSResultsFuncs[] = {
{"sortByProperty", SortByProperty, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},
{NULL, NULL},
};
JSClassRef RJSResultsClass() {
const JSStaticFunction resultsFuncs[] = {
{"sortByProperty", SortByProperty, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},
{NULL, NULL},
};
static JSClassRef s_objectClass = RJSCreateWrapperClass<Results *>("Results", ResultsGetProperty, NULL, resultsFuncs, ResultsPropertyNames);
static JSClassRef s_objectClass = RJSCreateWrapperClass<Results *>("Results", ResultsGetProperty, NULL, RJSResultsFuncs, ResultsPropertyNames);
return s_objectClass;
}

View File

@ -99,6 +99,9 @@ JSValueRef RJSMakeError(JSContextRef ctx, RJSException &exp);
JSValueRef RJSMakeError(JSContextRef ctx, std::exception &exp);
JSValueRef RJSMakeError(JSContextRef ctx, const std::string &message);
bool RJSIsValueArray(JSContextRef ctx, JSValueRef value);
bool RJSIsValueDate(JSContextRef ctx, JSValueRef value);
static inline JSObjectRef RJSValidatedValueToObject(JSContextRef ctx, JSValueRef value, const char *message = NULL) {
JSObjectRef object = JSValueToObject(ctx, value, NULL);
if (!object) {
@ -107,6 +110,14 @@ static inline JSObjectRef RJSValidatedValueToObject(JSContextRef ctx, JSValueRef
return object;
}
static inline JSObjectRef RJSValidatedValueToDate(JSContextRef ctx, JSValueRef value, const char *message = NULL) {
JSObjectRef object = JSValueToObject(ctx, value, NULL);
if (!object || !RJSIsValueDate(ctx, object)) {
throw std::runtime_error(message ?: "Value is not a date.");
}
return object;
}
static inline JSObjectRef RJSValidatedValueToFunction(JSContextRef ctx, JSValueRef value, const char *message = NULL) {
JSObjectRef object = JSValueToObject(ctx, value, NULL);
if (!object || !JSObjectIsFunction(ctx, object)) {
@ -200,6 +211,3 @@ static inline bool RJSIsValueObjectOfType(JSContextRef ctx, JSValueRef value, JS
return ret;
}
bool RJSIsValueArray(JSContextRef ctx, JSValueRef value);

View File

@ -115,6 +115,10 @@ bool RJSIsValueArray(JSContextRef ctx, JSValueRef value) {
return RJSIsValueObjectOfType(ctx, value, arrayString);
}
bool RJSIsValueDate(JSContextRef ctx, JSValueRef value) {
static JSStringRef dateString = JSStringCreateWithUTF8CString("Date");
return RJSIsValueObjectOfType(ctx, value, dateString);
}
#include <realm.hpp>

View File

@ -19,6 +19,7 @@
#import "RealmRPC.h"
#import <JavaScriptCore/JavaScriptCore.h>
#include <dlfcn.h>
#include <map>
#include <string>
#include "RealmJS.h"
@ -35,6 +36,10 @@
using RPCObjectID = u_int64_t;
using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
static const char * const RealmObjectTypesFunction = "ObjectTypesFUNCTION";
static const char * const RealmObjectTypesNotification = "ObjectTypesNOTIFICATION";
static const char * const RealmObjectTypesResults = "ObjectTypesRESULTS";
@implementation RJSRPCServer {
JSGlobalContextRef _context;
std::map<std::string, RPCRequest> _requests;
@ -55,9 +60,24 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
if (self) {
_context = JSGlobalContextCreate(NULL);
// JavaScriptCore crashes when trying to walk up the native stack to print the stacktrace.
// FIXME: Avoid having to do this!
static void (*setIncludesNativeCallStack)(JSGlobalContextRef, bool) = (void (*)(JSGlobalContextRef, bool))dlsym(RTLD_DEFAULT, "JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions");
if (setIncludesNativeCallStack) {
setIncludesNativeCallStack(_context, false);
}
id _self = self;
__weak __typeof__(self) self = _self;
_requests["/get_default_path"] = [=](NSDictionary *dict) {
return @{@"result": @(RJSDefaultPath().c_str())};
};
_requests["/set_default_path"] = [=](NSDictionary *dict) {
NSString *path = dict[@"path"];
RJSSetDefaultPath(path.UTF8String);
return nil;
};
_requests["/create_realm"] = [=](NSDictionary *dict) {
NSArray *args = dict[@"arguments"];
NSUInteger argCount = args.count;
@ -92,36 +112,50 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
RJSGetInternal<realm::SharedRealm *>(_objects[realmId])->get()->commit_transaction();
return nil;
};
_requests["/call_realm_method"] = [=](NSDictionary *dict) {
NSString *name = dict[@"name"];
return [self performObjectMethod:name.UTF8String
classMethods:RJSRealmFuncs
_requests["/call_method"] = [=](NSDictionary *dict) {
return [self performObjectMethod:dict[@"name"]
args:dict[@"arguments"]
objectId:[dict[@"realmId"] unsignedLongValue]];
objectId:[dict[@"id"] unsignedLongValue]];
};
_requests["/get_property"] = [=](NSDictionary *dict) {
RPCObjectID oid = [dict[@"id"] unsignedLongValue];
id name = dict[@"name"];
JSValueRef value = NULL;
JSValueRef exception = NULL;
NSString *name = dict[@"name"];
JSStringRef propString = RJSStringForString(name.UTF8String);
RPCObjectID objectId = [dict[@"objectId"] unsignedLongValue];
JSValueRef propertyValue = ObjectGetProperty(_context, _objects[objectId], propString, &exception);
JSStringRelease(propString);
if ([name isKindOfClass:[NSNumber class]]) {
value = JSObjectGetPropertyAtIndex(_context, _objects[oid], [name unsignedIntValue], &exception);
}
else {
JSStringRef propString = RJSStringForString([name UTF8String]);
value = JSObjectGetProperty(_context, _objects[oid], propString, &exception);
JSStringRelease(propString);
}
if (exception) {
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
}
return @{@"result": [self resultForJSValue:propertyValue]};
return @{@"result": [self resultForJSValue:value]};
};
_requests["/set_property"] = [=](NSDictionary *dict) {
JSStringRef propString = RJSStringForString([dict[@"name"] UTF8String]);
RPCObjectID realmId = [dict[@"objectId"] unsignedLongValue];
RPCObjectID oid = [dict[@"id"] unsignedLongValue];
id name = dict[@"name"];
JSValueRef value = [self deserializeDictionaryValue:dict[@"value"]];
JSValueRef exception = NULL;
ObjectSetProperty(_context, _objects[realmId], propString, value, &exception);
JSStringRelease(propString);
if ([name isKindOfClass:[NSNumber class]]) {
JSObjectSetPropertyAtIndex(_context, _objects[oid], [name unsignedIntValue], value, &exception);
}
else {
JSStringRef propString = RJSStringForString([name UTF8String]);
JSObjectSetProperty(_context, _objects[oid], propString, value, 0, &exception);
JSStringRelease(propString);
}
return exception ? @{@"error": @(RJSStringForValue(_context, exception).c_str())} : @{};
if (exception) {
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
}
return @{};
};
_requests["/dispose_object"] = [=](NSDictionary *dict) {
RPCObjectID oid = [dict[@"id"] unsignedLongValue];
@ -129,84 +163,6 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
_objects.erase(oid);
return nil;
};
_requests["/get_results_size"] = [=](NSDictionary *dict) {
RPCObjectID resultsId = [dict[@"resultsId"] unsignedLongValue];
JSValueRef exception = NULL;
static JSStringRef lengthPropertyName = JSStringCreateWithUTF8CString("length");
JSValueRef lengthValue = ResultsGetProperty(_context, _objects[resultsId], lengthPropertyName, &exception);
size_t length = JSValueToNumber(_context, lengthValue, &exception);
if (exception) {
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
}
return @{@"result": @(length)};
};
_requests["/get_results_item"] = [=](NSDictionary *dict) {
RPCObjectID resultsId = [dict[@"resultsId"] unsignedLongValue];
long index = [dict[@"index"] longValue];
JSValueRef exception = NULL;
JSStringRef indexPropertyName = JSStringCreateWithUTF8CString(std::to_string(index).c_str());
JSValueRef objectValue = ResultsGetProperty(_context, _objects[resultsId], indexPropertyName, &exception);
JSStringRelease(indexPropertyName);
if (exception) {
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
}
return @{@"result": [self resultForJSValue:objectValue]};
};
_requests["/get_list_size"] = [=](NSDictionary *dict) {
RPCObjectID listId = [dict[@"listId"] unsignedLongValue];
JSValueRef exception = NULL;
static JSStringRef lengthPropertyName = JSStringCreateWithUTF8CString("length");
JSValueRef lengthValue = ListGetProperty(_context, _objects[listId], lengthPropertyName, &exception);
size_t length = JSValueToNumber(_context, lengthValue, &exception);
if (exception) {
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
}
return @{@"result": @(length)};
};
_requests["/get_list_item"] = [=](NSDictionary *dict) {
RPCObjectID listId = [dict[@"listId"] unsignedLongValue];
long index = [dict[@"index"] longValue];
JSValueRef exception = NULL;
JSStringRef indexPropertyName = JSStringCreateWithUTF8CString(std::to_string(index).c_str());
JSValueRef objectValue = ListGetProperty(_context, _objects[listId], indexPropertyName, &exception);
JSStringRelease(indexPropertyName);
if (exception) {
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
}
return @{@"result": [self resultForJSValue:objectValue]};
};
_requests["/set_list_item"] = [=](NSDictionary *dict) {
RPCObjectID listId = [dict[@"listId"] unsignedLongValue];
long index = [dict[@"index"] longValue];
JSValueRef exception = NULL;
JSStringRef indexPropertyName = JSStringCreateWithUTF8CString(std::to_string(index).c_str());
JSValueRef value = [self deserializeDictionaryValue:dict[@"value"]];
ListSetProperty(_context, _objects[listId], indexPropertyName, value, &exception);
JSStringRelease(indexPropertyName);
if (exception) {
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
}
return @{};
};
_requests["/call_list_method"] = [=](NSDictionary *dict) {
NSString *name = dict[@"name"];
return [self performObjectMethod:name.UTF8String
classMethods:RJSListFuncs
args:dict[@"arguments"]
objectId:[dict[@"listId"] unsignedLongValue]];
};
_requests["/clear_test_state"] = [=](NSDictionary *dict) {
for (auto object : _objects) {
JSValueUnprotect(_context, object.second);
@ -236,30 +192,25 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
return response ?: @{};
}
- (NSDictionary *)performObjectMethod:(const char *)name
classMethods:(const JSStaticFunction [])methods
args:(NSArray *)args
objectId:(RPCObjectID)oid {
- (NSDictionary *)performObjectMethod:(NSString *)method args:(NSArray *)args objectId:(RPCObjectID)oid {
JSObjectRef object = _objects[oid];
JSStringRef methodString = RJSStringForString(method.UTF8String);
JSObjectRef function = RJSValidatedObjectProperty(_context, object, methodString);
JSStringRelease(methodString);
NSUInteger argCount = args.count;
JSValueRef argValues[argCount];
for (NSUInteger i = 0; i < argCount; i++) {
argValues[i] = [self deserializeDictionaryValue:args[i]];
}
size_t index = 0;
while (methods[index].name) {
if (!strcmp(methods[index].name, name)) {
JSValueRef exception = NULL;
JSValueRef ret = methods[index].callAsFunction(_context, NULL, _objects[oid], argCount, argValues, &exception);
if (exception) {
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
}
return @{@"result": [self resultForJSValue:ret]};
}
index++;
}
JSValueRef exception = NULL;
JSValueRef result = JSObjectCallAsFunction(_context, function, object, argCount, argValues, &exception);
return @{@"error": @"invalid method"};
if (exception) {
return @{@"error": @(RJSStringForValue(_context, exception).c_str())};
}
return @{@"result": [self resultForJSValue:result]};
}
- (RPCObjectID)storeObject:(JSObjectRef)object {
@ -311,12 +262,19 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
realm::Results *results = RJSGetInternal<realm::Results *>(jsObject);
RPCObjectID oid = [self storeObject:jsObject];
return @{
@"type": @"ObjectTypesRESULTS",
@"type": @(RealmObjectTypesResults),
@"id": @(oid),
@"size": @(results->size()),
@"schema": [self objectSchemaToJSONObject:results->object_schema]
};
}
else if (JSValueIsObjectOfClass(_context, value, RJSNotificationClass())) {
RPCObjectID oid = [self storeObject:jsObject];
return @{
@"type": @(RealmObjectTypesNotification),
@"id": @(oid),
};
}
else if (RJSIsValueArray(_context, value)) {
size_t length = RJSValidatedListLength(_context, jsObject);
NSMutableArray *array = [NSMutableArray new];
@ -325,6 +283,12 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
}
return @{@"value": array};
}
else if (RJSIsValueDate(_context, value)) {
return @{
@"type": @(RJSTypeGet(realm::PropertyTypeDate).c_str()),
@"value": @(RJSValidatedValueToNumber(_context, value)),
};
}
else {
assert(0);
}
@ -354,7 +318,28 @@ using RPCRequest = std::function<NSDictionary *(NSDictionary *dictionary)>;
return _objects[oid];
}
NSString *type = dict[@"type"];
id value = dict[@"value"];
if ([type isEqualToString:@(RealmObjectTypesFunction)]) {
// FIXME: Make this actually call the function by its id once we need it to.
JSStringRef jsBody = JSStringCreateWithUTF8CString("");
JSObjectRef jsFunction = JSObjectMakeFunction(_context, NULL, 0, NULL, jsBody, NULL, 1, NULL);
JSStringRelease(jsBody);
return jsFunction;
}
else if ([type isEqualToString:@(RJSTypeGet(realm::PropertyTypeDate).c_str())]) {
JSValueRef exception = NULL;
JSValueRef time = JSValueMakeNumber(_context, [value doubleValue]);
JSObjectRef date = JSObjectMakeDate(_context, 1, &time, &exception);
if (exception) {
throw RJSException(_context, exception);
}
return date;
}
if (!value) {
return JSValueMakeUndefined(_context);
}