diff --git a/src/ios/RealmJS.xcodeproj/project.pbxproj b/src/ios/RealmJS.xcodeproj/project.pbxproj index 3ff11c4f..3dd31948 100644 --- a/src/ios/RealmJS.xcodeproj/project.pbxproj +++ b/src/ios/RealmJS.xcodeproj/project.pbxproj @@ -125,6 +125,7 @@ 02A3C7A41BC4341500B1A7BE /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; 02AFE5871CA9B23400953DA3 /* jsc_list.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = jsc_list.cpp; path = jsc/jsc_list.cpp; sourceTree = ""; }; 02AFE5881CA9B23400953DA3 /* jsc_list.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = jsc_list.hpp; path = jsc/jsc_list.hpp; sourceTree = ""; }; + 02AFE5B11CAB133500953DA3 /* js_compat.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = js_compat.hpp; path = jsc/js_compat.hpp; sourceTree = ""; }; 02B58CB11AE99CEC009B348C /* RealmJS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RealmJS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 02B58CBC1AE99CEC009B348C /* RealmJSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RealmJSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; @@ -259,6 +260,7 @@ 0290480C1C0428DF00ABDED4 /* js_schema.hpp */, 0290480D1C0428DF00ABDED4 /* js_util.cpp */, 0290480E1C0428DF00ABDED4 /* js_util.hpp */, + 02AFE5B11CAB133500953DA3 /* js_compat.hpp */, 02AFE5871CA9B23400953DA3 /* jsc_list.cpp */, 02AFE5881CA9B23400953DA3 /* jsc_list.hpp */, 029048351C042A3C00ABDED4 /* platform.hpp */, diff --git a/src/js_compat.hpp b/src/js_compat.hpp deleted file mode 100644 index f13e177c..00000000 --- a/src/js_compat.hpp +++ /dev/null @@ -1,68 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include "types.hpp" -#include - -namespace realm { -namespace js { - -void *GetInternal(Types::ObjectType jsObject) { - return JSObjectGetPrivate(jsObject); -} - - -std::string StringForStringType(Types::StringType jsString); -std::string StringForValueType(Types::ContextType ctx, Types::ValueType value); -std::string ValidatedStringForValueType(Types::ContextType ctx, Types::ValueType value, const char * name = nullptr); -bool ValidatedBooleanForValueType(Types::ContextType ctx, Types::ValueType value, const char * name = nullptr); - -Types::StringType StringTypeForString(const std::string &str); -Types::ValueType ValueTypeForString(Types::ContextType ctx, const std::string &str); - -bool IsValueTypeArray(Types::ContextType ctx, Types::ValueType value); -bool IsValueTypeArrayBuffer(Types::ContextType ctx, Types::ValueType value); -bool IsValueTypeDate(Types::ContextType ctx, Types::ValueType value); - -Types::ObjectType ValidatedValueTypeToObject(Types::ContextType ctx, Types::ValueType value, const char *message = NULL); -Types::ObjectType ValidatedValueTypeToDate(Types::ContextType ctx, Types::ValueType value, const char *message = NULL); -Types::ObjectType ValidatedValueTypeToFunction(Types::ContextType ctx, Types::ValueType value, const char *message = NULL); -double ValidatedValueTypeToNumber(Types::ContextType ctx, Types::ValueType value); -Types::ValueType ValidatedPropertyValue(Types::ContextType ctx, Types::ObjectType object, Types::StringType property); -Types::ValueType ValidatedPropertyAtIndex(Types::ContextType ctx, Types::ObjectType object, unsigned int index); -Types::ObjectType ValidatedObjectProperty(Types::ContextType ctx, Types::ObjectType object, Types::StringType property, const char *err = NULL); -Types::ObjectType ValidatedObjectAtIndex(Types::ContextType ctx, Types::ObjectType object, unsigned int index); -std::string ValidatedStringProperty(Types::ContextType ctx, Types::ObjectType object, Types::StringType property); -bool ValidatedBooleanProperty(Types::ContextType ctx, Types::ObjectType object, Types::StringType property, const char *err = NULL); -size_t ValidatedListLength(Types::ContextType ctx, Types::ObjectType object); -void ValidatedSetProperty(Types::ContextType ctx, Types::ObjectType object, Types::StringType propertyName, Types::ValueType value, JSPropertyAttributes attributes = 0); - -bool IsValueTypeIsObject(Types::ContextType ctx, Types::ValueType value); -bool IsValueTypeObjectOfType(Types::ContextType ctx, Types::ValueType value, Types::StringType type); -bool ObjectTypeHasProperty(Types::ContextType ctx, Types::ObjectType object, Types::StringType propName); - -template -void SetReturnNumber(Types::ContextType ctx, Types::ValueType &returnObject, T number); -void SetReturnArray(Types::ContextType ctx, size_t count, const Types::ValueType *objects, Types::ValueType &returnObject);\ -void SetReturnUndefined(Types::ContextType ctx, Types::ValueType &returnObject); - -void SetException(Types::ContextType ctx, Types::ValueType * &exceptionObject, std::exception &exception); - -}} \ No newline at end of file diff --git a/src/js_list.hpp b/src/js_list.hpp index a952a623..660bc981 100644 --- a/src/js_list.hpp +++ b/src/js_list.hpp @@ -35,8 +35,8 @@ using RJSAccessor = realm::NativeAccessor; using namespace realm; -template -void ListPush(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { +template +void ListPush(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { List *list = RJSGetInternal(thisObject); RJSValidateArgumentCountIsAtLeast(argumentCount, 1); @@ -50,8 +50,8 @@ void ListPush(ContextType ctx, ThisType thisObject, size_t argumentCount, const } } -template -void ListPop(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { +template +void ListPop(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { List *list = RJSGetInternal(thisObject); RJSValidateArgumentCount(argumentCount, 0); @@ -73,8 +73,8 @@ void ListPop(ContextType ctx, ThisType thisObject, size_t argumentCount, const A } -template -void ListUnshift(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { +template +void ListUnshift(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { List *list = RJSGetInternal(thisObject); RJSValidateArgumentCountIsAtLeast(argumentCount, 1); @@ -88,8 +88,8 @@ void ListUnshift(ContextType ctx, ThisType thisObject, size_t argumentCount, con } } -template -void ListShift(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { +template +void ListShift(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { List *list = RJSGetInternal(thisObject); RJSValidateArgumentCount(argumentCount, 0); @@ -107,8 +107,8 @@ void ListShift(ContextType ctx, ThisType thisObject, size_t argumentCount, const } } -template -void ListSplice(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { +template +void ListSplice(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { List *list = RJSGetInternal(thisObject); size_t size = list->size(); @@ -144,8 +144,8 @@ void ListSplice(ContextType ctx, ThisType thisObject, size_t argumentCount, cons } -template -void ListStaticResults(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { +template +void ListStaticResults(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { List *list = RJSGetInternal(thisObject); RJSValidateArgumentCount(argumentCount, 0); @@ -156,8 +156,8 @@ void ListStaticResults(ContextType ctx, ThisType thisObject, size_t argumentCoun } } -template -void ListFiltered(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { +template +void ListFiltered(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { List *list = RJSGetInternal(thisObject); RJSValidateArgumentCountIsAtLeast(argumentCount, 1); @@ -170,8 +170,8 @@ void ListFiltered(ContextType ctx, ThisType thisObject, size_t argumentCount, co } } -template -void ListSorted(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { +template +void ListSorted(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { List *list = RJSGetInternal(thisObject); RJSValidateArgumentRange(argumentCount, 1, 2); diff --git a/src/js_realm.cpp b/src/js_realm.cpp index e2a2068d..a6689ef9 100644 --- a/src/js_realm.cpp +++ b/src/js_realm.cpp @@ -238,34 +238,34 @@ static const JSStaticValue RealmStaticProperties[] = { {NULL, NULL} }; -JSValueRef RealmSchemaVersion(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { +template +void RealmSchemaVersion(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentRange(argumentCount, 1, 2); Realm::Config config; config.path = RJSNormalizePath(RJSValidatedStringForValue(ctx, arguments[0])); if (argumentCount == 2) { - JSValueRef encryptionKeyValue = arguments[1]; + auto encryptionKeyValue = arguments[1]; std::string encryptionKey = RJSAccessor::to_binary(ctx, encryptionKeyValue); config.encryption_key = std::vector(encryptionKey.begin(), encryptionKey.end()); } auto version = Realm::get_schema_version(config); if (version == ObjectStore::NotVersioned) { - return JSValueMakeNumber(ctx, -1); + RJSSetReturnNumber(ctx, returnObject, -1); } else { - return JSValueMakeNumber(ctx, version); + RJSSetReturnNumber(ctx, returnObject, version); } } catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } + RJSSetException(ctx, exceptionObject, exp); } - return NULL; } +WRAP_METHOD(RealmSchemaVersion) + static const JSStaticFunction RealmConstructorFuncs[] = { {"schemaVersion", RealmSchemaVersion, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {NULL, NULL}, @@ -311,8 +311,8 @@ std::string RJSValidatedObjectTypeForValue(SharedRealm &realm, JSContextRef ctx, return RJSValidatedStringForValue(ctx, value, "objectType"); } -template -void RealmObjects(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { +template +void RealmObjects(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentCount(argumentCount, 1); SharedRealm sharedRealm = *RJSGetInternal(thisObject); @@ -324,8 +324,9 @@ void RealmObjects(ContextType ctx, ThisType thisObject, size_t argumentCount, co } } -template -void RealmCreateObject(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { +template +void RealmCreateObject(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { + try { RJSValidateArgumentRange(argumentCount, 2, 3); SharedRealm sharedRealm = *RJSGetInternal(thisObject); @@ -337,7 +338,7 @@ void RealmCreateObject(ContextType ctx, ThisType thisObject, size_t argumentCoun throw std::runtime_error("Object type '" + className + "' not found in schema."); } - JSObjectRef object = RJSValidatedValueToObject(ctx, arguments[1]); + auto object = RJSValidatedValueToObject(ctx, arguments[1]); if (RJSIsValueArray(ctx, arguments[1])) { object = RJSDictForPropertyArray(ctx, *object_schema, object); } @@ -347,15 +348,15 @@ void RealmCreateObject(ContextType ctx, ThisType thisObject, size_t argumentCoun update = RJSValidatedValueToBoolean(ctx, arguments[2]); } - returnObject = RJSObjectCreate(ctx, Object::create(ctx, sharedRealm, *object_schema, object, update)); + returnObject = RJSObjectCreate(ctx, Object::create(ctx, sharedRealm, *object_schema, object, update)); } catch (std::exception &exp) { RJSSetException(ctx, exceptionObject, exp); } } -template -void RealmDelete(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { +template +void RealmDelete(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentCount(argumentCount, 1); SharedRealm realm = *RJSGetInternal(thisObject); @@ -363,7 +364,7 @@ void RealmDelete(ContextType ctx, ThisType thisObject, size_t argumentCount, con throw std::runtime_error("Can only delete objects within a transaction."); } - JSObjectRef arg = RJSValidatedValueToObject(ctx, arguments[0]); + auto arg = RJSValidatedValueToObject(ctx, arguments[0]); if (RJSValueIsObjectOfClass(ctx, arg, RJSObjectClass())) { Object *object = RJSGetInternal(arg); realm::TableRef table = ObjectStore::table_for_object_type(realm->read_group(), object->get_object_schema().name); @@ -373,7 +374,7 @@ void RealmDelete(ContextType ctx, ThisType thisObject, size_t argumentCount, con size_t length = RJSValidatedListLength(ctx, arg); for (long i = length-1; i >= 0; i--) { JSObjectRef jsObject = RJSValidatedValueToObject(ctx, RJSValidatedObjectAtIndex(ctx, arg, (unsigned int)i)); - if (!JSValueIsObjectOfClass(ctx, jsObject, RJSObjectClass())) { + if (!RJSValueIsObjectOfClass(ctx, jsObject, RJSObjectClass())) { throw std::runtime_error("Argument to 'delete' must be a Realm object or a collection of Realm objects."); } @@ -399,8 +400,8 @@ void RealmDelete(ContextType ctx, ThisType thisObject, size_t argumentCount, con } } -template -void RealmDeleteAll(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { +template +void RealmDeleteAll(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentCount(argumentCount, 0); SharedRealm realm = *RJSGetInternal(thisObject); @@ -418,13 +419,13 @@ void RealmDeleteAll(ContextType ctx, ThisType thisObject, size_t argumentCount, } } -template -void RealmWrite(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { +template +void RealmWrite(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { SharedRealm realm = *RJSGetInternal(thisObject); try { RJSValidateArgumentCount(argumentCount, 1); - JSObjectRef object = RJSValidatedValueToFunction(ctx, arguments[0]); + auto object = RJSValidatedValueToFunction(ctx, arguments[0]); realm->begin_transaction(); RJSCallFunction(ctx, object, thisObject, 0, NULL); realm->commit_transaction(); @@ -445,11 +446,11 @@ std::string RJSValidatedNotificationName(JSContextRef ctx, JSValueRef value) { return name; } -template -void RealmAddListener(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { +template +void RealmAddListener(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentCount(argumentCount, 2); __unused std::string name = RJSValidatedNotificationName(ctx, arguments[0]); - JSObjectRef callback = RJSValidatedValueToFunction(ctx, arguments[1]); + auto callback = RJSValidatedValueToFunction(ctx, arguments[1]); SharedRealm realm = *RJSGetInternal(thisObject); static_cast(realm->m_binding_context.get())->add_notification(callback); @@ -459,11 +460,11 @@ void RealmAddListener(ContextType ctx, ThisType thisObject, size_t argumentCount } } -template -void RealmRemoveListener(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { +template +void RealmRemoveListener(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentCount(argumentCount, 2); __unused std::string name = RJSValidatedNotificationName(ctx, arguments[0]); - JSObjectRef callback = RJSValidatedValueToFunction(ctx, arguments[1]); + auto callback = RJSValidatedValueToFunction(ctx, arguments[1]); SharedRealm realm = *RJSGetInternal(thisObject); static_cast(realm->m_binding_context.get())->remove_notification(callback); @@ -473,8 +474,8 @@ void RealmRemoveListener(ContextType ctx, ThisType thisObject, size_t argumentCo } } -template -void RealmRemoveAllListeners(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { +template +void RealmRemoveAllListeners(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentRange(argumentCount, 0, 1); if (argumentCount) { RJSValidatedNotificationName(ctx, arguments[0]); @@ -488,8 +489,8 @@ void RealmRemoveAllListeners(ContextType ctx, ThisType thisObject, size_t argume } } -template -void RealmClose(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { +template +void RealmClose(ContextType ctx, ObjectType thisObject, size_t argumentCount, const ValueType arguments[], ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentCount(argumentCount, 0); SharedRealm realm = *RJSGetInternal(thisObject); realm->close(); diff --git a/src/js_util.hpp b/src/js_util.hpp index c55cba4b..c56e3fc3 100644 --- a/src/js_util.hpp +++ b/src/js_util.hpp @@ -29,6 +29,8 @@ #include #include #include "property.hpp" + +#include "js_compat.hpp" #include "schema.hpp" #define WRAP_METHOD(METHOD_NAME) \ @@ -38,7 +40,6 @@ JSValueRef METHOD_NAME(JSContextRef ctx, JSObjectRef function, JSObjectRef thisO return returnObject; \ } - template inline void RJSFinalize(JSObjectRef object) { delete static_cast(JSObjectGetPrivate(object)); @@ -314,3 +315,4 @@ static void RJSCallFunction(JSContextRef ctx, JSObjectRef function, JSObjectRef static bool RJSValueIsObjectOfClass(JSContextRef ctx, JSValueRef value, JSClassRef jsClass) { return JSValueIsObjectOfClass(ctx, value, jsClass); } + diff --git a/src/jsc/js_compat.hpp b/src/jsc/js_compat.hpp new file mode 100644 index 00000000..9218484a --- /dev/null +++ b/src/jsc/js_compat.hpp @@ -0,0 +1,34 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "types.hpp" +#include + +namespace realm { +namespace js { + +static bool ValueIsUndefined(Types::Context ctx, Types::Value value) { return JSValueIsUndefined(ctx, value); } +static bool ValueIsNull(Types::Context ctx, Types::Value value) { return JSValueIsNull(ctx, value); } +static bool ValueIsBoolean(Types::Context ctx, Types::Value value) { return JSValueIsBoolean(ctx, value); } +static bool ValueIsNumber(Types::Context ctx, Types::Value value) { return JSValueIsNumber(ctx, value); } +static bool ValueIsString(Types::Context ctx, Types::Value value) { return JSValueIsString(ctx, value); } +static bool ValueIsObject(Types::Context ctx, Types::Value value) { return JSValueIsObject(ctx, value); } + +}} \ No newline at end of file diff --git a/src/jsc/js_realm.cpp b/src/jsc/js_realm.cpp new file mode 100644 index 00000000..5929c435 --- /dev/null +++ b/src/jsc/js_realm.cpp @@ -0,0 +1,572 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "js_realm.hpp" +#include "js_object.hpp" +#include "js_results.hpp" +#include "jsc_list.hpp" +#include "js_schema.hpp" +#include "platform.hpp" + +#include "shared_realm.hpp" +#include "impl/realm_coordinator.hpp" +#include "object_accessor.hpp" +#include "binding_context.hpp" + +#include +#include + +using namespace realm; +using RJSAccessor = realm::NativeAccessor; + +class RJSRealmDelegate : public BindingContext { +public: + virtual void changes_available() { + assert(0); + } + virtual void did_change(std::vector const& observers, std::vector const& invalidated) { + notify("change"); + } + virtual std::vector get_observed_rows() { + return std::vector(); + } + virtual void will_change(std::vector const& observers, + std::vector const& invalidated) {} + + RJSRealmDelegate(WeakRealm realm, JSGlobalContextRef ctx) : m_context(ctx), m_realm(realm) { + JSGlobalContextRetain(m_context); + } + + ~RJSRealmDelegate() { + remove_all_notifications(); + + for (auto constructor : m_constructors) { + JSValueUnprotect(m_context, constructor.second); + } + for (auto objectDefaults : m_defaults) { + for (auto value : objectDefaults.second) { + JSValueUnprotect(m_context, value.second); + } + } + JSGlobalContextRelease(m_context); + } + + void add_notification(JSObjectRef notification) { + if (!m_notifications.count(notification)) { + JSValueProtect(m_context, notification); + m_notifications.insert(notification); + } + } + void remove_notification(JSObjectRef notification) { + if (m_notifications.count(notification)) { + JSValueUnprotect(m_context, notification); + m_notifications.erase(notification); + } + } + void remove_all_notifications() { + for (auto notification : m_notifications) { + JSValueUnprotect(m_context, notification); + } + m_notifications.clear(); + } + + std::map m_defaults; + std::map m_constructors; + + private: + std::set m_notifications; + JSGlobalContextRef m_context; + WeakRealm m_realm; + + void notify(const char *notification_name) { + JSValueRef arguments[2]; + SharedRealm realm = m_realm.lock(); + if (!realm) { + throw std::runtime_error("Realm no longer exists"); + } + JSObjectRef realm_object = RJSWrapObject(m_context, RJSRealmClass(), new SharedRealm(realm)); + arguments[0] = realm_object; + arguments[1] = RJSValueForString(m_context, notification_name); + + for (auto callback : m_notifications) { + JSValueRef ex = NULL; + JSObjectCallAsFunction(m_context, callback, realm_object, 2, arguments, &ex); + if (ex) { + throw RJSException(m_context, ex); + } + } + } +}; + +std::map &RJSDefaults(Realm *realm) { + return static_cast(realm->m_binding_context.get())->m_defaults; +} + +std::map &RJSConstructors(Realm *realm) { + return static_cast(realm->m_binding_context.get())->m_constructors; +} + +// static std::string s_defaultPath = realm::default_realm_file_directory() + "/default.realm"; +static std::string s_defaultPath = ""; +std::string RJSDefaultPath() { + if (s_defaultPath.size() == 0) { + s_defaultPath = realm::default_realm_file_directory() + "/default.realm"; + } + return s_defaultPath; +} +void RJSSetDefaultPath(std::string path) { + s_defaultPath = path; +} + +static JSValueRef GetDefaultPath(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* jsException) { + return RJSValueForString(ctx, s_defaultPath); +} + +static bool SetDefaultPath(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* jsException) { + try { + s_defaultPath = RJSValidatedStringForValue(ctx, value, "defaultPath"); + } + catch (std::exception &ex) { + if (jsException) { + *jsException = RJSMakeError(ctx, ex); + } + } + return true; +} + +inline std::string RJSNormalizePath(std::string path) { + if (path.size() && path[0] != '/') { + return default_realm_file_directory() + "/" + path; + } + return path; +} + +JSObjectRef RealmConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { + try { + Realm::Config config; + std::map defaults; + std::map constructors; + if (argumentCount == 0) { + config.path = RJSDefaultPath(); + } + else if (argumentCount == 1) { + JSValueRef value = arguments[0]; + if (JSValueIsString(ctx, value)) { + config.path = RJSValidatedStringForValue(ctx, value, "path"); + } + else if (JSValueIsObject(ctx, value)) { + JSObjectRef object = RJSValidatedValueToObject(ctx, value); + + static JSStringRef pathString = JSStringCreateWithUTF8CString("path"); + JSValueRef pathValue = RJSValidatedPropertyValue(ctx, object, pathString); + if (!JSValueIsUndefined(ctx, pathValue)) { + config.path = RJSValidatedStringForValue(ctx, pathValue, "path"); + } + else { + config.path = RJSDefaultPath(); + } + + static JSStringRef schemaString = JSStringCreateWithUTF8CString("schema"); + JSValueRef schemaValue = RJSValidatedPropertyValue(ctx, object, schemaString); + if (!JSValueIsUndefined(ctx, schemaValue)) { + config.schema.reset(new Schema(RJSParseSchema(ctx, RJSValidatedValueToObject(ctx, schemaValue), defaults, constructors))); + } + + static JSStringRef schemaVersionString = JSStringCreateWithUTF8CString("schemaVersion"); + JSValueRef versionValue = RJSValidatedPropertyValue(ctx, object, schemaVersionString); + if (JSValueIsNumber(ctx, versionValue)) { + config.schema_version = RJSValidatedValueToNumber(ctx, versionValue); + } + else { + config.schema_version = 0; + } + + static JSStringRef encryptionKeyString = JSStringCreateWithUTF8CString("encryptionKey"); + JSValueRef encryptionKeyValue = RJSValidatedPropertyValue(ctx, object, encryptionKeyString); + if (!JSValueIsUndefined(ctx, encryptionKeyValue)) { + std::string encryptionKey = RJSAccessor::to_binary(ctx, encryptionKeyValue); + config.encryption_key = std::vector(encryptionKey.begin(), encryptionKey.end());; + } + } + } + else { + *jsException = RJSMakeError(ctx, "Invalid arguments when constructing 'Realm'"); + return NULL; + } + + config.path = RJSNormalizePath(config.path); + + ensure_directory_exists_for_file(config.path); + SharedRealm realm = Realm::get_shared_realm(config); + if (!realm->m_binding_context) { + realm->m_binding_context.reset(new RJSRealmDelegate(realm, JSContextGetGlobalContext(ctx))); + } + RJSDefaults(realm.get()) = defaults; + RJSConstructors(realm.get()) = constructors; + return RJSWrapObject(ctx, RJSRealmClass(), new SharedRealm(realm)); + } + catch (std::exception &ex) { + if (jsException) { + *jsException = RJSMakeError(ctx, ex); + } + return NULL; + } +} + +bool RealmHasInstance(JSContextRef ctx, JSObjectRef constructor, JSValueRef value, JSValueRef* exception) { + return JSValueIsObjectOfClass(ctx, value, RJSRealmClass()); +} + +static const JSStaticValue RealmStaticProperties[] = { + {"defaultPath", GetDefaultPath, SetDefaultPath, kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {NULL, NULL} +}; + +JSValueRef RealmSchemaVersion(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { + try { + RJSValidateArgumentRange(argumentCount, 1, 2); + + Realm::Config config; + config.path = RJSNormalizePath(RJSValidatedStringForValue(ctx, arguments[0])); + if (argumentCount == 2) { + JSValueRef encryptionKeyValue = arguments[1]; + std::string encryptionKey = RJSAccessor::to_binary(ctx, encryptionKeyValue); + config.encryption_key = std::vector(encryptionKey.begin(), encryptionKey.end()); + } + + auto version = Realm::get_schema_version(config); + if (version == ObjectStore::NotVersioned) { + return JSValueMakeNumber(ctx, -1); + } + else { + return JSValueMakeNumber(ctx, version); + } + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + } + return NULL; +} + +static const JSStaticFunction RealmConstructorFuncs[] = { + {"schemaVersion", RealmSchemaVersion, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {NULL, NULL}, +}; + +JSClassRef RJSRealmConstructorClass() { + JSClassDefinition realmConstructorDefinition = kJSClassDefinitionEmpty; + realmConstructorDefinition.attributes = kJSClassAttributeNoAutomaticPrototype; + realmConstructorDefinition.className = "RealmConstructor"; + realmConstructorDefinition.callAsConstructor = RealmConstructor; + realmConstructorDefinition.hasInstance = RealmHasInstance; + realmConstructorDefinition.staticValues = RealmStaticProperties; + realmConstructorDefinition.staticFunctions = RealmConstructorFuncs; + return JSClassCreate(&realmConstructorDefinition); +} + +JSValueRef RealmGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) { + static JSStringRef s_pathString = JSStringCreateWithUTF8CString("path"); + if (JSStringIsEqual(propertyName, s_pathString)) { + return RJSValueForString(ctx, RJSGetInternal(object)->get()->config().path); + } + + static JSStringRef s_schemaVersion = JSStringCreateWithUTF8CString("schemaVersion"); + if (JSStringIsEqual(propertyName, s_schemaVersion)) { + return JSValueMakeNumber(ctx, RJSGetInternal(object)->get()->config().schema_version); + } + return NULL; +} + +std::string RJSValidatedObjectTypeForValue(SharedRealm &realm, JSContextRef ctx, JSValueRef value) { + if (JSValueIsObject(ctx, value) && JSObjectIsConstructor(ctx, (JSObjectRef)value)) { + JSObjectRef constructor = (JSObjectRef)value; + + for (auto pair : RJSConstructors(realm.get())) { + if (pair.second == constructor) { + return pair.first; + } + } + + throw std::runtime_error("Constructor was not registered in the schema for this Realm"); + } + + return RJSValidatedStringForValue(ctx, value, "objectType"); +} + +JSValueRef RealmObjects(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { + try { + RJSValidateArgumentCount(argumentCount, 1); + + SharedRealm sharedRealm = *RJSGetInternal(thisObject); + std::string className = RJSValidatedObjectTypeForValue(sharedRealm, ctx, arguments[0]); + return RJSResultsCreate(ctx, sharedRealm, className); + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + return NULL; + } +} + +JSObjectRef RJSDictForPropertyArray(JSContextRef ctx, const ObjectSchema &object_schema, JSObjectRef array) { + // copy to dictionary + if (object_schema.properties.size() != RJSValidatedListLength(ctx, array)) { + throw std::runtime_error("Array must contain values for all object properties"); + } + + JSValueRef exception = NULL; + JSObjectRef dict = JSObjectMake(ctx, NULL, NULL); + for (unsigned int i = 0; i < object_schema.properties.size(); i++) { + JSStringRef nameStr = JSStringCreateWithUTF8CString(object_schema.properties[i].name.c_str()); + JSValueRef value = JSObjectGetPropertyAtIndex(ctx, array, i, &exception); + if (exception) { + throw RJSException(ctx, exception); + } + JSObjectSetProperty(ctx, dict, nameStr, value, kJSPropertyAttributeNone, &exception); + if (exception) { + throw RJSException(ctx, exception); + } + JSStringRelease(nameStr); + } + return dict; +} + +JSValueRef RealmCreateObject(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { + try { + RJSValidateArgumentRange(argumentCount, 2, 3); + + SharedRealm sharedRealm = *RJSGetInternal(thisObject); + std::string className = RJSValidatedObjectTypeForValue(sharedRealm, ctx, arguments[0]); + auto &schema = sharedRealm->config().schema; + auto object_schema = schema->find(className); + + if (object_schema == schema->end()) { + *jsException = RJSMakeError(ctx, "Object type '" + className + "' not found in schema."); + return NULL; + } + + JSObjectRef object = RJSValidatedValueToObject(ctx, arguments[1]); + if (RJSIsValueArray(ctx, arguments[1])) { + object = RJSDictForPropertyArray(ctx, *object_schema, object); + } + + bool update = false; + if (argumentCount == 3) { + update = JSValueToBoolean(ctx, arguments[2]); + } + + return RJSObjectCreate(ctx, Object::create(ctx, sharedRealm, *object_schema, object, update)); + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + return NULL; + } +} + +JSValueRef RealmDelete(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { + try { + RJSValidateArgumentCount(argumentCount, 1); + + if (RJSIsValueArray(ctx, arguments[0]) || + JSValueIsObjectOfClass(ctx, arguments[0], RJSResultsClass()) || + JSValueIsObjectOfClass(ctx, arguments[0], RJSListClass())) + { + JSObjectRef array = RJSValidatedValueToObject(ctx, arguments[0]); + size_t length = RJSValidatedListLength(ctx, array); + for (long i = length-1; i >= 0; i--) { + JSValueRef object = RJSValidatedObjectAtIndex(ctx, array, (unsigned int)i); + RealmDelete(ctx, function, thisObject, 1, &object, jsException); + if (*jsException) { + return NULL; + } + } + return NULL; + } + + if (!JSValueIsObjectOfClass(ctx, arguments[0], RJSObjectClass())) { + throw std::runtime_error("Argument to 'delete' must be a Realm object or a collection of Realm objects."); + } + + Object *object = RJSGetInternal(RJSValidatedValueToObject(ctx, arguments[0])); + + SharedRealm realm = *RJSGetInternal(thisObject); + + if (!realm->is_in_transaction()) { + throw std::runtime_error("Can only delete objects within a transaction."); + } + + realm::TableRef table = ObjectStore::table_for_object_type(realm->read_group(), object->get_object_schema().name); + table->move_last_over(object->row().get_index()); + + return NULL; + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + return NULL; + } +} + +JSValueRef RealmDeleteAll(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { + try { + RJSValidateArgumentCount(argumentCount, 0); + + SharedRealm realm = *RJSGetInternal(thisObject); + + if (!realm->is_in_transaction()) { + throw std::runtime_error("Can only delete objects within a transaction."); + } + + for (auto objectSchema : *realm->config().schema) { + ObjectStore::table_for_object_type(realm->read_group(), objectSchema.name)->clear(); + } + return NULL; + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + return NULL; + } +} + +JSValueRef RealmWrite(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { + try { + RJSValidateArgumentCount(argumentCount, 1); + + JSObjectRef object = RJSValidatedValueToFunction(ctx, arguments[0]); + SharedRealm realm = *RJSGetInternal(thisObject); + realm->begin_transaction(); + JSObjectCallAsFunction(ctx, object, thisObject, 0, NULL, jsException); + if (*jsException) { + realm->cancel_transaction(); + } + else { + realm->commit_transaction(); + } + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + return NULL; + } + + return NULL; +} + +std::string RJSValidatedNotificationName(JSContextRef ctx, JSValueRef value) { + std::string name = RJSValidatedStringForValue(ctx, value); + if (name != "change") { + throw std::runtime_error("Only the 'change' notification name is supported."); + } + return name; +} + +JSValueRef RealmAddListener(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { + try { + RJSValidateArgumentCount(argumentCount, 2); + __unused std::string name = RJSValidatedNotificationName(ctx, arguments[0]); + JSObjectRef callback = RJSValidatedValueToFunction(ctx, arguments[1]); + + SharedRealm realm = *RJSGetInternal(thisObject); + static_cast(realm->m_binding_context.get())->add_notification(callback); + return NULL; + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + return NULL; + } +} + +JSValueRef RealmRemoveListener(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { + try { + RJSValidateArgumentCount(argumentCount, 2); + __unused std::string name = RJSValidatedNotificationName(ctx, arguments[0]); + JSObjectRef callback = RJSValidatedValueToFunction(ctx, arguments[1]); + + SharedRealm realm = *RJSGetInternal(thisObject); + static_cast(realm->m_binding_context.get())->remove_notification(callback); + return NULL; + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + return NULL; + } +} + +JSValueRef RealmRemoveAllListeners(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { + try { + RJSValidateArgumentRange(argumentCount, 0, 1); + if (argumentCount) { + RJSValidatedNotificationName(ctx, arguments[0]); + } + + SharedRealm realm = *RJSGetInternal(thisObject); + static_cast(realm->m_binding_context.get())->remove_all_notifications(); + return NULL; + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + return NULL; + } +} + +JSValueRef RealmClose(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { + try { + RJSValidateArgumentCount(argumentCount, 0); + SharedRealm realm = *RJSGetInternal(thisObject); + realm->close(); + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + } + return NULL; +} + +static const JSStaticFunction RJSRealmFuncs[] = { + {"objects", RealmObjects, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"create", RealmCreateObject, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"delete", RealmDelete, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"deleteAll", RealmDeleteAll, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"write", RealmWrite, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"addListener", RealmAddListener, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"removeListener", RealmRemoveListener, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"removeAllListeners", RealmRemoveAllListeners, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"close", RealmClose, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {NULL, NULL}, +}; + +JSClassRef RJSRealmClass() { + static JSClassRef s_realmClass = RJSCreateWrapperClass("Realm", RealmGetProperty, NULL, RJSRealmFuncs); + return s_realmClass; +} diff --git a/src/jsc/js_realm.hpp b/src/jsc/js_realm.hpp new file mode 100644 index 00000000..167e64bd --- /dev/null +++ b/src/jsc/js_realm.hpp @@ -0,0 +1,36 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "js_util.hpp" +#include + +namespace realm { + class Realm; + using ObjectDefaults = std::map; +} + +JSClassRef RJSRealmClass(); +JSClassRef RJSRealmConstructorClass(); + +std::string RJSDefaultPath(); +void RJSSetDefaultPath(std::string path); + +std::map &RJSDefaults(realm::Realm *realm); +std::map &RJSConstructors(realm::Realm *realm); diff --git a/src/jsc/types.hpp b/src/jsc/types.hpp new file mode 100644 index 00000000..1d2c5406 --- /dev/null +++ b/src/jsc/types.hpp @@ -0,0 +1,37 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include + +namespace realm { +namespace js { + +struct Types { + using Context = JSContextRef; + using Value = JSValueRef; + using Object = JSObjectRef; + using String = JSStringRef; + using Function = JSObjectRef; + using Return = JSValueRef; +}; + +}} \ No newline at end of file