diff --git a/examples/ReactNativeBenchmarks/benchmarks.js b/examples/ReactNativeBenchmarks/benchmarks.js index e8481a2f..2327f26b 100644 --- a/examples/ReactNativeBenchmarks/benchmarks.js +++ b/examples/ReactNativeBenchmarks/benchmarks.js @@ -1,7 +1,21 @@ -/** - * Sample React Native App - * https://github.com/facebook/react-native - */ +//////////////////////////////////////////////////////////////////////////// +// +// 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. +// +//////////////////////////////////////////////////////////////////////////// + 'use strict'; import React, { AppRegistry, diff --git a/lib/browser/index.js b/lib/browser/index.js index c9793fee..f14f22ef 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -19,11 +19,11 @@ 'use strict'; import { NativeModules } from 'react-native'; -import { keys, propTypes, objectTypes } from './constants'; +import { keys, objectTypes } from './constants'; import Collection, * as collections from './collections'; import List, { createList } from './lists'; import Results, { createResults } from './results'; -import RealmObject, { createObject, registerConstructors, typeForConstructor } from './objects'; +import RealmObject, * as objects from './objects'; import * as rpc from './rpc'; import * as util from './util'; @@ -31,7 +31,7 @@ const {debugHosts, debugPort} = NativeModules.Realm; rpc.registerTypeConverter(objectTypes.LIST, createList); rpc.registerTypeConverter(objectTypes.RESULTS, createResults); -rpc.registerTypeConverter(objectTypes.OBJECT, createObject); +rpc.registerTypeConverter(objectTypes.OBJECT, objects.createObject); rpc.registerTypeConverter(objectTypes.REALM, createRealm); function createRealm(_, info) { @@ -59,7 +59,7 @@ function setupRealm(realm, realmId) { export default class Realm { constructor(config) { let schemas = typeof config == 'object' && config.schema; - let constructors = {}; + let constructors = schemas ? {} : null; for (let i = 0, len = schemas ? schemas.length : 0; i < len; i++) { let item = schemas[i]; @@ -83,14 +83,15 @@ export default class Realm { } let realmId = rpc.createRealm(Array.from(arguments)); - - registerConstructors(realmId, constructors); setupRealm(this, realmId); + + // This will create mappings between the id, path, and potential constructors. + objects.registerConstructors(realmId, this.path, constructors); } create(type, ...args) { if (typeof type == 'function') { - type = typeForConstructor(this[keys.realm], type); + type = objects.typeForConstructor(this[keys.realm], type); } let method = util.createMethod(objectTypes.REALM, 'create', true); @@ -99,7 +100,7 @@ export default class Realm { objects(type, ...args) { if (typeof type == 'function') { - type = typeForConstructor(this[keys.realm], type); + type = objects.typeForConstructor(this[keys.realm], type); } let method = util.createMethod(objectTypes.REALM, 'objects'); @@ -152,6 +153,7 @@ Object.defineProperties(Realm, { clearTestState: { value: function() { collections.clearMutationListeners(); + objects.clearRegisteredConstructors(); rpc.clearTestState(); }, }, diff --git a/lib/browser/objects.js b/lib/browser/objects.js index 30ed84c2..908c1e15 100644 --- a/lib/browser/objects.js +++ b/lib/browser/objects.js @@ -21,7 +21,8 @@ import { keys, objectTypes } from './constants'; import { getterForProperty, setterForProperty, createMethods } from './util'; -const registeredConstructors = {}; +let registeredConstructors = {}; +let registeredRealmPaths = {}; export default class RealmObject { } @@ -31,9 +32,15 @@ createMethods(RealmObject.prototype, objectTypes.OBJECT, [ 'isValid', ]); +export function clearRegisteredConstructors() { + registeredConstructors = {}; + registeredRealmPaths = {}; +} + export function createObject(realmId, info) { let schema = info.schema; - let constructor = (registeredConstructors[realmId] || {})[schema.name]; + let realmPath = registeredRealmPaths[realmId]; + let constructor = (registeredConstructors[realmPath] || {})[schema.name]; let object = Object.create(constructor ? constructor.prototype : RealmObject.prototype); object[keys.realm] = realmId; @@ -58,12 +65,17 @@ export function createObject(realmId, info) { return object; } -export function registerConstructors(realmId, constructors) { - registeredConstructors[realmId] = constructors; +export function registerConstructors(realmId, realmPath, constructors) { + registeredRealmPaths[realmId] = realmPath; + + if (constructors) { + registeredConstructors[realmPath] = constructors; + } } export function typeForConstructor(realmId, constructor) { - let constructors = registeredConstructors[realmId]; + let realmPath = registeredRealmPaths[realmId]; + let constructors = registeredConstructors[realmPath]; for (let name in constructors) { if (constructors[name] == constructor) { diff --git a/package.json b/package.json index c2910c13..f4911715 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "dependencies": { "bindings": "^1.2.1", "mockery": "^1.6.2", - "nan": "^2.2.1", + "nan": "^2.3.3", "node-gyp": "^3.3.1", "rnpm": "1.6.5", "xcode": "0.8.4" diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 04935ed8..ebc7ca5e 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -37,6 +37,9 @@ namespace realm { namespace js { +template +class Realm; + template struct RealmClass; @@ -60,7 +63,7 @@ class RealmDelegate : public BindingContext { } virtual void will_change(std::vector const& observers, std::vector const& invalidated) {} - RealmDelegate(std::weak_ptr realm, GlobalContextType ctx) : m_context(ctx), m_realm(realm) {} + RealmDelegate(std::weak_ptr realm, GlobalContextType ctx) : m_context(ctx), m_realm(realm) {} ~RealmDelegate() { // All protected values need to be unprotected while the context is retained. @@ -95,7 +98,7 @@ class RealmDelegate : public BindingContext { private: Protected m_context; std::list> m_notifications; - std::weak_ptr m_realm; + std::weak_ptr m_realm; void notify(const char *notification_name) { SharedRealm realm = m_realm.lock(); @@ -112,6 +115,8 @@ class RealmDelegate : public BindingContext { Function::call(m_context, callback, realm_object, 2, arguments); } } + + friend class Realm; }; std::string default_path(); @@ -120,6 +125,7 @@ void delete_all_realms(); template class Realm { + using GlobalContextType = typename T::GlobalContext; using ContextType = typename T::Context; using FunctionType = typename T::Function; using ObjectType = typename T::Object; @@ -253,6 +259,7 @@ void Realm::constructor(ContextType ctx, ObjectType this_object, size_t argc, realm::Realm::Config config; typename Schema::ObjectDefaultsMap defaults; typename Schema::ConstructorMap constructors; + bool schema_updated = false; if (argc == 0) { config.path = default_path(); @@ -283,6 +290,7 @@ void Realm::constructor(ContextType ctx, ObjectType this_object, size_t argc, if (!Value::is_undefined(ctx, schema_value)) { ObjectType schema_object = Value::validated_to_object(ctx, schema_value, "schema"); config.schema.reset(new realm::Schema(Schema::parse_schema(ctx, schema_object, defaults, constructors))); + schema_updated = true; } static const String schema_version_string = "schemaVersion"; @@ -321,7 +329,6 @@ void Realm::constructor(ContextType ctx, ObjectType this_object, size_t argc, realm_ptr->reset(); }; } - static const String encryption_key_string = "encryptionKey"; ValueType encryption_key_value = Object::get_property(ctx, object, encryption_key_string); @@ -339,6 +346,23 @@ void Realm::constructor(ContextType ctx, ObjectType this_object, size_t argc, ensure_directory_exists_for_file(config.path); SharedRealm realm = realm::Realm::get_shared_realm(config); + GlobalContextType global_context = Context::get_global_context(ctx); + BindingContext *binding_context = realm->m_binding_context.get(); + RealmDelegate *js_binding_context = dynamic_cast *>(binding_context); + + if (!binding_context) { + js_binding_context = new RealmDelegate(realm, global_context); + realm->m_binding_context.reset(js_binding_context); + } + else if (!js_binding_context || js_binding_context->m_context != global_context) { + throw std::runtime_error("Realm is already open in another context on this thread: " + config.path); + } + + // If a new schema was provided, then use its defaults and constructors. + if (schema_updated) { + js_binding_context->m_defaults = std::move(defaults); + js_binding_context->m_constructors = std::move(constructors); + } // Fix for datetime -> timestamp conversion if (realm->config().upgrade_initial_version != realm->config().upgrade_final_version && @@ -363,11 +387,6 @@ void Realm::constructor(ContextType ctx, ObjectType this_object, size_t argc, } } } - - auto delegate = new RealmDelegate(realm, Context::get_global_context(ctx)); - delegate->m_defaults = std::move(defaults); - delegate->m_constructors = std::move(constructors); - realm->m_binding_context.reset(delegate); set_internal>(this_object, new SharedRealm(realm)); } diff --git a/src/node/node_class.hpp b/src/node/node_class.hpp index 757e6228..ea95cf98 100644 --- a/src/node/node_class.hpp +++ b/src/node/node_class.hpp @@ -104,7 +104,7 @@ class ObjectWrap { using Internal = void; static v8::Local get_template() { - return v8::Local();; + return v8::Local(); } }; @@ -195,22 +195,21 @@ inline v8::Local ObjectWrap::create_template() template inline void ObjectWrap::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(); + v8::Local fn_tpl = Nan::New(callback, v8::Local(), signature); v8::Local fn_name = Nan::New(name).ToLocalChecked(); // The reason we use this rather than Nan::SetPrototypeMethod is DontEnum. - tpl->PrototypeTemplate()->Set(fn_name, fn, v8::PropertyAttribute::DontEnum); - fn->SetName(fn_name); + tpl->PrototypeTemplate()->Set(fn_name, fn_tpl, v8::PropertyAttribute::DontEnum); + fn_tpl->SetClassName(fn_name); } template inline void ObjectWrap::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_tpl = Nan::New(callback); v8::Local fn_name = Nan::New(name).ToLocalChecked(); - tpl->Set(fn_name, fn, v8::PropertyAttribute::DontEnum); - fn->SetName(fn_name); + tpl->Set(fn_name, fn_tpl, v8::PropertyAttribute::DontEnum); + fn_tpl->SetClassName(fn_name); } template diff --git a/tests/js/asserts.js b/tests/js/asserts.js index 75ec6c31..7031717b 100644 --- a/tests/js/asserts.js +++ b/tests/js/asserts.js @@ -109,6 +109,14 @@ module.exports = { throw new TestFailureError(errorMessage || 'Condition expected to be true'); } }, + + isNode: function() { + return typeof process == 'object' && Object.prototype.toString.call(process) == '[object process]'; + }, + + isNode6: function() { + return this.isNode() && process.version.indexOf('v6.') == 0; + }, }; function TestFailureError(message) { diff --git a/tests/js/list-tests.js b/tests/js/list-tests.js index cb316b31..4563528d 100644 --- a/tests/js/list-tests.js +++ b/tests/js/list-tests.js @@ -554,7 +554,11 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(list.slice(1, 3).length, 2); TestCase.assertEqual(list.slice(1, 3)[1].age, 12); - TestCase.assertEqual(list.join(' '), 'Ari Tim Bjarne'); + // A Node 6 regression in v8 causes an error when converting our objects to strings: + // TypeError: Cannot convert a Symbol value to a string + if (!TestCase.isNode6()) { + TestCase.assertEqual(list.join(' '), 'Ari Tim Bjarne'); + } var count = 0; list.forEach(function(p, i) { diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index 35294bd4..79c018d6 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -46,12 +46,10 @@ module.exports = BaseTest.extend({ var defaultDir = Realm.defaultPath.substring(0, Realm.defaultPath.lastIndexOf("/") + 1) var testPath = 'test1.realm'; var realm = new Realm({schema: [], path: testPath}); - //TestCase.assertTrue(realm instanceof Realm); TestCase.assertEqual(realm.path, defaultDir + testPath); var testPath2 = 'test2.realm'; var realm2 = new Realm({schema: [], path: testPath2}); - //TestCase.assertTrue(realm2 instanceof Realm); TestCase.assertEqual(realm2.path, defaultDir + testPath2); }, @@ -68,10 +66,9 @@ module.exports = BaseTest.extend({ var realm = new Realm({path: 'test1.realm', schema: [], schemaVersion: 1}); TestCase.assertEqual(realm.schemaVersion, 1); - // FIXME - enable once Realm exposes a schema object - //TestCase.assertEqual(realm.schema.length, 0); - + TestCase.assertEqual(realm.schema.length, 0); realm.close(); + // FIXME - enable once realm initialization supports schema comparison // TestCase.assertThrows(function() { // realm = new Realm({path: testPath, schema: [schemas.TestObject], schemaVersion: 1}); @@ -81,7 +78,9 @@ module.exports = BaseTest.extend({ realm.write(function() { realm.create('TestObject', {doubleCol: 1}); }); - TestCase.assertEqual(realm.objects('TestObject')[0].doubleCol, 1) + TestCase.assertEqual(realm.objects('TestObject')[0].doubleCol, 1); + TestCase.assertEqual(realm.schemaVersion, 2); + TestCase.assertEqual(realm.schema.length, 1); }, testRealmConstructorDynamicSchema: function() { @@ -403,7 +402,7 @@ module.exports = BaseTest.extend({ testRealmCreateWithDefaults: function() { var realm = new Realm({schema: [schemas.DefaultValues, schemas.TestObject]}); - realm.write(function() { + var createAndTestObject = function() { var obj = realm.create('DefaultValuesObject', {}); var properties = schemas.DefaultValues.properties; @@ -418,7 +417,36 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(obj.nullObjectCol, null); TestCase.assertEqual(obj.arrayCol.length, properties.arrayCol.default.length); TestCase.assertEqual(obj.arrayCol[0].doubleCol, properties.arrayCol.default[0].doubleCol); - }); + }; + + realm.write(createAndTestObject); + + // Defaults should still work when creating another Realm instance. + realm = new Realm(); + realm.write(createAndTestObject); + }, + + testRealmCreateWithChangingDefaults: function() { + var objectSchema = { + name: 'IntObject', + properties: { + intCol: {type: 'int', default: 1}, + } + }; + + var realm = new Realm({schema: [objectSchema]}); + + var createAndTestObject = function() { + var object = realm.create('IntObject', {}); + TestCase.assertEqual(object.intCol, objectSchema.properties.intCol.default); + }; + + realm.write(createAndTestObject); + + objectSchema.properties.intCol.default++; + + realm = new Realm({schema: [objectSchema]}); + realm.write(createAndTestObject); }, testRealmCreateWithConstructor: function() { @@ -426,7 +454,6 @@ module.exports = BaseTest.extend({ function CustomObject() { customCreated++; - this.intCol *= 100; } CustomObject.schema = { name: 'CustomObject', @@ -447,7 +474,7 @@ module.exports = BaseTest.extend({ properties: { intCol: 'int' } - } + }; var realm = new Realm({schema: [CustomObject, InvalidObject]}); @@ -457,15 +484,11 @@ module.exports = BaseTest.extend({ TestCase.assertTrue(Object.getPrototypeOf(object) == CustomObject.prototype); TestCase.assertEqual(customCreated, 1); - // Should have been multiplied by 100 in the constructor. - TestCase.assertEqual(object.intCol, 100); - // Should be able to create object by passing in constructor. object = realm.create(CustomObject, {intCol: 2}); TestCase.assertTrue(object instanceof CustomObject); TestCase.assertTrue(Object.getPrototypeOf(object) == CustomObject.prototype); TestCase.assertEqual(customCreated, 2); - TestCase.assertEqual(object.intCol, 200); }); TestCase.assertThrows(function() { @@ -483,6 +506,36 @@ module.exports = BaseTest.extend({ realm.create(InvalidCustomObject, {intCol: 1}); }); }); + + // The constructor should still work when creating another Realm instance. + realm = new Realm(); + TestCase.assertTrue(realm.objects('CustomObject')[0] instanceof CustomObject); + TestCase.assertTrue(realm.objects(CustomObject).length > 0); + }, + + testRealmCreateWithChangingConstructor: function() { + function CustomObject() {} + CustomObject.schema = { + name: 'CustomObject', + properties: { + intCol: 'int' + } + }; + + var realm = new Realm({schema: [CustomObject]}); + realm.write(function() { + var object = realm.create('CustomObject', {intCol: 1}); + TestCase.assertTrue(object instanceof CustomObject); + }); + + function NewCustomObject() {} + NewCustomObject.schema = CustomObject.schema; + + realm = new Realm({schema: [NewCustomObject]}); + realm.write(function() { + var object = realm.create('CustomObject', {intCol: 1}); + TestCase.assertTrue(object instanceof NewCustomObject); + }); }, testRealmDelete: function() {