diff --git a/examples/ReactExample/components/realm.js b/examples/ReactExample/components/realm.js index 4b00f9d7..13d042ad 100644 --- a/examples/ReactExample/components/realm.js +++ b/examples/ReactExample/components/realm.js @@ -20,20 +20,22 @@ import Realm from 'realm'; -class Todo {} +class Todo extends Realm.Object {} Todo.schema = { name: 'Todo', properties: { - done: {type: Realm.Types.BOOL, default: false}, - text: Realm.Types.STRING, + done: {type: 'bool', default: false}, + text: 'string', }, }; -class TodoList {} + +class TodoList extends Realm.Object {} TodoList.schema = { name: 'TodoList', properties: { - name: Realm.Types.STRING, - items: {type: Realm.Types.LIST, objectType: 'Todo', default: []}, + name: 'string', + items: {type: 'list', objectType: 'Todo'}, }, }; + export default new Realm({schema: [Todo, TodoList]}); diff --git a/src/js_realm.hpp b/src/js_realm.hpp index c7b49408..b436b0d8 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -243,7 +243,7 @@ inline typename T::Function Realm::create_constructor(ContextType ctx) { FunctionType results_constructor = ObjectWrap>::create_constructor(ctx); FunctionType realm_object_constructor = ObjectWrap>::create_constructor(ctx); - PropertyAttributes attributes = PropertyAttributes(ReadOnly | DontEnum | DontDelete); + PropertyAttributes attributes = ReadOnly | DontEnum | DontDelete; Object::set_property(ctx, realm_constructor, "Collection", collection_constructor, attributes); Object::set_property(ctx, realm_constructor, "List", list_constructor, attributes); Object::set_property(ctx, realm_constructor, "Results", results_constructor, attributes); diff --git a/src/js_types.hpp b/src/js_types.hpp index 79b6486d..2b1f1fbc 100644 --- a/src/js_types.hpp +++ b/src/js_types.hpp @@ -35,13 +35,17 @@ namespace realm { namespace js { -enum PropertyAttributes { +enum PropertyAttributes : unsigned { None = 0, ReadOnly = 1 << 0, DontEnum = 1 << 1, DontDelete = 1 << 2 }; +inline PropertyAttributes operator|(PropertyAttributes a, PropertyAttributes b) { + return PropertyAttributes(static_cast(a) | static_cast(b)); +} + template struct String { using StringType = typename T::String; diff --git a/src/jsc/jsc_class.hpp b/src/jsc/jsc_class.hpp index f60080f6..0a236219 100644 --- a/src/jsc/jsc_class.hpp +++ b/src/jsc/jsc_class.hpp @@ -48,10 +48,7 @@ class ObjectWrap { } static JSObjectRef create_constructor(JSContextRef ctx) { - if (JSClassRef constructor_class = get_constructor_class()) { - return JSObjectMake(ctx, constructor_class, nullptr); - } - return JSObjectMakeConstructor(ctx, get_class(), construct); + return JSObjectMake(ctx, get_constructor_class(), nullptr); } static JSClassRef get_class() { @@ -92,7 +89,9 @@ class ObjectWrap { static std::vector get_methods(const MethodMap &); static std::vector get_properties(const PropertyMap &); + static JSValueRef call(JSContextRef, JSObjectRef, JSObjectRef, size_t, const JSValueRef[], JSValueRef*); static JSObjectRef construct(JSContextRef, JSObjectRef, size_t, const JSValueRef[], JSValueRef*); + static void initialize_constructor(JSContextRef, JSObjectRef); static void finalize(JSObjectRef); static void get_property_names(JSContextRef, JSObjectRef, JSPropertyNameAccumulatorRef); static JSValueRef get_property(JSContextRef, JSObjectRef, JSStringRef, JSValueRef*); @@ -104,7 +103,7 @@ class ObjectWrap { } static bool has_instance(JSContextRef ctx, JSObjectRef constructor, JSValueRef value, JSValueRef* exception) { - return JSValueIsObjectOfClass(ctx, value, get_class()); + return has_instance(ctx, value); } }; @@ -158,19 +157,18 @@ inline JSClassRef ObjectWrap::create_class() { template inline JSClassRef ObjectWrap::create_constructor_class() { - // Skip creating a special constructor class if possible. - if (!s_class.constructor && s_class.static_methods.empty() && s_class.static_properties.empty()) { - return nullptr; - } - JSClassDefinition definition = kJSClassDefinitionEmpty; std::vector methods; std::vector properties; definition.attributes = kJSClassAttributeNoAutomaticPrototype; - definition.className = s_class.name.c_str(); + definition.className = "Function"; + definition.initialize = initialize_constructor; definition.hasInstance = has_instance; + // This must be set for `typeof constructor` to be 'function'. + definition.callAsFunction = call; + if (s_class.constructor) { definition.callAsConstructor = construct; } @@ -220,22 +218,57 @@ inline std::vector ObjectWrap::get_properties(const Pr } template -inline JSObjectRef ObjectWrap::construct(JSContextRef ctx, JSObjectRef constructor, size_t argc, const JSValueRef arguments[], JSValueRef* exception) { - if (!s_class.constructor) { - *exception = jsc::Exception::value(ctx, "Illegal constructor"); +inline JSValueRef ObjectWrap::call(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], JSValueRef* exception) { + // This should only be called as a super() call in the constructor of a subclass. + if (!has_instance(ctx, this_object)) { + *exception = jsc::Exception::value(ctx, s_class.name + " cannot be called as a function"); return nullptr; } - JSObjectRef this_object = ObjectWrap::create_instance(ctx); + // Classes without a constructor should still be subclassable. + if (s_class.constructor) { + try { + s_class.constructor(ctx, this_object, argc, arguments); + } + catch (std::exception &e) { + *exception = jsc::Exception::value(ctx, e); + return nullptr; + } + } + + return JSValueMakeUndefined(ctx); +} + +template +inline JSObjectRef ObjectWrap::construct(JSContextRef ctx, JSObjectRef constructor, size_t argc, const JSValueRef arguments[], JSValueRef* exception) { + if (!s_class.constructor) { + *exception = jsc::Exception::value(ctx, s_class.name + " is not a constructor"); + return nullptr; + } + + JSObjectRef this_object = create_instance(ctx); try { s_class.constructor(ctx, this_object, argc, arguments); } catch (std::exception &e) { *exception = jsc::Exception::value(ctx, e); + return nullptr; } return this_object; } +template +inline void ObjectWrap::initialize_constructor(JSContextRef ctx, JSObjectRef constructor) { + static const String prototype_string = "prototype"; + + // Set the prototype of the constructor to be Function.prototype. + Object::set_prototype(ctx, constructor, Object::get_prototype(ctx, JSObjectMakeFunctionWithCallback(ctx, nullptr, call))); + + // Set the constructor prototype to be the prototype generated from the instance JSClassRef. + JSObjectRef prototype = Object::validated_get_object(ctx, JSObjectMakeConstructor(ctx, get_class(), construct), prototype_string); + Object::set_property(ctx, constructor, prototype_string, prototype, js::ReadOnly | js::DontEnum | js::DontDelete); +} + template inline void ObjectWrap::finalize(JSObjectRef object) { // This is called for the most derived class before superclasses. diff --git a/src/jsc/jsc_init.cpp b/src/jsc/jsc_init.cpp index cb78f7c7..a1ba8a9e 100644 --- a/src/jsc/jsc_init.cpp +++ b/src/jsc/jsc_init.cpp @@ -37,7 +37,7 @@ void RJSInitializeInContext(JSContextRef ctx) { JSObjectRef global_object = JSContextGetGlobalObject(ctx); JSObjectRef realm_constructor = RJSConstructorCreate(ctx); - jsc::Object::set_property(ctx, global_object, realm_string, realm_constructor, js::PropertyAttributes(js::ReadOnly | js::DontEnum | js::DontDelete)); + jsc::Object::set_property(ctx, global_object, realm_string, realm_constructor, js::ReadOnly | js::DontEnum | js::DontDelete); } } // extern "C" diff --git a/tests/js/list-tests.js b/tests/js/list-tests.js index 4563528d..ab8f8472 100644 --- a/tests/js/list-tests.js +++ b/tests/js/list-tests.js @@ -36,6 +36,9 @@ module.exports = BaseTest.extend({ TestCase.assertThrows(function() { new Realm.List(); }); + + TestCase.assertEqual(typeof Realm.List, 'function'); + TestCase.assertTrue(Realm.List instanceof Function); }, testListLength: function() { diff --git a/tests/js/object-tests.js b/tests/js/object-tests.js index 3abad10b..d4351fbf 100644 --- a/tests/js/object-tests.js +++ b/tests/js/object-tests.js @@ -435,10 +435,14 @@ module.exports = BaseTest.extend({ testObjectConstructor: function() { var realm = new Realm({schema: [schemas.TestObject]}); + realm.write(function() { var obj = realm.create('TestObject', {doubleCol: 1}); TestCase.assertTrue(obj instanceof Realm.Object); }); + + TestCase.assertEqual(typeof Realm.Object, 'function'); + TestCase.assertTrue(Realm.Object instanceof Function); }, testIsValid: function() { diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index 79c018d6..04fcf29c 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -27,6 +27,9 @@ module.exports = BaseTest.extend({ testRealmConstructor: function() { var realm = new Realm({schema: []}); TestCase.assertTrue(realm instanceof Realm); + + TestCase.assertEqual(typeof Realm, 'function'); + TestCase.assertTrue(Realm instanceof Function); }, testRealmConstructorPath: function() { diff --git a/tests/js/results-tests.js b/tests/js/results-tests.js index 99229238..8a3f3f4e 100644 --- a/tests/js/results-tests.js +++ b/tests/js/results-tests.js @@ -34,6 +34,9 @@ module.exports = BaseTest.extend({ TestCase.assertThrows(function() { new Realm.Results(); }); + + TestCase.assertEqual(typeof Realm.Results, 'function'); + TestCase.assertTrue(Realm.Results instanceof Function); }, testResultsLength: function() {