From 133289ad85f3386a528fa5e0ea73eeb81aec4bee Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Thu, 14 Apr 2016 12:39:17 -0700 Subject: [PATCH] All tests now pass on Node --- binding.gyp | 1 - lib/index.js | 21 ++- package.json | 1 + src/ios/RealmJS.xcodeproj/project.pbxproj | 14 +- src/js_realm.hpp | 28 ++- src/js_types.hpp | 4 +- src/jsc/jsc_class.hpp | 40 +--- src/jsc/jsc_init.cpp | 33 +--- src/jsc/jsc_types.hpp | 17 +- src/node/node_class.hpp | 218 ++++++++++++++++------ src/node/{node_dummy.c => node_dummy.cpp} | 13 +- src/node/node_init.cpp | 11 +- src/node/node_object_accessor.cpp | 36 ---- src/node/node_object_accessor.hpp | 45 +++++ src/node/node_types.hpp | 42 +++-- tests/index.js | 38 ++++ tests/js/query-tests.json | 1 + 17 files changed, 348 insertions(+), 215 deletions(-) rename src/node/{node_dummy.c => node_dummy.cpp} (65%) delete mode 100644 src/node/node_object_accessor.cpp create mode 100644 tests/index.js create mode 120000 tests/js/query-tests.json diff --git a/binding.gyp b/binding.gyp index a259f902..77bcf04c 100644 --- a/binding.gyp +++ b/binding.gyp @@ -5,7 +5,6 @@ "sources": [ "src/js_realm.cpp", "src/node/node_init.cpp", - "src/node/node_object_accessor.cpp", "src/object-store/src/index_set.cpp", "src/object-store/src/list.cpp", "src/object-store/src/object_schema.cpp", diff --git a/lib/index.js b/lib/index.js index 9ac4465f..b8aa002c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -24,9 +24,13 @@ var realmConstructor; if (typeof Realm != 'undefined') { // The global Realm constructor should be available on device (using JavaScriptCore). realmConstructor = Realm; // eslint-disable-line no-undef -} else if (typeof navigator != 'undefined' && navigator.userAgent) { // eslint-disable-line no-undef +// eslint-disable-next-line +} else if (typeof navigator != 'undefined' && navigator.userAgent) { // The userAgent will be defined when running in a browser (such as Chrome debugging mode). realmConstructor = require('./browser').default; // (exported as ES6 module) +// eslint-disable-next-line +} else if (typeof process == 'object' && ('' + process) == '[object process]') { + realmConstructor = require('bindings')('realm').Realm; } else { throw new Error('Missing Realm constructor - please ensure RealmReact framework is included!'); } @@ -34,4 +38,19 @@ if (typeof Realm != 'undefined') { // Add the specified Array methods to the Collection prototype. Object.defineProperties(realmConstructor.Collection.prototype, arrayMethods); +// TODO: Remove this now useless object. +Object.defineProperty(realmConstructor, 'Types', { + value: Object.freeze({ + 'BOOL': 'bool', + 'INT': 'int', + 'FLOAT': 'float', + 'DOUBLE': 'double', + 'STRING': 'string', + 'DATE': 'date', + 'DATA': 'data', + 'OBJECT': 'object', + 'LIST': 'list', + }) +}); + module.exports = realmConstructor; diff --git a/package.json b/package.json index 6839cfa7..f7e3a128 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ }, "dependencies": { "bindings": "^1.2.1", + "mockery": "^1.6.2", "nan": "^2.2.1", "node-gyp": "^3.3.1", "rnpm": "1.5.2", diff --git a/src/ios/RealmJS.xcodeproj/project.pbxproj b/src/ios/RealmJS.xcodeproj/project.pbxproj index 536a75fd..7662ccf9 100644 --- a/src/ios/RealmJS.xcodeproj/project.pbxproj +++ b/src/ios/RealmJS.xcodeproj/project.pbxproj @@ -50,11 +50,10 @@ F60102DF1CBB96D300EC01BA /* async_query.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59ED61C88F2BA007F774C /* async_query.cpp */; }; F60102E01CBB96D900EC01BA /* realm_coordinator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EDB1C88F2BA007F774C /* realm_coordinator.cpp */; }; F60102E11CBB96DD00EC01BA /* transact_log_handler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EDD1C88F2BB007F774C /* transact_log_handler.cpp */; }; - F60102E41CBBB19700EC01BA /* node_object_accessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F60102E21CBBB19700EC01BA /* node_object_accessor.cpp */; }; F60102E51CBBB19700EC01BA /* node_object_accessor.hpp in Headers */ = {isa = PBXBuildFile; fileRef = F60102E31CBBB19700EC01BA /* node_object_accessor.hpp */; }; F60102E81CBBB36500EC01BA /* jsc_object_accessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F60102E61CBBB36500EC01BA /* jsc_object_accessor.cpp */; }; F60102E91CBCAEC500EC01BA /* platform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 029048381C042A8F00ABDED4 /* platform.mm */; }; - F60102EA1CBCAFC300EC01BA /* node_dummy.c in Sources */ = {isa = PBXBuildFile; fileRef = F6267BCA1CADC49200AC36B1 /* node_dummy.c */; }; + F60102EA1CBCAFC300EC01BA /* node_dummy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F6267BCA1CADC49200AC36B1 /* node_dummy.cpp */; }; F61378791C18EAC5008BFC51 /* js in Resources */ = {isa = PBXBuildFile; fileRef = F61378781C18EAAC008BFC51 /* js */; }; F620F0581CB766DA0082977B /* node_init.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F620F0571CB766DA0082977B /* node_init.cpp */; }; F620F0751CB9F60C0082977B /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F620F0741CB9F60C0082977B /* CoreFoundation.framework */; }; @@ -174,7 +173,6 @@ 02F59EE01C88F2BB007F774C /* weak_realm_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = weak_realm_notifier.hpp; path = src/impl/weak_realm_notifier.hpp; sourceTree = ""; }; F60102CF1CBB814A00EC01BA /* node_init.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = node_init.hpp; sourceTree = ""; }; F60102D11CBB865A00EC01BA /* jsc_init.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = jsc_init.hpp; sourceTree = ""; }; - F60102E21CBBB19700EC01BA /* node_object_accessor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = node_object_accessor.cpp; sourceTree = ""; }; F60102E31CBBB19700EC01BA /* node_object_accessor.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = node_object_accessor.hpp; sourceTree = ""; }; F60102E61CBBB36500EC01BA /* jsc_object_accessor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = jsc_object_accessor.cpp; sourceTree = ""; }; F60102E71CBBB36500EC01BA /* jsc_object_accessor.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = jsc_object_accessor.hpp; sourceTree = ""; }; @@ -187,7 +185,7 @@ F620F0591CB7B4C80082977B /* js_object_accessor.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = js_object_accessor.hpp; sourceTree = ""; }; F620F0741CB9F60C0082977B /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/CoreFoundation.framework; sourceTree = DEVELOPER_DIR; }; F6267BC91CADC30000AC36B1 /* js_util.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = js_util.hpp; sourceTree = ""; }; - F6267BCA1CADC49200AC36B1 /* node_dummy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = node_dummy.c; sourceTree = ""; }; + F6267BCA1CADC49200AC36B1 /* node_dummy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = node_dummy.cpp; sourceTree = ""; }; F62BF8FB1CAC71780022BCDC /* libRealmNode.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libRealmNode.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F63FF2B11C1241E500B3B8E0 /* libRealmJS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRealmJS.a; sourceTree = BUILT_PRODUCTS_DIR; }; F63FF2DC1C15659A00B3B8E0 /* RealmJS.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RealmJS.mm; sourceTree = ""; }; @@ -410,13 +408,12 @@ F62BF9001CAC72C40022BCDC /* Node */ = { isa = PBXGroup; children = ( - F6267BCA1CADC49200AC36B1 /* node_dummy.c */, + F6267BCA1CADC49200AC36B1 /* node_dummy.cpp */, F60102CF1CBB814A00EC01BA /* node_init.hpp */, F620F0571CB766DA0082977B /* node_init.cpp */, F620F0551CB655A50082977B /* node_class.hpp */, F6874A351CAC792D00EEEE36 /* node_types.hpp */, F60102E31CBBB19700EC01BA /* node_object_accessor.hpp */, - F60102E21CBBB19700EC01BA /* node_object_accessor.cpp */, ); name = Node; path = node; @@ -707,7 +704,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "cp ../object-store/tests/query.json \"$TARGET_BUILD_DIR/$CONTENTS_FOLDER_PATH/js/query-tests.json\""; + shellScript = "cp -f ../object-store/tests/query.json \"$TARGET_BUILD_DIR/$CONTENTS_FOLDER_PATH/js/query-tests.json\""; }; F63FF2C51C12462600B3B8E0 /* Download Core */ = { isa = PBXShellScriptBuildPhase; @@ -741,7 +738,6 @@ buildActionMask = 2147483647; files = ( F60102D31CBB966E00EC01BA /* js_realm.cpp in Sources */, - F60102E41CBBB19700EC01BA /* node_object_accessor.cpp in Sources */, F60102D61CBB96B400EC01BA /* object_schema.cpp in Sources */, F60102D41CBB96AB00EC01BA /* index_set.cpp in Sources */, F60102DB1CBB96C600EC01BA /* parser.cpp in Sources */, @@ -752,7 +748,7 @@ F60102D71CBB96B800EC01BA /* object_store.cpp in Sources */, F60102DA1CBB96C300EC01BA /* shared_realm.cpp in Sources */, F60102E01CBB96D900EC01BA /* realm_coordinator.cpp in Sources */, - F60102EA1CBCAFC300EC01BA /* node_dummy.c in Sources */, + F60102EA1CBCAFC300EC01BA /* node_dummy.cpp in Sources */, F60102D81CBB96BD00EC01BA /* results.cpp in Sources */, F60102DE1CBB96CF00EC01BA /* weak_realm_notifier.cpp in Sources */, F620F0581CB766DA0082977B /* node_init.cpp in Sources */, diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 4233d4af..2ea8ebf3 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -18,8 +18,8 @@ #pragma once +#include #include -#include #include "js_class.hpp" #include "js_types.hpp" @@ -67,10 +67,20 @@ class RealmDelegate : public BindingContext { } void add_notification(TFunction notification) { - m_notifications.insert(Protected(m_context, notification)); + for (auto &handler : m_notifications) { + if (handler == notification) { + return; + } + } + m_notifications.emplace_back(m_context, notification); } void remove_notification(TFunction notification) { - m_notifications.erase(Protected(m_context, notification)); + for (auto iter = m_notifications.begin(); iter != m_notifications.end(); ++iter) { + if (*iter == notification) { + m_notifications.erase(iter); + return; + } + } } void remove_all_notifications() { m_notifications.clear(); @@ -81,7 +91,7 @@ class RealmDelegate : public BindingContext { private: Protected m_context; - std::set> m_notifications; + std::list> m_notifications; std::weak_ptr m_realm; void notify(const char *notification_name) { @@ -142,11 +152,11 @@ class Realm { static void GetDefaultPath(TContext, TObject, ReturnValue &); static void SetDefaultPath(TContext, TObject, TValue value); - static TObject create_constructor(TContext ctx) { - TObject realm_constructor = ObjectWrap>::create_constructor(ctx); - TObject collection_constructor = ObjectWrap>::create_constructor(ctx); - TObject list_constructor = ObjectWrap>::create_constructor(ctx); - TObject results_constructor = ObjectWrap>::create_constructor(ctx); + static TFunction create_constructor(TContext ctx) { + TFunction realm_constructor = ObjectWrap>::create_constructor(ctx); + TFunction collection_constructor = ObjectWrap>::create_constructor(ctx); + TFunction list_constructor = ObjectWrap>::create_constructor(ctx); + TFunction results_constructor = ObjectWrap>::create_constructor(ctx); PropertyAttributes attributes = PropertyAttributes(ReadOnly | DontEnum | DontDelete); Object::set_property(ctx, realm_constructor, "Collection", collection_constructor, attributes); diff --git a/src/js_types.hpp b/src/js_types.hpp index a9697efc..3bd81bf2 100644 --- a/src/js_types.hpp +++ b/src/js_types.hpp @@ -73,6 +73,7 @@ struct Value { static bool is_array(TContext, const TValue &); static bool is_array_buffer(TContext, const TValue &); + static bool is_array_buffer_view(TContext, const TValue &); static bool is_boolean(TContext, const TValue &); static bool is_constructor(TContext, const TValue &); static bool is_date(TContext, const TValue &); @@ -239,7 +240,6 @@ class Protected { bool operator!=(const TValue &) const; bool operator==(const Protected &) const; bool operator!=(const Protected &) const; - bool operator<(const Protected &) const; }; template @@ -249,6 +249,8 @@ struct Exception : public std::runtime_error { const Protected m_value; + Exception(TContext ctx, const std::string &message) + : std::runtime_error(message), m_value(value(ctx, message)) {} Exception(TContext ctx, const TValue &val) : std::runtime_error(std::string(Value::to_string(ctx, val))), m_value(ctx, val) {} diff --git a/src/jsc/jsc_class.hpp b/src/jsc/jsc_class.hpp index 3299fc5c..2aefe1cf 100644 --- a/src/jsc/jsc_class.hpp +++ b/src/jsc/jsc_class.hpp @@ -40,30 +40,32 @@ template class ObjectWrap { public: using Internal = typename ClassType::Internal; + operator Internal*() const { return m_object.get(); } + ObjectWrap& operator=(Internal* object) { if (m_object.get() != object) { m_object = std::unique_ptr(object); } return *this; } - + static JSClassRef get_class() { static JSClassRef js_class = create_class(); return js_class; } - + static JSClassRef get_constructor_class() { static JSClassRef js_class = create_constructor_class(); return js_class; } - + static JSObjectRef create_instance(JSContextRef ctx, Internal* internal = nullptr) { return JSObjectMake(ctx, get_class(), new ObjectWrap(internal)); } - + static JSObjectRef create_constructor(JSContextRef ctx) { if (JSClassRef constructor_class = get_constructor_class()) { return JSObjectMake(ctx, constructor_class, nullptr); @@ -71,11 +73,11 @@ public: return JSObjectMakeConstructor(ctx, get_class(), construct); } - + static bool has_instance(JSContextRef ctx, JSValueRef value) { return JSValueIsObjectOfClass(ctx, value, get_class()); } - + private: static ClassType s_class; @@ -134,7 +136,7 @@ private: return index_setter(ctx, object, index, value, exception); } else { - *exception = Exception::value(ctx, std::string("Cannot assigned to read only index ") + util::to_string(index)); + *exception = Exception::value(ctx, std::string("Cannot assign to read only index ") + util::to_string(index)); return false; } } @@ -288,33 +290,9 @@ class ObjectWrap { public: using Internal = void; - operator Internal*() const { - return nullptr; - } - - ObjectWrap& operator=(Internal* object) { - return *this; - } - static JSClassRef get_class() { return nullptr; } - - static JSClassRef get_constructor_class() { - return nullptr; - } - - static JSObjectRef create_instance(JSContextRef ctx, Internal* internal = nullptr) { - return nullptr; - } - - static JSObjectRef create_constructor(JSContextRef ctx) { - return nullptr; - } - - static bool has_instance(JSContextRef ctx, JSValueRef value) { - return nullptr; - } }; // The declared static variables must be defined as well. diff --git a/src/jsc/jsc_init.cpp b/src/jsc/jsc_init.cpp index 3a36ba6a..cb78f7c7 100644 --- a/src/jsc/jsc_init.cpp +++ b/src/jsc/jsc_init.cpp @@ -27,39 +27,8 @@ extern "C" { using namespace realm; using namespace realm::jsc; -JSValueRef RJSTypeGet(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) { - std::string str = String(propertyName); - std::transform(str.begin(), str.end(), str.begin(), ::tolower); - return Value::from_string(ctx, str); -} - -JSClassRef RJSRealmTypeClass() { - JSClassDefinition realmTypesDefinition = kJSClassDefinitionEmpty; - realmTypesDefinition.className = "PropTypes"; - JSStaticValue types[] = { - { "BOOL", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, - { "INT", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, - { "FLOAT", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, - { "DOUBLE", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, - { "STRING", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, - { "DATE", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, - { "DATA", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, - { "OBJECT", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, - { "LIST", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, - { NULL, NULL, NULL, 0 } - }; - realmTypesDefinition.staticValues = types; - return JSClassCreate(&realmTypesDefinition); -} - JSObjectRef RJSConstructorCreate(JSContextRef ctx) { - JSObjectRef realm_constructor = js::Realm::create_constructor(ctx); - JSObjectRef types_object = JSObjectMake(ctx, RJSRealmTypeClass(), nullptr); - - // TODO: Either remove this (preferable) or move implementation to JS. - jsc::Object::set_property(ctx, realm_constructor, "Types", types_object, js::PropertyAttributes(js::ReadOnly | js::DontEnum | js::DontDelete)); - - return realm_constructor; + return js::Realm::create_constructor(ctx); } void RJSInitializeInContext(JSContextRef ctx) { diff --git a/src/jsc/jsc_types.hpp b/src/jsc/jsc_types.hpp index 039b9c27..835f0f96 100644 --- a/src/jsc/jsc_types.hpp +++ b/src/jsc/jsc_types.hpp @@ -57,21 +57,6 @@ class Protected { operator T() const { return m_value; } - bool operator==(const T &other) const { - return m_value == other; - } - bool operator!=(const T &other) const { - return m_value != other; - } - bool operator==(const Protected &other) const { - return m_value == other; - } - bool operator!=(const Protected &other) const { - return m_value != other; - } - bool operator<(const Protected &other) const { - return m_value < other.m_value; - } }; template @@ -126,7 +111,7 @@ class ReturnValue { const JSContextRef m_context; JSValueRef m_value = nullptr; -public: + public: ReturnValue(JSContextRef ctx) : m_context(ctx) {} void set(const JSValueRef &value) { diff --git a/src/node/node_class.hpp b/src/node/node_class.hpp index 27e93d81..5319e011 100644 --- a/src/node/node_class.hpp +++ b/src/node/node_class.hpp @@ -20,6 +20,7 @@ #include "node_types.hpp" #include "js_class.hpp" +#include "js_util.hpp" namespace realm { namespace node { @@ -45,9 +46,15 @@ static inline std::vector> get_arguments(const Nan::Functio return arguments; } -static inline void setup_method(v8::Local tpl, const std::string &name, Nan::FunctionCallback callback) { - Nan::HandleScope scope; +static inline void setup_static_method(v8::Local tpl, const std::string &name, Nan::FunctionCallback callback) { + v8::Local fn = Nan::GetFunction(Nan::New(callback)).ToLocalChecked(); + v8::Local fn_name = Nan::New(name).ToLocalChecked(); + tpl->Set(fn_name, fn, v8::PropertyAttribute::DontEnum); + fn->SetName(fn_name); +} + +static inline void setup_method(v8::Local tpl, const std::string &name, Nan::FunctionCallback callback) { v8::Local signature = Nan::New(tpl); v8::Local t = Nan::New(callback, v8::Local(), signature); v8::Local fn = Nan::GetFunction(t).ToLocalChecked(); @@ -58,54 +65,83 @@ static inline void setup_method(v8::Local tpl, const std:: fn->SetName(fn_name); } -static inline void setup_property(v8::Local tpl, const std::string &name, const PropertyType &property) { - Nan::HandleScope scope; - - v8::Local prop_name = Nan::New(name).ToLocalChecked(); - v8::PropertyAttribute attributes = static_cast(v8::DontEnum | v8::DontDelete | (property.setter ? v8::None : v8::ReadOnly)); - - Nan::SetAccessor(tpl, prop_name, property.getter, property.setter, v8::Local(), v8::DEFAULT, attributes); +static inline void set_readonly_property(v8::Local property, v8::Local value, Nan::NAN_SETTER_ARGS_TYPE info) { + std::string message = std::string("Cannot assign to read only property '") + std::string(String(property)) + "'"; + Nan::ThrowError(message.c_str()); } -template +static inline void set_readonly_index(uint32_t index, v8::Local value, Nan::NAN_INDEX_SETTER_ARGS_TYPE info) { + std::string message = std::string("Cannot assign to read only index ") + util::to_string(index); + Nan::ThrowError(message.c_str()); +} + +template +static inline void setup_property(v8::Local target, const std::string &name, const PropertyType &property) { + v8::Local prop_name = Nan::New(name).ToLocalChecked(); + v8::PropertyAttribute attributes = v8::PropertyAttribute(v8::DontEnum | v8::DontDelete); + + Nan::SetAccessor(target, prop_name, property.getter, property.setter ?: set_readonly_property, v8::Local(), v8::DEFAULT, attributes); +} + +template class ObjectWrap : public Nan::ObjectWrap { - static ClassDefinition s_class; - static Nan::Persistent s_constructor; - static Nan::Persistent s_template; + using Internal = typename ClassType::Internal; - std::unique_ptr m_object; + static ClassType s_class; - ObjectWrap(T* object = nullptr) : m_object(object) {} + std::unique_ptr m_object; - public: - operator T*() const { - return m_object.get(); + ObjectWrap(Internal* object = nullptr) : m_object(object) {} + + template + static v8::Local get_superclass(ClassDefinition*) { + return ObjectWrap::get_template(); } - ObjectWrap& operator=(T* object) { - if (m_object.get() != object) { - m_object = std::unique_ptr(object); + + static void get_nonexistent_property(v8::Local property, Nan::NAN_PROPERTY_GETTER_ARGS_TYPE info) { + // Do nothing. This function exists only to prevent a crash where it is used. + } + + static void set_property(v8::Local property, v8::Local value, Nan::NAN_PROPERTY_SETTER_ARGS_TYPE info) { + if (s_class.index_accessor.getter || s_class.index_accessor.setter) { + try { + // Negative indices are passed into this string property interceptor, so check for them here. + validated_positive_index(node::String(property)); + } + catch (std::out_of_range &e) { + Nan::ThrowError(Exception::value(info.GetIsolate(), e)); + return; + } + catch (std::invalid_argument &) { + // Property is not a number. + } + } + if (auto string_setter = s_class.string_accessor.setter) { + string_setter(property, value, info); } - return *this; } - static v8::Local create_instance(v8::Isolate* isolate, T* internal = nullptr) { + static void get_indexes(Nan::NAN_INDEX_ENUMERATOR_ARGS_TYPE info) { + uint32_t length; + try { + length = Object::validated_get_length(info.GetIsolate(), info.This()); + } + catch (std::exception &) { + // Enumerating properties should never throw an exception. + return; + } + + v8::Local array = Nan::New(length); + for (uint32_t i = 0; i < length; i++) { + Nan::Set(array, i, Nan::New(i)); + } + + info.GetReturnValue().Set(array); + } + + static v8::Local create_template() { Nan::EscapableHandleScope scope; - // TODO: Figure out why this template ends up being empty here. - v8::Local tpl = Nan::New(s_template); - v8::Local instance = Nan::NewInstance(tpl->InstanceTemplate()).ToLocalChecked(); - - auto wrap = new ObjectWrap(internal); - wrap->Wrap(instance); - - return scope.Escape(instance); - } - - static bool has_instance(v8::Isolate* isolate, const v8::Local &value) { - return Nan::New(s_template)->HasInstance(value); - } - - static NAN_MODULE_INIT(init) { v8::Local tpl = Nan::New(construct); v8::Local instance_tpl = tpl->InstanceTemplate(); v8::Local name = Nan::New(s_class.name).ToLocalChecked(); @@ -113,32 +149,81 @@ class ObjectWrap : public Nan::ObjectWrap { tpl->SetClassName(name); instance_tpl->SetInternalFieldCount(1); - // TODO: Setup static properties and methods. + v8::Local super_tpl = get_superclass(s_class.superclass); + if (!super_tpl.IsEmpty()) { + tpl->Inherit(super_tpl); + } + + // Static properties are setup in create_constructor() because V8. + for (auto &pair : s_class.static_methods) { + setup_static_method(tpl, pair.first, pair.second); + } for (auto &pair : s_class.methods) { setup_method(tpl, pair.first, pair.second); } for (auto &pair : s_class.properties) { - setup_property(instance_tpl, pair.first, pair.second); + setup_property(instance_tpl, pair.first, pair.second); } if (s_class.index_accessor.getter) { - // TODO: Add our own index enumerator. auto &index_accessor = s_class.index_accessor; - Nan::SetIndexedPropertyHandler(instance_tpl, index_accessor.getter, index_accessor.setter); + Nan::SetIndexedPropertyHandler(instance_tpl, index_accessor.getter, index_accessor.setter ?: set_readonly_index, 0, 0, get_indexes); } - if (s_class.string_accessor.getter) { + if (s_class.string_accessor.getter || s_class.index_accessor.getter || s_class.index_accessor.setter) { + // Use our own wrapper for the setter since we want to throw for negative indices. auto &string_accessor = s_class.string_accessor; - Nan::SetNamedPropertyHandler(instance_tpl, string_accessor.getter, string_accessor.setter, 0, 0, string_accessor.enumerator); + Nan::SetNamedPropertyHandler(instance_tpl, string_accessor.getter ?: get_nonexistent_property, set_property, 0, 0, string_accessor.enumerator); } - v8::Local constructor = Nan::GetFunction(tpl).ToLocalChecked(); - s_constructor.Reset(constructor); - s_template.Reset(tpl); - - Nan::Set(target, name, constructor); + return scope.Escape(tpl); } - static NAN_METHOD(construct) { + public: + operator Internal*() const { + return m_object.get(); + } + ObjectWrap& operator=(Internal* object) { + if (m_object.get() != object) { + m_object = std::unique_ptr(object); + } + return *this; + } + + static v8::Local get_template() { + static Nan::Persistent js_template(create_template()); + return Nan::New(js_template); + } + + static v8::Local create_constructor(v8::Isolate* isolate) { + Nan::EscapableHandleScope scope; + + v8::Local tpl = get_template(); + v8::Local constructor = Nan::GetFunction(tpl).ToLocalChecked(); + + for (auto &pair : s_class.static_properties) { + setup_property(constructor, pair.first, pair.second); + } + + return scope.Escape(constructor); + } + + static v8::Local create_instance(v8::Isolate* isolate, Internal* internal = nullptr) { + Nan::EscapableHandleScope scope; + + v8::Local tpl = get_template(); + v8::Local instance = Nan::NewInstance(tpl->InstanceTemplate()).ToLocalChecked(); + + auto wrap = new ObjectWrap(internal); + wrap->Wrap(instance); + + return scope.Escape(instance); + } + + static bool has_instance(v8::Isolate* isolate, const v8::Local &value) { + return get_template()->HasInstance(value); + } + + static void construct(Nan::NAN_METHOD_ARGS_TYPE info) { if (!info.IsConstructCall()) { Nan::ThrowError("Constructor must be called with new"); } @@ -148,7 +233,7 @@ class ObjectWrap : public Nan::ObjectWrap { v8::Local this_object = info.This(); info.GetReturnValue().Set(this_object); - auto wrap = new ObjectWrap(); + auto wrap = new ObjectWrap(); wrap->Wrap(this_object); try { @@ -164,10 +249,19 @@ class ObjectWrap : public Nan::ObjectWrap { } }; +template<> +class ObjectWrap { + public: + using Internal = void; + + static v8::Local get_template() { + return v8::Local();; + } +}; + // The declared static variables must be defined as well. -template ClassDefinition ObjectWrap::s_class; -template Nan::Persistent ObjectWrap::s_constructor; -template Nan::Persistent ObjectWrap::s_template; +template +ClassType ObjectWrap::s_class; } // node @@ -190,7 +284,7 @@ void wrap(Nan::NAN_METHOD_ARGS_TYPE info) { } } -Type::template +template void wrap(v8::Local property, Nan::NAN_GETTER_ARGS_TYPE info) { v8::Isolate* isolate = info.GetIsolate(); node::ReturnValue return_value(info.GetReturnValue()); @@ -220,6 +314,10 @@ void wrap(uint32_t index, Nan::NAN_INDEX_GETTER_ARGS_TYPE info) { try { F(isolate, info.This(), index, return_value); } + catch (std::out_of_range &) { + // Out-of-bounds index getters should just return undefined in JS. + return_value.set_undefined(); + } catch(std::exception &e) { Nan::ThrowError(node::Exception::value(isolate, e)); } @@ -229,7 +327,10 @@ template void wrap(uint32_t index, v8::Local value, Nan::NAN_INDEX_SETTER_ARGS_TYPE info) { v8::Isolate* isolate = info.GetIsolate(); try { - F(isolate, info.This(), index, value); + if (F(isolate, info.This(), index, value)) { + // Indicate that the property was intercepted. + info.GetReturnValue().Set(value); + } } catch(std::exception &e) { Nan::ThrowError(node::Exception::value(isolate, e)); @@ -252,7 +353,10 @@ template void wrap(v8::Local property, v8::Local value, Nan::NAN_PROPERTY_SETTER_ARGS_TYPE info) { v8::Isolate* isolate = info.GetIsolate(); try { - F(isolate, info.This(), property, value); + if (F(isolate, info.This(), property, value)) { + // Indicate that the property was intercepted. + info.GetReturnValue().Set(value); + } } catch(std::exception &e) { Nan::ThrowError(node::Exception::value(isolate, e)); diff --git a/src/node/node_dummy.c b/src/node/node_dummy.cpp similarity index 65% rename from src/node/node_dummy.c rename to src/node/node_dummy.cpp index 1221fe3b..c4522d55 100644 --- a/src/node/node_dummy.c +++ b/src/node/node_dummy.cpp @@ -16,4 +16,15 @@ // //////////////////////////////////////////////////////////////////////////// -void node_module_register(void* mod) {} +// NOTE: This dummy file exists only to make Xcode build the Realm Node dynamic library. +#include "node.h" + +extern "C" void node_module_register(void* mod) {} + +namespace node { +namespace Buffer { + bool HasInstance(v8::Local val) { return false; } + char* Data(v8::Local val) { return nullptr; } + size_t Length(v8::Local val) { return 0; } +} +} diff --git a/src/node/node_init.cpp b/src/node/node_init.cpp index d9bf5cfa..da21c3ff 100644 --- a/src/node/node_init.cpp +++ b/src/node/node_init.cpp @@ -22,13 +22,10 @@ namespace realm { namespace node { static void init(v8::Local exports) { - ObjectWrap::init(exports); - ObjectWrap::init(exports); -// RealmObjectWrap::Init(exports); -// RealmResultsWrap::Init(exports); -// RealmListWrap::Init(exports); -// -// NODE_SET_METHOD(exports, "__clearCaches", ClearCaches); + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Local realm_constructor = js::Realm::create_constructor(isolate); + + Nan::Set(exports, realm_constructor->GetName(), realm_constructor); } } // node diff --git a/src/node/node_object_accessor.cpp b/src/node/node_object_accessor.cpp deleted file mode 100644 index 3c244331..00000000 --- a/src/node/node_object_accessor.cpp +++ /dev/null @@ -1,36 +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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "node_object_accessor.hpp" - -using namespace realm; -using namespace realm::node; - -using Accessor = js::NativeAccessor; - -template<> -std::string Accessor::to_binary(v8::Isolate* isolate, v8::Local &value) { - // TODO - return std::string(); -} - -template<> -v8::Local Accessor::from_binary(v8::Isolate* isolate, BinaryData data) { - // TODO - return v8::Local(); -} diff --git a/src/node/node_object_accessor.hpp b/src/node/node_object_accessor.hpp index 975aab37..029ffdd0 100644 --- a/src/node/node_object_accessor.hpp +++ b/src/node/node_object_accessor.hpp @@ -27,4 +27,49 @@ namespace realm { template<> class NativeAccessor : public js::NativeAccessor {}; +namespace js { + +template<> +inline std::string NativeAccessor::to_binary(v8::Isolate* isolate, v8::Local &value) { + if (Value::is_array_buffer(isolate, value)) { + // TODO: This probably needs some abstraction for older V8. +#if REALM_V8_ARRAY_BUFFER_API + v8::Local array_buffer = value.As(); + v8::ArrayBuffer::Contents contents = array_buffer->GetContents(); + + return std::string(static_cast(contents.Data()), contents.ByteLength()); +#else + // TODO: Implement this for older V8 +#endif + } + else if (Value::is_array_buffer_view(isolate, value)) { + Nan::TypedArrayContents contents(value); + + return std::string(*contents, contents.length()); + } + else if (::node::Buffer::HasInstance(value)) { + return std::string(::node::Buffer::Data(value), ::node::Buffer::Length(value)); + } + + throw std::runtime_error("Can only convert Buffer, ArrayBuffer, and TypedArray objects to binary"); +} + +template<> +inline v8::Local NativeAccessor::from_binary(v8::Isolate* isolate, BinaryData data) { +#if REALM_V8_ARRAY_BUFFER_API + size_t byte_count = data.size(); + void* bytes = nullptr; + + if (byte_count) { + bytes = memcpy(malloc(byte_count), data.data(), byte_count); + } + + // An "internalized" ArrayBuffer will free the malloc'd memory when garbage collected. + return v8::ArrayBuffer::New(isolate, bytes, byte_count, v8::ArrayBufferCreationMode::kInternalized); +#else + // TODO: Implement this for older V8 +#endif +} + +} // js } // realm diff --git a/src/node/node_types.hpp b/src/node/node_types.hpp index 95643197..d99343ac 100644 --- a/src/node/node_types.hpp +++ b/src/node/node_types.hpp @@ -27,6 +27,10 @@ #include "js_types.hpp" +#if defined(V8_MAJOR_VERSION) && (V8_MAJOR_VERSION > 4 || (V8_MAJOR_VERSION == 4 && defined(V8_MINOR_VERSION) && V8_MINOR_VERSION >= 3)) +#define REALM_V8_ARRAY_BUFFER_API 1 +#endif + namespace realm { namespace node { @@ -72,9 +76,6 @@ class Protected { bool operator!=(const Protected &other) const { return m_value != other.m_value; } - bool operator<(const Protected &other) const { - return *Nan::New(m_value) < *Nan::New(other.m_value); - } }; template @@ -188,7 +189,20 @@ inline bool node::Value::is_array(v8::Isolate* isolate, const v8::Local inline bool node::Value::is_array_buffer(v8::Isolate* isolate, const v8::Local &value) { +#if REALM_V8_ARRAY_BUFFER_API return value->IsArrayBuffer(); +#else + // TODO: Implement this! +#endif +} + +template<> +inline bool node::Value::is_array_buffer_view(v8::Isolate* isolate, const v8::Local &value) { +#if REALM_V8_ARRAY_BUFFER_API + return value->IsArrayBufferView(); +#else + // TODO: Implement this! +#endif } template<> @@ -440,27 +454,27 @@ inline v8::Local node::Object::create_date(v8::Isolate* isolate, dou } template<> -template -inline v8::Local node::Object::create_instance(v8::Isolate* isolate, U* internal) { - return node::ObjectWrap::create_instance(isolate, internal); +template +inline v8::Local node::Object::create_instance(v8::Isolate* isolate, typename ClassType::Internal* internal) { + return node::ObjectWrap::create_instance(isolate, internal); } template<> -template +template inline bool node::Object::is_instance(v8::Isolate* isolate, const v8::Local &object) { - return node::ObjectWrap::has_instance(isolate, object); + return node::ObjectWrap::has_instance(isolate, object); } template<> -template -inline U* node::Object::get_internal(const v8::Local &object) { - return *Nan::ObjectWrap::Unwrap>(object); +template +inline typename ClassType::Internal* node::Object::get_internal(const v8::Local &object) { + return *Nan::ObjectWrap::Unwrap>(object); } template<> -template -inline void node::Object::set_internal(const v8::Local &object, U* ptr) { - auto wrap = Nan::ObjectWrap::Unwrap>(object); +template +inline void node::Object::set_internal(const v8::Local &object, typename ClassType::Internal* ptr) { + auto wrap = Nan::ObjectWrap::Unwrap>(object); *wrap = ptr; } diff --git a/tests/index.js b/tests/index.js new file mode 100644 index 00000000..48455ea8 --- /dev/null +++ b/tests/index.js @@ -0,0 +1,38 @@ +'use strict'; + +const mockery = require('mockery'); + +function runTests() { + const RealmTests = require('./js'); + const testNames = RealmTests.getTestNames(); + + for (let suiteName in testNames) { + let testSuite = RealmTests[suiteName]; + + console.log('Starting ' + suiteName); + + for (let testName of testNames[suiteName]) { + RealmTests.runTest(suiteName, 'beforeEach'); + + try { + RealmTests.runTest(suiteName, testName); + console.log('+ ' + testName); + } + catch (e) { + console.warn('- ' + testName); + console.error(e.message, e.stack); + } + finally { + RealmTests.runTest(suiteName, 'afterEach'); + } + } + } +} + +if (require.main == module) { + mockery.enable(); + mockery.warnOnUnregistered(false); + mockery.registerMock('realm', require('..')); + + runTests(); +} diff --git a/tests/js/query-tests.json b/tests/js/query-tests.json new file mode 120000 index 00000000..cb35905f --- /dev/null +++ b/tests/js/query-tests.json @@ -0,0 +1 @@ +../../src/object-store/tests/query.json \ No newline at end of file