diff --git a/lib/browser/index.js b/lib/browser/index.js index 03a809af..1830df93 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -169,9 +169,6 @@ Object.defineProperties(Realm, { Results: { value: Results, }, - Types: { - value: Object.freeze(propTypes), - }, defaultPath: { get: util.getterForProperty('defaultPath'), set: util.setterForProperty('defaultPath'), diff --git a/lib/index.js b/lib/index.js index 9ac4465f..fb4c7128 100644 --- a/lib/index.js +++ b/lib/index.js @@ -24,9 +24,15 @@ 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]') { + // Prevent React Native packager from seeing this module. + var bindings = 'bindings'; + realmConstructor = require(bindings)('realm').Realm; } else { throw new Error('Missing Realm constructor - please ensure RealmReact framework is included!'); } @@ -34,4 +40,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 489f59d0..d29faf42 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,10 @@ "prepublish": "scripts/prepublish.sh" }, "dependencies": { + "bindings": "^1.2.1", + "mockery": "^1.6.2", + "nan": "^2.2.1", + "node-gyp": "^3.3.1", "rnpm": "1.5.2", "xcode": "0.8.4" }, diff --git a/react-native/android/src/main/jni/Android.mk b/react-native/android/src/main/jni/Android.mk index 7d90ceb6..c3d61dd1 100644 --- a/react-native/android/src/main/jni/Android.mk +++ b/react-native/android/src/main/jni/Android.mk @@ -15,15 +15,9 @@ include $(CLEAR_VARS) LOCAL_MODULE := librealmreact LOCAL_SRC_FILES := \ - src/js_collection.cpp \ - src/js_list.cpp \ - src/js_results.cpp \ - src/js_init.cpp \ src/js_realm.cpp \ - src/js_util.cpp \ - src/js_object.cpp \ - src/js_schema.cpp \ src/rpc.cpp \ + src/jsc/jsc_init.cpp \ src/android/platform.cpp \ src/android/io_realm_react_RealmReactModule.cpp \ src/android/jsc_override.cpp \ @@ -43,6 +37,7 @@ LOCAL_SRC_FILES := \ vendor/base64.cpp LOCAL_C_INCLUDES := src +LOCAL_C_INCLUDES += src/jsc LOCAL_C_INCLUDES += src/object-store/src LOCAL_C_INCLUDES += src/object-store/src/impl LOCAL_C_INCLUDES += src/object-store/src/parser diff --git a/react-native/ios/RealmReact/RealmReact.mm b/react-native/ios/RealmReact/RealmReact.mm index e07c5208..b5b850b6 100644 --- a/react-native/ios/RealmReact/RealmReact.mm +++ b/react-native/ios/RealmReact/RealmReact.mm @@ -20,7 +20,7 @@ #import "RealmAnalytics.h" #import "RCTBridge.h" -#import "js_init.h" +#import "jsc_init.h" #import "shared_realm.hpp" #import "realm_coordinator.hpp" @@ -37,6 +37,8 @@ #import "rpc.hpp" #define WEB_SERVER_PORT 8082 + +using namespace realm::rpc; #endif @interface NSObject () @@ -82,7 +84,7 @@ extern "C" JSGlobalContextRef RealmReactGetJSGlobalContextForExecutor(id executo #if DEBUG GCDWebServer *_webServer; - std::unique_ptr _rpcServer; + std::unique_ptr _rpcServer; #endif } @@ -204,7 +206,7 @@ RCT_REMAP_METHOD(emit, emitEvent:(NSString *)eventName withObject:(id)object) { - (void)startRPC { [GCDWebServer setLogLevel:3]; _webServer = [[GCDWebServer alloc] init]; - _rpcServer = std::make_unique(); + _rpcServer = std::make_unique(); __weak __typeof__(self) weakSelf = self; // Add a handler to respond to POST requests on any URL @@ -219,7 +221,7 @@ RCT_REMAP_METHOD(emit, emitEvent:(NSString *)eventName withObject:(id)object) { RealmReact *self = weakSelf; if (self) { if (_rpcServer) { - realm_js::json args = realm_js::json::parse([[(GCDWebServerDataRequest *)request text] UTF8String]); + json args = json::parse([[(GCDWebServerDataRequest *)request text] UTF8String]); std::string responseText = _rpcServer->perform_request(request.path.UTF8String, args).dump(); responseData = [NSData dataWithBytes:responseText.c_str() length:responseText.length()]; return; diff --git a/scripts/test.sh b/scripts/test.sh index 5aac53d6..e80e8e73 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -12,6 +12,9 @@ SRCROOT=$(cd "$(dirname "$0")/.." && pwd) # Start current working directory at the root of the project. cd "$SRCROOT" +# Add node_modules to PATH just in case we weren't called from `npm test` +PATH="$PWD/node_modules/.bin:$PATH" + if [[ $TARGET = *-android ]]; then # Inform the prepublish script to build Android modules. export REALM_BUILD_ANDROID=1 @@ -150,11 +153,33 @@ case "$TARGET" in echo "********* File location: $(pwd)/tests.xml *********"; cat tests.xml ;; +"node") + npm install + scripts/download-core.sh + + pushd src/node + node-gyp configure + + # Being explicit about debug mode rather than relying on defaults. + if [[ $CONFIGURATION == 'Debug' ]]; then + node-gyp build --debug + else + node-gyp build --no-debug + fi + + popd + + # Link to the appropriate module in the build directory. + mkdir -p build + ln -fs "../src/node/build/$CONFIGURATION/realm.node" build + + node tests + ;; "object-store") pushd src/object-store cmake -DCMAKE_BUILD_TYPE=$CONFIGURATION . make run-tests -;; + ;; *) echo "Invalid target '${TARGET}'" exit 1 diff --git a/src/android/io_realm_react_RealmReactModule.cpp b/src/android/io_realm_react_RealmReactModule.cpp index 864b7390..f94761f7 100644 --- a/src/android/io_realm_react_RealmReactModule.cpp +++ b/src/android/io_realm_react_RealmReactModule.cpp @@ -23,7 +23,9 @@ #include "rpc.hpp" #include "platform.hpp" -static realm_js::RPCServer *s_rpc_server; +using namespace realm::rpc; + +static RPCServer *s_rpc_server; extern bool realmContextInjected; JNIEXPORT void JNICALL Java_io_realm_react_RealmReactModule_setDefaultRealmFileDirectory @@ -46,7 +48,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactModule_setupChromeDebugMod if (s_rpc_server) { delete s_rpc_server; } - s_rpc_server = new realm_js::RPCServer(); + s_rpc_server = new RPCServer(); return (jlong)s_rpc_server; } @@ -55,8 +57,8 @@ JNIEXPORT jstring JNICALL Java_io_realm_react_RealmReactModule_processChromeDebu { const char* cmd = env->GetStringUTFChars(chrome_cmd, NULL); const char* args = env->GetStringUTFChars(chrome_args, NULL); - realm_js::json json = realm_js::json::parse(args); - realm_js::json response = s_rpc_server->perform_request(cmd, json); + json parsed_args = json::parse(args); + json response = s_rpc_server->perform_request(cmd, parsed_args); env->ReleaseStringUTFChars(chrome_cmd, cmd); env->ReleaseStringUTFChars(chrome_args, args); return env->NewStringUTF(response.dump().c_str()); diff --git a/src/android/jsc_override.cpp b/src/android/jsc_override.cpp index 8b49ef25..4ec0399e 100644 --- a/src/android/jsc_override.cpp +++ b/src/android/jsc_override.cpp @@ -23,7 +23,7 @@ #include #include -#include "js_init.h" +#include "jsc_init.h" #include "shared_realm.hpp" #include "realm_coordinator.hpp" diff --git a/src/android/platform.cpp b/src/android/platform.cpp index e4d29fa9..1ce4a029 100644 --- a/src/android/platform.cpp +++ b/src/android/platform.cpp @@ -16,11 +16,11 @@ // //////////////////////////////////////////////////////////////////////////// -#include "../platform.hpp" - #include "../js_init.h" #include #include +#include "../platform.hpp" + std::string s_default_realm_directory; namespace realm { diff --git a/src/ios/RealmJS.h b/src/ios/RealmJS.h index fd67f48c..1649a416 100644 --- a/src/ios/RealmJS.h +++ b/src/ios/RealmJS.h @@ -19,6 +19,6 @@ #ifndef REALM_JS_H #define REALM_JS_H -#include +#include #endif /* REALM_JS_H */ diff --git a/src/ios/RealmJS.xcodeproj/project.pbxproj b/src/ios/RealmJS.xcodeproj/project.pbxproj index 129bea40..69bcd48d 100644 --- a/src/ios/RealmJS.xcodeproj/project.pbxproj +++ b/src/ios/RealmJS.xcodeproj/project.pbxproj @@ -10,13 +10,12 @@ 02409DC21BCF11D6005F3B3E /* RealmJSCoreTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 02409DC11BCF11D6005F3B3E /* RealmJSCoreTests.m */; }; 0270BC821B7D020100010E03 /* RealmJSTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0270BC7B1B7D020100010E03 /* RealmJSTests.mm */; }; 0270BC871B7D023200010E03 /* RealmJS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CB11AE99CEC009B348C /* RealmJS.framework */; }; - 029048121C0428DF00ABDED4 /* js_init.h in Headers */ = {isa = PBXBuildFile; fileRef = 029048021C0428DF00ABDED4 /* js_init.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 029048121C0428DF00ABDED4 /* jsc_init.h in Headers */ = {isa = PBXBuildFile; fileRef = 029048021C0428DF00ABDED4 /* jsc_init.h */; settings = {ATTRIBUTES = (Public, ); }; }; 029048141C0428DF00ABDED4 /* js_list.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 029048041C0428DF00ABDED4 /* js_list.hpp */; }; - 029048161C0428DF00ABDED4 /* js_object.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 029048061C0428DF00ABDED4 /* js_object.hpp */; }; + 029048161C0428DF00ABDED4 /* js_realm_object.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 029048061C0428DF00ABDED4 /* js_realm_object.hpp */; }; 029048181C0428DF00ABDED4 /* js_realm.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 029048081C0428DF00ABDED4 /* js_realm.hpp */; }; 0290481A1C0428DF00ABDED4 /* js_results.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0290480A1C0428DF00ABDED4 /* js_results.hpp */; }; 0290481C1C0428DF00ABDED4 /* js_schema.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0290480C1C0428DF00ABDED4 /* js_schema.hpp */; }; - 0290481E1C0428DF00ABDED4 /* js_util.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0290480E1C0428DF00ABDED4 /* js_util.hpp */; }; 029048201C0428DF00ABDED4 /* rpc.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 029048101C0428DF00ABDED4 /* rpc.hpp */; settings = {ATTRIBUTES = (Public, ); }; }; 029048371C042A3C00ABDED4 /* platform.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 029048351C042A3C00ABDED4 /* platform.hpp */; }; 0290483B1C042EE200ABDED4 /* RealmJS.h in Headers */ = {isa = PBXBuildFile; fileRef = 0290483A1C042EE200ABDED4 /* RealmJS.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -36,14 +35,29 @@ 02F59EE11C88F2BB007F774C /* async_query.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59ED61C88F2BA007F774C /* async_query.cpp */; }; 02F59EE21C88F2BB007F774C /* realm_coordinator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EDB1C88F2BA007F774C /* realm_coordinator.cpp */; }; 02F59EE31C88F2BB007F774C /* transact_log_handler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EDD1C88F2BB007F774C /* transact_log_handler.cpp */; }; + F60102D31CBB966E00EC01BA /* js_realm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029048071C0428DF00ABDED4 /* js_realm.cpp */; }; + F60102D41CBB96AB00EC01BA /* index_set.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EAF1C88F17D007F774C /* index_set.cpp */; }; + F60102D51CBB96AE00EC01BA /* list.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EB11C88F17D007F774C /* list.cpp */; }; + F60102D61CBB96B400EC01BA /* object_schema.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EB41C88F17D007F774C /* object_schema.cpp */; }; + F60102D71CBB96B800EC01BA /* object_store.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EB61C88F17D007F774C /* object_store.cpp */; }; + F60102D81CBB96BD00EC01BA /* results.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EB91C88F17D007F774C /* results.cpp */; }; + F60102D91CBB96C100EC01BA /* schema.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EBB1C88F17D007F774C /* schema.cpp */; }; + F60102DA1CBB96C300EC01BA /* shared_realm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EBD1C88F17D007F774C /* shared_realm.cpp */; }; + F60102DB1CBB96C600EC01BA /* parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EC61C88F190007F774C /* parser.cpp */; }; + F60102DC1CBB96C900EC01BA /* query_builder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EC81C88F190007F774C /* query_builder.cpp */; }; + F60102DD1CBB96CC00EC01BA /* external_commit_helper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59ECF1C88F1B6007F774C /* external_commit_helper.cpp */; }; + F60102DE1CBB96CF00EC01BA /* weak_realm_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59ED11C88F1B6007F774C /* weak_realm_notifier.cpp */; }; + 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 */; }; + F60102E51CBBB19700EC01BA /* node_object_accessor.hpp in Headers */ = {isa = PBXBuildFile; fileRef = F60102E31CBBB19700EC01BA /* node_object_accessor.hpp */; }; + F60102E91CBCAEC500EC01BA /* platform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 029048381C042A8F00ABDED4 /* platform.mm */; }; + F60102EA1CBCAFC300EC01BA /* node_dummy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F6267BCA1CADC49200AC36B1 /* node_dummy.cpp */; }; F61378791C18EAC5008BFC51 /* js in Resources */ = {isa = PBXBuildFile; fileRef = F61378781C18EAAC008BFC51 /* js */; }; - F63FF2C61C12469E00B3B8E0 /* js_init.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029048011C0428DF00ABDED4 /* js_init.cpp */; }; - F63FF2C71C12469E00B3B8E0 /* js_list.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029048031C0428DF00ABDED4 /* js_list.cpp */; }; - F63FF2C81C12469E00B3B8E0 /* js_object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029048051C0428DF00ABDED4 /* js_object.cpp */; }; + F620F0581CB766DA0082977B /* node_init.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F620F0571CB766DA0082977B /* node_init.cpp */; }; + F620F0751CB9F60C0082977B /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F620F0741CB9F60C0082977B /* CoreFoundation.framework */; }; + F63FF2C61C12469E00B3B8E0 /* jsc_init.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029048011C0428DF00ABDED4 /* jsc_init.cpp */; }; F63FF2C91C12469E00B3B8E0 /* js_realm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029048071C0428DF00ABDED4 /* js_realm.cpp */; }; - F63FF2CA1C12469E00B3B8E0 /* js_results.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029048091C0428DF00ABDED4 /* js_results.cpp */; }; - F63FF2CB1C12469E00B3B8E0 /* js_schema.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0290480B1C0428DF00ABDED4 /* js_schema.cpp */; }; - F63FF2CC1C12469E00B3B8E0 /* js_util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0290480D1C0428DF00ABDED4 /* js_util.cpp */; }; F63FF2CD1C12469E00B3B8E0 /* rpc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0290480F1C0428DF00ABDED4 /* rpc.cpp */; }; F63FF2DE1C1565EA00B3B8E0 /* RealmJS.mm in Sources */ = {isa = PBXBuildFile; fileRef = F63FF2DC1C15659A00B3B8E0 /* RealmJS.mm */; }; F63FF2E11C1591EC00B3B8E0 /* libRealmJS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F63FF2B11C1241E500B3B8E0 /* libRealmJS.a */; }; @@ -65,8 +79,6 @@ F68A278C1BC2722A0063D40A /* RJSModuleLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = F68A278B1BC2722A0063D40A /* RJSModuleLoader.m */; }; F6BB7DF21BF681BC00D0A69E /* base64.hpp in Headers */ = {isa = PBXBuildFile; fileRef = F6BB7DF01BF681BC00D0A69E /* base64.hpp */; }; F6BCCFE21C8380A400FE31AE /* lib in Resources */ = {isa = PBXBuildFile; fileRef = F6BCCFDF1C83809A00FE31AE /* lib */; }; - F6CB31001C8EDDAB0070EF3F /* js_collection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F6CB30FC1C8EDD760070EF3F /* js_collection.cpp */; }; - F6CB31011C8EDDBB0070EF3F /* js_collection.hpp in Headers */ = {isa = PBXBuildFile; fileRef = F6CB30FD1C8EDD760070EF3F /* js_collection.hpp */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -100,24 +112,19 @@ /* Begin PBXFileReference section */ 02409DC11BCF11D6005F3B3E /* RealmJSCoreTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RealmJSCoreTests.m; path = ios/RealmJSCoreTests.m; sourceTree = ""; }; + 025678951CAB392000FB8501 /* jsc_types.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = jsc_types.hpp; sourceTree = ""; }; 0270BC5A1B7CFC1300010E03 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0270BC781B7D020100010E03 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ios/Info.plist; sourceTree = ""; }; 0270BC7A1B7D020100010E03 /* RealmJSTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RealmJSTests.h; path = ios/RealmJSTests.h; sourceTree = ""; }; 0270BC7B1B7D020100010E03 /* RealmJSTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RealmJSTests.mm; path = ios/RealmJSTests.mm; sourceTree = ""; }; - 029048011C0428DF00ABDED4 /* js_init.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = js_init.cpp; sourceTree = ""; }; - 029048021C0428DF00ABDED4 /* js_init.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = js_init.h; sourceTree = ""; }; - 029048031C0428DF00ABDED4 /* js_list.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = js_list.cpp; sourceTree = ""; }; + 029048011C0428DF00ABDED4 /* jsc_init.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = jsc_init.cpp; sourceTree = ""; }; + 029048021C0428DF00ABDED4 /* jsc_init.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jsc_init.h; sourceTree = ""; }; 029048041C0428DF00ABDED4 /* js_list.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = js_list.hpp; sourceTree = ""; }; - 029048051C0428DF00ABDED4 /* js_object.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = js_object.cpp; sourceTree = ""; }; - 029048061C0428DF00ABDED4 /* js_object.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = js_object.hpp; sourceTree = ""; }; + 029048061C0428DF00ABDED4 /* js_realm_object.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = js_realm_object.hpp; sourceTree = ""; }; 029048071C0428DF00ABDED4 /* js_realm.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = js_realm.cpp; sourceTree = ""; }; 029048081C0428DF00ABDED4 /* js_realm.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = js_realm.hpp; sourceTree = ""; }; - 029048091C0428DF00ABDED4 /* js_results.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = js_results.cpp; sourceTree = ""; }; 0290480A1C0428DF00ABDED4 /* js_results.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = js_results.hpp; sourceTree = ""; }; - 0290480B1C0428DF00ABDED4 /* js_schema.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = js_schema.cpp; sourceTree = ""; }; 0290480C1C0428DF00ABDED4 /* js_schema.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = js_schema.hpp; sourceTree = ""; }; - 0290480D1C0428DF00ABDED4 /* js_util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = js_util.cpp; sourceTree = ""; }; - 0290480E1C0428DF00ABDED4 /* js_util.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = js_util.hpp; sourceTree = ""; }; 0290480F1C0428DF00ABDED4 /* rpc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = rpc.cpp; sourceTree = ""; }; 029048101C0428DF00ABDED4 /* rpc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = rpc.hpp; sourceTree = ""; }; 029048351C042A3C00ABDED4 /* platform.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = platform.hpp; sourceTree = ""; }; @@ -161,7 +168,37 @@ 02F59EDE1C88F2BB007F774C /* transact_log_handler.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = transact_log_handler.hpp; path = src/impl/transact_log_handler.hpp; sourceTree = ""; }; 02F59EDF1C88F2BB007F774C /* weak_realm_notifier_base.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = weak_realm_notifier_base.hpp; path = src/impl/weak_realm_notifier_base.hpp; sourceTree = ""; }; 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 = ""; }; + F60102E31CBBB19700EC01BA /* node_object_accessor.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = node_object_accessor.hpp; sourceTree = ""; }; + F60102E71CBBB36500EC01BA /* jsc_object_accessor.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = jsc_object_accessor.hpp; sourceTree = ""; }; + F60102F71CBDA6D400EC01BA /* js_collection.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = js_collection.hpp; sourceTree = ""; }; + F60103071CC4B3DF00EC01BA /* node_protected.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = node_protected.hpp; sourceTree = ""; }; + F60103081CC4B4F900EC01BA /* jsc_protected.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = jsc_protected.hpp; sourceTree = ""; }; + F60103091CC4B5E800EC01BA /* jsc_context.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = jsc_context.hpp; sourceTree = ""; }; + F601030A1CC4B64E00EC01BA /* node_context.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = node_context.hpp; sourceTree = ""; }; + F601030B1CC4B6C900EC01BA /* jsc_value.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = jsc_value.hpp; sourceTree = ""; }; + F601030C1CC4B72B00EC01BA /* node_value.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = node_value.hpp; sourceTree = ""; }; + F601030D1CC4B76F00EC01BA /* jsc_object.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = jsc_object.hpp; sourceTree = ""; }; + F601030E1CC4B7C900EC01BA /* node_object.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = node_object.hpp; sourceTree = ""; }; + F601030F1CC4B80800EC01BA /* jsc_function.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = jsc_function.hpp; sourceTree = ""; }; + F60103101CC4B86000EC01BA /* node_function.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = node_function.hpp; sourceTree = ""; }; + F60103111CC4BA6500EC01BA /* jsc_exception.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = jsc_exception.hpp; sourceTree = ""; }; + F60103121CC4CBF000EC01BA /* node_exception.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = node_exception.hpp; sourceTree = ""; }; + F60103131CC4CC4500EC01BA /* jsc_string.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = jsc_string.hpp; sourceTree = ""; }; + F60103141CC4CC8C00EC01BA /* jsc_return_value.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = jsc_return_value.hpp; sourceTree = ""; }; + F60103151CC4CCFD00EC01BA /* node_return_value.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = node_return_value.hpp; sourceTree = ""; }; + F60103161CC4CD2F00EC01BA /* node_string.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = node_string.hpp; sourceTree = ""; }; F61378781C18EAAC008BFC51 /* js */ = {isa = PBXFileReference; lastKnownFileType = folder; path = js; sourceTree = ""; }; + F620F0521CAF0B600082977B /* js_class.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = js_class.hpp; sourceTree = ""; }; + F620F0531CAF2EF70082977B /* jsc_class.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = jsc_class.hpp; sourceTree = ""; }; + F620F0551CB655A50082977B /* node_class.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = node_class.hpp; sourceTree = ""; }; + F620F0571CB766DA0082977B /* node_init.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = node_init.cpp; sourceTree = ""; }; + 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.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 = ""; }; F63FF2F01C16405C00B3B8E0 /* libGCDWebServers.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libGCDWebServers.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -198,14 +235,14 @@ F63FF32C1C16432E00B3B8E0 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; F63FF32E1C16433900B3B8E0 /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = usr/lib/libxml2.tbd; sourceTree = SDKROOT; }; F63FF3301C16434400B3B8E0 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + F6874A351CAC792D00EEEE36 /* node_types.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = node_types.hpp; sourceTree = ""; }; + F6874A3E1CACA5A900EEEE36 /* js_types.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = js_types.hpp; sourceTree = ""; }; F68A278A1BC2722A0063D40A /* RJSModuleLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RJSModuleLoader.h; path = ios/RJSModuleLoader.h; sourceTree = ""; }; F68A278B1BC2722A0063D40A /* RJSModuleLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RJSModuleLoader.m; path = ios/RJSModuleLoader.m; sourceTree = ""; }; F6BB7DEF1BF681BC00D0A69E /* base64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = base64.cpp; sourceTree = ""; }; F6BB7DF01BF681BC00D0A69E /* base64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = base64.hpp; sourceTree = ""; }; F6BCCFDF1C83809A00FE31AE /* lib */ = {isa = PBXFileReference; lastKnownFileType = folder; name = lib; path = ../../lib; sourceTree = SOURCE_ROOT; }; F6C3FBBC1BF680EC00E6FFD4 /* json.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = json.hpp; sourceTree = ""; }; - F6CB30FC1C8EDD760070EF3F /* js_collection.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = js_collection.cpp; sourceTree = ""; }; - F6CB30FD1C8EDD760070EF3F /* js_collection.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = js_collection.hpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -227,6 +264,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F62BF8F81CAC71780022BCDC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F620F0751CB9F60C0082977B /* CoreFoundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F63FF2ED1C16405C00B3B8E0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -242,23 +287,17 @@ children = ( F6BCCFDF1C83809A00FE31AE /* lib */, F62A35131C18E6E2004A917D /* iOS */, + F60103051CC4ADE500EC01BA /* JS */, + F6874A441CAD2ACD00EEEE36 /* JSC */, + F62BF9001CAC72C40022BCDC /* Node */, F62A35141C18E783004A917D /* Object Store */, - 029048011C0428DF00ABDED4 /* js_init.cpp */, - 029048021C0428DF00ABDED4 /* js_init.h */, - F6CB30FC1C8EDD760070EF3F /* js_collection.cpp */, - F6CB30FD1C8EDD760070EF3F /* js_collection.hpp */, - 029048031C0428DF00ABDED4 /* js_list.cpp */, + F60102F71CBDA6D400EC01BA /* js_collection.hpp */, 029048041C0428DF00ABDED4 /* js_list.hpp */, - 029048051C0428DF00ABDED4 /* js_object.cpp */, - 029048061C0428DF00ABDED4 /* js_object.hpp */, + 029048061C0428DF00ABDED4 /* js_realm_object.hpp */, 029048071C0428DF00ABDED4 /* js_realm.cpp */, 029048081C0428DF00ABDED4 /* js_realm.hpp */, - 029048091C0428DF00ABDED4 /* js_results.cpp */, 0290480A1C0428DF00ABDED4 /* js_results.hpp */, - 0290480B1C0428DF00ABDED4 /* js_schema.cpp */, 0290480C1C0428DF00ABDED4 /* js_schema.hpp */, - 0290480D1C0428DF00ABDED4 /* js_util.cpp */, - 0290480E1C0428DF00ABDED4 /* js_util.hpp */, 029048351C042A3C00ABDED4 /* platform.hpp */, 0290480F1C0428DF00ABDED4 /* rpc.cpp */, 029048101C0428DF00ABDED4 /* rpc.hpp */, @@ -285,6 +324,7 @@ 02B58CBC1AE99CEC009B348C /* RealmJSTests.xctest */, F63FF2B11C1241E500B3B8E0 /* libRealmJS.a */, F63FF2F01C16405C00B3B8E0 /* libGCDWebServers.a */, + F62BF8FB1CAC71780022BCDC /* libRealmNode.dylib */, ); name = Products; sourceTree = ""; @@ -307,6 +347,7 @@ 02B58CCF1AE99D8C009B348C /* Frameworks */ = { isa = PBXGroup; children = ( + F620F0741CB9F60C0082977B /* CoreFoundation.framework */, 02A3C7A41BC4341500B1A7BE /* libc++.tbd */, F63FF3301C16434400B3B8E0 /* libz.tbd */, F63FF32E1C16433900B3B8E0 /* libxml2.tbd */, @@ -318,6 +359,17 @@ name = Frameworks; sourceTree = ""; }; + F60103051CC4ADE500EC01BA /* JS */ = { + isa = PBXGroup; + children = ( + F620F0521CAF0B600082977B /* js_class.hpp */, + F6874A3E1CACA5A900EEEE36 /* js_types.hpp */, + F6267BC91CADC30000AC36B1 /* js_util.hpp */, + F620F0591CB7B4C80082977B /* js_object_accessor.hpp */, + ); + name = JS; + sourceTree = ""; + }; F62A35131C18E6E2004A917D /* iOS */ = { isa = PBXGroup; children = ( @@ -372,6 +424,28 @@ path = "object-store"; sourceTree = ""; }; + F62BF9001CAC72C40022BCDC /* Node */ = { + isa = PBXGroup; + children = ( + F6267BCA1CADC49200AC36B1 /* node_dummy.cpp */, + F60102CF1CBB814A00EC01BA /* node_init.hpp */, + F620F0571CB766DA0082977B /* node_init.cpp */, + F620F0551CB655A50082977B /* node_class.hpp */, + F6874A351CAC792D00EEEE36 /* node_types.hpp */, + F60103161CC4CD2F00EC01BA /* node_string.hpp */, + F601030A1CC4B64E00EC01BA /* node_context.hpp */, + F601030C1CC4B72B00EC01BA /* node_value.hpp */, + F601030E1CC4B7C900EC01BA /* node_object.hpp */, + F60103101CC4B86000EC01BA /* node_function.hpp */, + F60103121CC4CBF000EC01BA /* node_exception.hpp */, + F60103071CC4B3DF00EC01BA /* node_protected.hpp */, + F60103151CC4CCFD00EC01BA /* node_return_value.hpp */, + F60102E31CBBB19700EC01BA /* node_object_accessor.hpp */, + ); + name = Node; + path = node; + sourceTree = ""; + }; F63FF2FB1C1642BB00B3B8E0 /* GCDWebServer */ = { isa = PBXGroup; children = ( @@ -432,6 +506,28 @@ path = Responses; sourceTree = ""; }; + F6874A441CAD2ACD00EEEE36 /* JSC */ = { + isa = PBXGroup; + children = ( + 029048021C0428DF00ABDED4 /* jsc_init.h */, + F60102D11CBB865A00EC01BA /* jsc_init.hpp */, + 029048011C0428DF00ABDED4 /* jsc_init.cpp */, + F620F0531CAF2EF70082977B /* jsc_class.hpp */, + 025678951CAB392000FB8501 /* jsc_types.hpp */, + F60103131CC4CC4500EC01BA /* jsc_string.hpp */, + F60103091CC4B5E800EC01BA /* jsc_context.hpp */, + F601030B1CC4B6C900EC01BA /* jsc_value.hpp */, + F601030D1CC4B76F00EC01BA /* jsc_object.hpp */, + F601030F1CC4B80800EC01BA /* jsc_function.hpp */, + F60103111CC4BA6500EC01BA /* jsc_exception.hpp */, + F60103081CC4B4F900EC01BA /* jsc_protected.hpp */, + F60103141CC4CC8C00EC01BA /* jsc_return_value.hpp */, + F60102E71CBBB36500EC01BA /* jsc_object_accessor.hpp */, + ); + name = JSC; + path = jsc; + sourceTree = ""; + }; F6C3FBB41BF680D000E6FFD4 /* Vendor */ = { isa = PBXGroup; children = ( @@ -452,20 +548,26 @@ buildActionMask = 2147483647; files = ( 0290483B1C042EE200ABDED4 /* RealmJS.h in Headers */, - 029048121C0428DF00ABDED4 /* js_init.h in Headers */, + 029048121C0428DF00ABDED4 /* jsc_init.h in Headers */, 029048201C0428DF00ABDED4 /* rpc.hpp in Headers */, - 0290481E1C0428DF00ABDED4 /* js_util.hpp in Headers */, 0290481A1C0428DF00ABDED4 /* js_results.hpp in Headers */, 029048181C0428DF00ABDED4 /* js_realm.hpp in Headers */, 029048141C0428DF00ABDED4 /* js_list.hpp in Headers */, F6BB7DF21BF681BC00D0A69E /* base64.hpp in Headers */, - F6CB31011C8EDDBB0070EF3F /* js_collection.hpp in Headers */, 0290481C1C0428DF00ABDED4 /* js_schema.hpp in Headers */, - 029048161C0428DF00ABDED4 /* js_object.hpp in Headers */, + 029048161C0428DF00ABDED4 /* js_realm_object.hpp in Headers */, 029048371C042A3C00ABDED4 /* platform.hpp in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; + F62BF8F91CAC71780022BCDC /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + F60102E51CBBB19700EC01BA /* node_object_accessor.hpp in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ @@ -507,6 +609,23 @@ productReference = 02B58CBC1AE99CEC009B348C /* RealmJSTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + F62BF8FA1CAC71780022BCDC /* RealmNode */ = { + isa = PBXNativeTarget; + buildConfigurationList = F62BF8FF1CAC71780022BCDC /* Build configuration list for PBXNativeTarget "RealmNode" */; + buildPhases = ( + F62BF8F71CAC71780022BCDC /* Sources */, + F62BF8F81CAC71780022BCDC /* Frameworks */, + F62BF8F91CAC71780022BCDC /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RealmNode; + productName = RealmNode; + productReference = F62BF8FB1CAC71780022BCDC /* libRealmNode.dylib */; + productType = "com.apple.product-type.library.dynamic"; + }; F63FF2B01C1241E500B3B8E0 /* RealmJS static */ = { isa = PBXNativeTarget; buildConfigurationList = F63FF2C41C1241E500B3B8E0 /* Build configuration list for PBXNativeTarget "RealmJS static" */; @@ -555,6 +674,9 @@ 02B58CBB1AE99CEC009B348C = { CreatedOnToolsVersion = 6.3.1; }; + F62BF8FA1CAC71780022BCDC = { + CreatedOnToolsVersion = 7.3; + }; F63FF2B01C1241E500B3B8E0 = { CreatedOnToolsVersion = 7.1.1; }; @@ -577,6 +699,7 @@ targets = ( F63FF2EF1C16405C00B3B8E0 /* GCDWebServers */, F63FF2B01C1241E500B3B8E0 /* RealmJS static */, + F62BF8FA1CAC71780022BCDC /* RealmNode */, 02B58CB01AE99CEC009B348C /* RealmJS */, 02B58CBB1AE99CEC009B348C /* RealmJSTests */, ); @@ -615,7 +738,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "cp ../object-store/tests/query.json \"$TARGET_BUILD_DIR/$CONTENTS_FOLDER_PATH/js/query-tests.json\""; + shellScript = "DEST=\"$TARGET_BUILD_DIR/$CONTENTS_FOLDER_PATH/js/query-tests.json\"\nrm -f \"$DEST\"\ncp ../object-store/tests/query.json \"$DEST\""; }; F63FF2C51C12462600B3B8E0 /* Download Core */ = { isa = PBXShellScriptBuildPhase; @@ -644,28 +767,47 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F62BF8F71CAC71780022BCDC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F60102D31CBB966E00EC01BA /* js_realm.cpp in Sources */, + F60102D61CBB96B400EC01BA /* object_schema.cpp in Sources */, + F60102D41CBB96AB00EC01BA /* index_set.cpp in Sources */, + F60102DB1CBB96C600EC01BA /* parser.cpp in Sources */, + F60102D51CBB96AE00EC01BA /* list.cpp in Sources */, + F60102DC1CBB96C900EC01BA /* query_builder.cpp in Sources */, + F60102DD1CBB96CC00EC01BA /* external_commit_helper.cpp in Sources */, + F60102E11CBB96DD00EC01BA /* transact_log_handler.cpp in Sources */, + F60102D71CBB96B800EC01BA /* object_store.cpp in Sources */, + F60102DA1CBB96C300EC01BA /* shared_realm.cpp in Sources */, + F60102E01CBB96D900EC01BA /* realm_coordinator.cpp 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 */, + F60102D91CBB96C100EC01BA /* schema.cpp in Sources */, + F60102E91CBCAEC500EC01BA /* platform.mm in Sources */, + F60102DF1CBB96D300EC01BA /* async_query.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F63FF2AD1C1241E500B3B8E0 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F6CB31001C8EDDAB0070EF3F /* js_collection.cpp in Sources */, 02F59EE31C88F2BB007F774C /* transact_log_handler.cpp in Sources */, F63FF2E81C159C4B00B3B8E0 /* platform.mm in Sources */, 02F59EC31C88F17D007F774C /* results.cpp in Sources */, F63FF2E21C15921A00B3B8E0 /* base64.cpp in Sources */, - F63FF2C61C12469E00B3B8E0 /* js_init.cpp in Sources */, + F63FF2C61C12469E00B3B8E0 /* jsc_init.cpp in Sources */, 02F59ECA1C88F190007F774C /* parser.cpp in Sources */, - F63FF2C71C12469E00B3B8E0 /* js_list.cpp in Sources */, - F63FF2C81C12469E00B3B8E0 /* js_object.cpp in Sources */, 02F59EE11C88F2BB007F774C /* async_query.cpp in Sources */, 02F59EC01C88F17D007F774C /* list.cpp in Sources */, 02F59EBF1C88F17D007F774C /* index_set.cpp in Sources */, 02F59ED51C88F1B6007F774C /* weak_realm_notifier.cpp in Sources */, F63FF2C91C12469E00B3B8E0 /* js_realm.cpp in Sources */, - F63FF2CA1C12469E00B3B8E0 /* js_results.cpp in Sources */, 02F59EC51C88F17D007F774C /* shared_realm.cpp in Sources */, - F63FF2CB1C12469E00B3B8E0 /* js_schema.cpp in Sources */, - F63FF2CC1C12469E00B3B8E0 /* js_util.cpp in Sources */, 02F59ECB1C88F190007F774C /* query_builder.cpp in Sources */, 02F59EE21C88F2BB007F774C /* realm_coordinator.cpp in Sources */, 02F59EC41C88F17D007F774C /* schema.cpp in Sources */, @@ -759,6 +901,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/../object-store/external/pegtl", + "$(SRCROOT)/../object-store/src", + "$(SRCROOT)/../../vendor", + ); IPHONEOS_DEPLOYMENT_TARGET = 8.0; LIBRARY_SEARCH_PATHS = ../../core; MTL_ENABLE_DEBUG_INFO = YES; @@ -767,6 +914,7 @@ "$(OTHER_CFLAGS)", "-isystem", ../../core/include, + "-ftemplate-backtrace-limit=0", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -811,6 +959,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/../object-store/external/pegtl", + "$(SRCROOT)/../object-store/src", + "$(SRCROOT)/../../vendor", + ); IPHONEOS_DEPLOYMENT_TARGET = 8.0; LIBRARY_SEARCH_PATHS = ../../core; MTL_ENABLE_DEBUG_INFO = NO; @@ -818,6 +971,7 @@ "$(OTHER_CFLAGS)", "-isystem", ../../core/include, + "-ftemplate-backtrace-limit=0", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -946,6 +1100,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/../object-store/external/pegtl", + "$(SRCROOT)/../object-store/src", + "$(SRCROOT)/../../vendor", + ); IPHONEOS_DEPLOYMENT_TARGET = 8.0; LIBRARY_SEARCH_PATHS = ../../core; MTL_ENABLE_DEBUG_INFO = YES; @@ -954,6 +1113,7 @@ "$(OTHER_CFLAGS)", "-isystem", ../../core/include, + "-ftemplate-backtrace-limit=0", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1004,6 +1164,83 @@ }; name = GCov_Build; }; + F62BF8FC1CAC71780022BCDC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEBUG_INFORMATION_FORMAT = dwarf; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + EXECUTABLE_PREFIX = lib; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../../node_modules/nan", + /usr/local/include/node, + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + /usr/local/lib, + ); + OTHER_LDFLAGS = ( + "-lrealm", + "-lv8", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + }; + name = Debug; + }; + F62BF8FD1CAC71780022BCDC /* GCov_Build */ = { + isa = XCBuildConfiguration; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + EXECUTABLE_PREFIX = lib; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../../node_modules/nan", + /usr/local/include/node, + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + /usr/local/lib, + ); + OTHER_LDFLAGS = ( + "-lrealm", + "-lv8", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + }; + name = GCov_Build; + }; + F62BF8FE1CAC71780022BCDC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + EXECUTABLE_PREFIX = lib; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../../node_modules/nan", + /usr/local/include/node, + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + /usr/local/lib, + ); + OTHER_LDFLAGS = ( + "-lrealm", + "-lv8", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + }; + name = Release; + }; F63FF2B71C1241E500B3B8E0 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1012,12 +1249,6 @@ "DEBUG=1", "$(inherited)", ); - HEADER_SEARCH_PATHS = ( - "$(inherited)", - "$(SRCROOT)/../object-store/external/pegtl", - "$(SRCROOT)/../object-store/src", - "$(SRCROOT)/../../vendor", - ); PRODUCT_NAME = RealmJS; SKIP_INSTALL = YES; }; @@ -1026,12 +1257,6 @@ F63FF2B81C1241E500B3B8E0 /* GCov_Build */ = { isa = XCBuildConfiguration; buildSettings = { - HEADER_SEARCH_PATHS = ( - "$(inherited)", - "$(SRCROOT)/../object-store/external/pegtl", - "$(SRCROOT)/../object-store/src", - "$(SRCROOT)/../../vendor", - ); PRODUCT_NAME = RealmJS; SKIP_INSTALL = YES; }; @@ -1040,12 +1265,6 @@ F63FF2B91C1241E500B3B8E0 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - HEADER_SEARCH_PATHS = ( - "$(inherited)", - "$(SRCROOT)/../object-store/external/pegtl", - "$(SRCROOT)/../object-store/src", - "$(SRCROOT)/../../vendor", - ); PRODUCT_NAME = RealmJS; SKIP_INSTALL = YES; }; @@ -1059,6 +1278,7 @@ "DEBUG=1", "$(inherited)", ); + HEADER_SEARCH_PATHS = ""; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1068,6 +1288,7 @@ F63FF2F71C16405D00B3B8E0 /* GCov_Build */ = { isa = XCBuildConfiguration; buildSettings = { + HEADER_SEARCH_PATHS = ""; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1077,6 +1298,7 @@ F63FF2F81C16405D00B3B8E0 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + HEADER_SEARCH_PATHS = ""; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1116,6 +1338,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F62BF8FF1CAC71780022BCDC /* Build configuration list for PBXNativeTarget "RealmNode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F62BF8FC1CAC71780022BCDC /* Debug */, + F62BF8FD1CAC71780022BCDC /* GCov_Build */, + F62BF8FE1CAC71780022BCDC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F63FF2C41C1241E500B3B8E0 /* Build configuration list for PBXNativeTarget "RealmJS static" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/src/js_class.hpp b/src/js_class.hpp new file mode 100644 index 00000000..39603897 --- /dev/null +++ b/src/js_class.hpp @@ -0,0 +1,93 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 + +#include "js_types.hpp" + +namespace realm { +namespace js { + +template +using ConstructorType = void(typename T::Context, typename T::Object, size_t, const typename T::Value[]); + +template +using MethodType = void(typename T::Context, typename T::Object, size_t, const typename T::Value[], ReturnValue &); + +template +struct PropertyType { + using GetterType = void(typename T::Context, typename T::Object, ReturnValue &); + using SetterType = void(typename T::Context, typename T::Object, typename T::Value); + + typename T::PropertyGetterCallback getter; + typename T::PropertySetterCallback setter; +}; + +template +struct IndexPropertyType { + using GetterType = void(typename T::Context, typename T::Object, uint32_t, ReturnValue &); + using SetterType = bool(typename T::Context, typename T::Object, uint32_t, typename T::Value); + + typename T::IndexPropertyGetterCallback getter; + typename T::IndexPropertySetterCallback setter; +}; + +template +struct StringPropertyType { + using GetterType = void(typename T::Context, typename T::Object, const String &, ReturnValue &); + using SetterType = bool(typename T::Context, typename T::Object, const String &, typename T::Value); + using EnumeratorType = std::vector>(typename T::Context, typename T::Object); + + typename T::StringPropertyGetterCallback getter; + typename T::StringPropertySetterCallback setter; + typename T::StringPropertyEnumeratorCallback enumerator; +}; + +template +using MethodMap = std::map; + +template +using PropertyMap = std::map>; + +template +struct ClassDefinition { + using Internal = U; + using Parent = V; + + // Every subclass *must* at least have a name. + // std::string const name; + + // ClassDefinition specializations should inherit from this class and override what's needed below. + ConstructorType* const constructor = nullptr; + MethodMap const static_methods = {}; + PropertyMap const static_properties = {}; + MethodMap const methods = {}; + PropertyMap const properties = {}; + IndexPropertyType const index_accessor = {}; + StringPropertyType const string_accessor = {}; +}; + +template +class ObjectWrap; + +} // js +} // realm diff --git a/src/js_collection.hpp b/src/js_collection.hpp index ee21c391..510333a7 100644 --- a/src/js_collection.hpp +++ b/src/js_collection.hpp @@ -18,6 +18,18 @@ #pragma once -#include "js_util.hpp" +#include "js_class.hpp" -JSClassRef RJSCollectionClass(); +namespace realm { +namespace js { + +// Empty class that merely serves as useful type for now. +class Collection {}; + +template +struct CollectionClass : ClassDefinition { + std::string const name = "Collection"; +}; + +} // js +} // realm diff --git a/src/js_init.cpp b/src/js_init.cpp deleted file mode 100644 index 3c1623c4..00000000 --- a/src/js_init.cpp +++ /dev/null @@ -1,117 +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 "js_init.h" -#include "js_realm.hpp" -#include "js_object.hpp" -#include "js_collection.hpp" -#include "js_list.hpp" -#include "js_results.hpp" -#include "js_util.hpp" -#include "js_schema.hpp" -#include "platform.hpp" - -#include "shared_realm.hpp" -#include "impl/realm_coordinator.hpp" -#include -#include - -extern "C" { - -JSValueRef RJSTypeGet(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) { - std::string str = RJSStringForJSString(propertyName); - std::transform(str.begin(), str.end(), str.begin(), ::tolower); - return RJSValueForString(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); -} - -static JSObjectRef UncallableConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { - *exception = RJSMakeError(ctx, "Illegal constructor"); - return NULL; -} - -static JSValueRef ClearTestState(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { - RJSClearTestState(); - return NULL; -} - -JSObjectRef RJSConstructorCreate(JSContextRef ctx) { - static JSStringRef clearTestStateString = JSStringCreateWithUTF8CString("clearTestState"); - static JSStringRef collectionString = JSStringCreateWithUTF8CString("Collection"); - static JSStringRef listString = JSStringCreateWithUTF8CString("List"); - static JSStringRef resultsString = JSStringCreateWithUTF8CString("Results"); - static JSStringRef typeString = JSStringCreateWithUTF8CString("Types"); - - JSObjectRef realmObject = JSObjectMake(ctx, RJSRealmConstructorClass(), NULL); - JSPropertyAttributes attributes = kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete; - - JSObjectRef collectionConstructor = JSObjectMakeConstructor(ctx, RJSCollectionClass(), UncallableConstructor); - RJSValidatedSetProperty(ctx, realmObject, collectionString, collectionConstructor, attributes); - - JSObjectRef listConstructor = JSObjectMakeConstructor(ctx, RJSListClass(), UncallableConstructor); - RJSValidatedSetProperty(ctx, realmObject, listString, listConstructor, attributes); - - JSObjectRef resultsContructor = JSObjectMakeConstructor(ctx, RJSResultsClass(), UncallableConstructor); - RJSValidatedSetProperty(ctx, realmObject, resultsString, resultsContructor, attributes); - - JSObjectRef typesObject = JSObjectMake(ctx, RJSRealmTypeClass(), NULL); - RJSValidatedSetProperty(ctx, realmObject, typeString, typesObject, attributes); - - JSObjectRef clearTestStateFunction = JSObjectMakeFunctionWithCallback(ctx, clearTestStateString, ClearTestState); - RJSValidatedSetProperty(ctx, realmObject, clearTestStateString, clearTestStateFunction, attributes); - - return realmObject; -} - -void RJSInitializeInContext(JSContextRef ctx) { - JSObjectRef globalObject = JSContextGetGlobalObject(ctx); - JSObjectRef realmObject = RJSConstructorCreate(ctx); - - JSValueRef exception = NULL; - JSStringRef nameString = JSStringCreateWithUTF8CString("Realm"); - JSPropertyAttributes attributes = kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete; - - JSObjectSetProperty(ctx, globalObject, nameString, realmObject, attributes, &exception); - JSStringRelease(nameString); - assert(!exception); -} - -void RJSClearTestState() { - realm::_impl::RealmCoordinator::clear_all_caches(); - realm::remove_realm_files_from_directory(realm::default_realm_file_directory()); -} - -} diff --git a/src/js_list.cpp b/src/js_list.cpp deleted file mode 100644 index 6a1321f9..00000000 --- a/src/js_list.cpp +++ /dev/null @@ -1,277 +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 "js_list.hpp" -#include "js_collection.hpp" -#include "js_object.hpp" -#include "js_results.hpp" -#include "js_util.hpp" - -#include "object_accessor.hpp" -#include "parser.hpp" -#include "query_builder.hpp" - -#include - -using RJSAccessor = realm::NativeAccessor; -using namespace realm; - -JSValueRef ListGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* jsException) { - try { - List *list = RJSGetInternal(object); - std::string indexStr = RJSStringForJSString(propertyName); - if (indexStr == "length") { - return JSValueMakeNumber(ctx, list->size()); - } - - return RJSObjectCreate(ctx, Object(list->get_realm(), list->get_object_schema(), list->get(RJSValidatedPositiveIndex(indexStr)))); - } - catch (std::out_of_range &exp) { - // getters for nonexistent properties in JS should always return undefined - return JSValueMakeUndefined(ctx); - } - catch (std::invalid_argument &exp) { - // for stol failure this could be another property that is handled externally, so ignore - return NULL; - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - return NULL; - } -} - -bool ListSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* jsException) { - try { - List *list = RJSGetInternal(object); - std::string indexStr = RJSStringForJSString(propertyName); - if (indexStr == "length") { - throw std::runtime_error("The 'length' property is readonly."); - } - - list->set(ctx, value, RJSValidatedPositiveIndex(indexStr)); - return true; - } - catch (std::invalid_argument &exp) { - // for stol failure this could be another property that is handled externally, so ignore - return false; - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - return false; - } -} - -void ListPropertyNames(JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames) { - List *list = RJSGetInternal(object); - size_t size = list->size(); - - char str[32]; - for (size_t i = 0; i < size; i++) { - sprintf(str, "%zu", i); - JSStringRef name = JSStringCreateWithUTF8CString(str); - JSPropertyNameAccumulatorAddName(propertyNames, name); - JSStringRelease(name); - } -} - -JSValueRef ListPush(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { - List *list = RJSGetInternal(thisObject); - RJSValidateArgumentCountIsAtLeast(argumentCount, 1); - for (size_t i = 0; i < argumentCount; i++) { - list->add(ctx, arguments[i]); - } - return JSValueMakeNumber(ctx, list->size()); - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - } - return NULL; -} - -JSValueRef ListPop(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { - List *list = RJSGetInternal(thisObject); - RJSValidateArgumentCount(argumentCount, 0); - - size_t size = list->size(); - if (size == 0) { - list->verify_in_transaction(); - return JSValueMakeUndefined(ctx); - } - size_t index = size - 1; - JSValueRef obj = RJSObjectCreate(ctx, Object(list->get_realm(), list->get_object_schema(), list->get(index))); - list->remove(index); - return obj; - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - } - return NULL; -} - -JSValueRef ListUnshift(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { - List *list = RJSGetInternal(thisObject); - RJSValidateArgumentCountIsAtLeast(argumentCount, 1); - for (size_t i = 0; i < argumentCount; i++) { - list->insert(ctx, arguments[i], i); - } - return JSValueMakeNumber(ctx, list->size()); - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - } - return NULL; -} - -JSValueRef ListShift(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { - List *list = RJSGetInternal(thisObject); - RJSValidateArgumentCount(argumentCount, 0); - if (list->size() == 0) { - list->verify_in_transaction(); - return JSValueMakeUndefined(ctx); - } - JSValueRef obj = RJSObjectCreate(ctx, Object(list->get_realm(), list->get_object_schema(), list->get(0))); - list->remove(0); - return obj; - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - } - return NULL; -} - -JSValueRef ListSplice(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { - List *list = RJSGetInternal(thisObject); - size_t size = list->size(); - - RJSValidateArgumentCountIsAtLeast(argumentCount, 1); - long index = std::min(RJSValidatedValueToNumber(ctx, arguments[0]), size); - if (index < 0) { - index = std::max(size + index, 0); - } - - long remove; - if (argumentCount < 2) { - remove = size - index; - } - else { - remove = std::max(RJSValidatedValueToNumber(ctx, arguments[1]), 0); - remove = std::min(remove, size - index); - } - - std::vector removedObjects(remove); - for (size_t i = 0; i < remove; i++) { - removedObjects[i] = RJSObjectCreate(ctx, Object(list->get_realm(), list->get_object_schema(), list->get(index))); - list->remove(index); - } - for (size_t i = 2; i < argumentCount; i++) { - list->insert(ctx, arguments[i], index + i - 2); - } - return JSObjectMakeArray(ctx, remove, removedObjects.data(), jsException); - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - } - return NULL; -} - -JSValueRef ListStaticResults(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { - List *list = RJSGetInternal(thisObject); - RJSValidateArgumentCount(argumentCount, 0); - - return RJSResultsCreate(ctx, list->get_realm(), list->get_object_schema(), std::move(list->get_query()), false); - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - } - return NULL; -} - -JSValueRef ListFiltered(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { - List *list = RJSGetInternal(thisObject); - RJSValidateArgumentCountIsAtLeast(argumentCount, 1); - - SharedRealm sharedRealm = *RJSGetInternal(thisObject); - return RJSResultsCreateFiltered(ctx, sharedRealm, list->get_object_schema(), std::move(list->get_query()), argumentCount, arguments); - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - } - return NULL; -} - -JSValueRef ListSorted(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { - List *list = RJSGetInternal(thisObject); - RJSValidateArgumentRange(argumentCount, 1, 2); - - SharedRealm sharedRealm = *RJSGetInternal(thisObject); - return RJSResultsCreateSorted(ctx, sharedRealm, list->get_object_schema(), std::move(list->get_query()), argumentCount, arguments); - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - } - return NULL; -} - -JSObjectRef RJSListCreate(JSContextRef ctx, List &list) { - return RJSWrapObject(ctx, RJSListClass(), new List(list)); -} - -static const JSStaticFunction RJSListFuncs[] = { - {"push", ListPush, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"pop", ListPop, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"shift", ListShift, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"unshift", ListUnshift, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"splice", ListSplice, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"filtered", ListFiltered, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"sorted", ListSorted, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"snapshot", ListStaticResults, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {NULL, NULL}, -}; - -JSClassRef RJSListClass() { - static JSClassRef s_listClass = RJSCreateWrapperClass("List", ListGetProperty, ListSetProperty, RJSListFuncs, ListPropertyNames, RJSCollectionClass()); - return s_listClass; -} diff --git a/src/js_list.hpp b/src/js_list.hpp index 50fe6716..628de5f1 100644 --- a/src/js_list.hpp +++ b/src/js_list.hpp @@ -18,9 +18,217 @@ #pragma once +#include "js_collection.hpp" +#include "js_realm_object.hpp" +#include "js_results.hpp" +#include "js_types.hpp" #include "js_util.hpp" + #include "shared_realm.hpp" #include "list.hpp" +#include "parser.hpp" +#include "query_builder.hpp" -JSClassRef RJSListClass(); -JSObjectRef RJSListCreate(JSContextRef ctx, realm::List &list); +namespace realm { +namespace js { + +template +class List { + using ContextType = typename T::Context; + using ObjectType = typename T::Object; + using ValueType = typename T::Value; + using Object = js::Object; + using Value = js::Value; + using ReturnValue = js::ReturnValue; + + public: + static ObjectType create_instance(ContextType, realm::List &); + + // properties + static void get_length(ContextType, ObjectType, ReturnValue &); + static void get_index(ContextType, ObjectType, uint32_t, ReturnValue &); + static bool set_index(ContextType, ObjectType, uint32_t, ValueType); + + // methods + static void push(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void pop(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void unshift(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void shift(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void splice(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void snapshot(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void filtered(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void sorted(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); +}; + +template +struct ListClass : ClassDefinition> { + using List = js::List; + + std::string const name = "List"; + + MethodMap const methods = { + {"push", wrap}, + {"pop", wrap}, + {"unshift", wrap}, + {"shift", wrap}, + {"splice", wrap}, + {"snapshot", wrap}, + {"filtered", wrap}, + {"sorted", wrap}, + }; + + PropertyMap const properties = { + {"length", {wrap, nullptr}}, + }; + + IndexPropertyType const index_accessor = {wrap, wrap}; +}; + +template +typename T::Object List::create_instance(ContextType ctx, realm::List &list) { + return create_object>(ctx, new realm::List(list)); +} + +template +void List::get_length(ContextType ctx, ObjectType object, ReturnValue &return_value) { + auto list = get_internal>(object); + return_value.set((uint32_t)list->size()); +} + +template +void List::get_index(ContextType ctx, ObjectType object, uint32_t index, ReturnValue &return_value) { + auto list = get_internal>(object); + auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(index)); + + return_value.set(RealmObject::create_instance(ctx, realm_object)); +} + +template +bool List::set_index(ContextType ctx, ObjectType object, uint32_t index, ValueType value) { + auto list = get_internal>(object); + list->set(ctx, value, index); + return true; +} + +template +void List::push(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count_at_least(argc, 1); + + auto list = get_internal>(this_object); + for (size_t i = 0; i < argc; i++) { + list->add(ctx, arguments[i]); + } + + return_value.set((uint32_t)list->size()); +} + +template +void List::pop(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 0); + + auto list = get_internal>(this_object); + size_t size = list->size(); + if (size == 0) { + list->verify_in_transaction(); + return_value.set_undefined(); + } + else { + size_t index = size - 1; + auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(index)); + + return_value.set(RealmObject::create_instance(ctx, realm_object)); + list->remove(index); + } +} + +template +void List::unshift(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count_at_least(argc, 1); + + auto list = get_internal>(this_object); + for (size_t i = 0; i < argc; i++) { + list->insert(ctx, arguments[i], i); + } + + return_value.set((uint32_t)list->size()); +} + +template +void List::shift(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 0); + + auto list = get_internal>(this_object); + if (list->size() == 0) { + list->verify_in_transaction(); + return_value.set_undefined(); + } + else { + auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(0)); + + return_value.set(RealmObject::create_instance(ctx, realm_object)); + list->remove(0); + } +} + +template +void List::splice(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count_at_least(argc, 1); + + auto list = get_internal>(this_object); + size_t size = list->size(); + long index = std::min(Value::to_number(ctx, arguments[0]), size); + if (index < 0) { + index = std::max(size + index, 0); + } + + size_t remove; + if (argc < 2) { + remove = size - index; + } + else { + remove = std::max(Value::to_number(ctx, arguments[1]), 0); + remove = std::min(remove, size - index); + } + + std::vector removed_objects; + removed_objects.reserve(remove); + + for (size_t i = 0; i < remove; i++) { + auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(index)); + + removed_objects.push_back(RealmObject::create_instance(ctx, realm_object)); + list->remove(index); + } + for (size_t i = 2; i < argc; i++) { + list->insert(ctx, arguments[i], index + i - 2); + } + + return_value.set(Object::create_array(ctx, removed_objects)); +} + +template +void List::snapshot(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 0); + + auto list = get_internal>(this_object); + return_value.set(Results::create_instance(ctx, *list, false)); +} + +template +void List::filtered(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count_at_least(argc, 1); + + auto list = get_internal>(this_object); + return_value.set(Results::create_filtered(ctx, *list, argc, arguments)); +} + +template +void List::sorted(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1, 2); + + auto list = get_internal>(this_object); + return_value.set(Results::create_sorted(ctx, *list, argc, arguments)); +} + +} // js +} // realm diff --git a/src/js_object.cpp b/src/js_object.cpp deleted file mode 100644 index 13654b61..00000000 --- a/src/js_object.cpp +++ /dev/null @@ -1,290 +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 "js_util.hpp" -#include "js_object.hpp" -#include "js_results.hpp" -#include "js_schema.hpp" -#include "js_list.hpp" -#include "js_realm.hpp" - -#include "object_store.hpp" -#include "object_accessor.hpp" - -using RJSAccessor = realm::NativeAccessor; -using namespace realm; - -JSValueRef ObjectGetProperty(JSContextRef ctx, JSObjectRef jsObject, JSStringRef jsPropertyName, JSValueRef* exception) { - try { - Object *obj = RJSGetInternal(jsObject); - return obj->get_property_value(ctx, RJSStringForJSString(jsPropertyName)); - } catch (InvalidPropertyException &ex) { - // getters for nonexistent properties in JS should always return undefined - } catch (std::exception &ex) { - if (exception) { - *exception = RJSMakeError(ctx, ex); - } - } - return NULL; -} - -bool ObjectSetProperty(JSContextRef ctx, JSObjectRef jsObject, JSStringRef jsPropertyName, JSValueRef value, JSValueRef* exception) { - try { - Object *obj = RJSGetInternal(jsObject); - obj->set_property_value(ctx, RJSStringForJSString(jsPropertyName), value, true); - } catch (std::exception &ex) { - if (exception) { - *exception = RJSMakeError(ctx, ex); - } - return false; - } - return true; -} - -void ObjectPropertyNames(JSContextRef ctx, JSObjectRef jsObject, JSPropertyNameAccumulatorRef propertyNames) { - Object *obj = RJSGetInternal(jsObject); - - for (auto &prop : obj->get_object_schema().properties) { - JSStringRef propertyName = RJSStringForString(prop.name); - JSPropertyNameAccumulatorAddName(propertyNames, propertyName); - JSStringRelease(propertyName); - } -} - -JSClassRef RJSObjectClass() { - static JSClassRef s_objectClass = RJSCreateWrapperClass("RealmObject", ObjectGetProperty, ObjectSetProperty, NULL, ObjectPropertyNames); - return s_objectClass; -} - -JSObjectRef RJSObjectCreate(JSContextRef ctx, Object object) { - static JSStringRef prototypeString = JSStringCreateWithUTF8CString("prototype"); - - JSObjectRef constructor = RJSConstructors(object.realm().get())[object.get_object_schema().name]; - JSObjectRef prototype = constructor ? RJSValidatedObjectProperty(ctx, constructor, prototypeString) : NULL; - JSObjectRef jsObject = RJSWrapObject(ctx, RJSObjectClass(), new Object(object), prototype); - - if (constructor) { - JSValueRef exception = NULL; - JSValueRef result = JSObjectCallAsFunction(ctx, constructor, jsObject, 0, NULL, &exception); - - if (exception) { - throw RJSException(ctx, exception); - } - else if (result != jsObject && !JSValueIsNull(ctx, result) && !JSValueIsUndefined(ctx, result)) { - throw std::runtime_error("Realm object constructor must not return another value"); - } - } - - return jsObject; -} - -extern JSObjectRef RJSDictForPropertyArray(JSContextRef ctx, const ObjectSchema &object_schema, JSObjectRef array); - -namespace realm { - -template<> bool RJSAccessor::dict_has_value_for_key(JSContextRef ctx, JSValueRef dict, const std::string &prop_name) { - JSObjectRef object = RJSValidatedValueToObject(ctx, dict); - JSStringRef propStr = RJSStringForString(prop_name); - bool ret = JSObjectHasProperty(ctx, object, propStr); - - JSStringRelease(propStr); - return ret; -} - -template<> JSValueRef RJSAccessor::dict_value_for_key(JSContextRef ctx, JSValueRef dict, const std::string &prop_name) { - JSObjectRef object = RJSValidatedValueToObject(ctx, dict); - JSStringRef propStr = RJSStringForString(prop_name); - JSValueRef ex = NULL; - JSValueRef ret = JSObjectGetProperty(ctx, object, propStr, &ex); - if (ex) { - throw RJSException(ctx, ex); - } - JSStringRelease(propStr); - return ret; -} - -template<> bool RJSAccessor::has_default_value_for_property(JSContextRef ctx, Realm *realm, const ObjectSchema &object_schema, const std::string &prop_name) { - ObjectDefaults &defaults = RJSDefaults(realm)[object_schema.name]; - return defaults.find(prop_name) != defaults.end(); -} - -template<> JSValueRef RJSAccessor::default_value_for_property(JSContextRef ctx, Realm *realm, const ObjectSchema &object_schema, const std::string &prop_name) { - ObjectDefaults &defaults = RJSDefaults(realm)[object_schema.name]; - return defaults[prop_name]; -} - -template<> bool RJSAccessor::is_null(JSContextRef ctx, JSValueRef &val) { - return JSValueIsNull(ctx, val) || JSValueIsUndefined(ctx, val); -} -template<> JSValueRef RJSAccessor::null_value(JSContextRef ctx) { - return JSValueMakeNull(ctx); -} - -template<> bool RJSAccessor::to_bool(JSContextRef ctx, JSValueRef &val) { - if (!JSValueIsBoolean(ctx, val)) { - throw std::runtime_error("Property expected to be of type boolean"); - } - return JSValueToBoolean(ctx, val); -} -template<> JSValueRef RJSAccessor::from_bool(JSContextRef ctx, bool b) { - return JSValueMakeBoolean(ctx, b); -} - -template<> long long RJSAccessor::to_long(JSContextRef ctx, JSValueRef &val) { - return RJSValidatedValueToNumber(ctx, val); -} -template<> JSValueRef RJSAccessor::from_long(JSContextRef ctx, long long l) { - return JSValueMakeNumber(ctx, l); -} - -template<> float RJSAccessor::to_float(JSContextRef ctx, JSValueRef &val) { - return RJSValidatedValueToNumber(ctx, val); -} -template<> JSValueRef RJSAccessor::from_float(JSContextRef ctx, float f) { - return JSValueMakeNumber(ctx, f); -} - -template<> double RJSAccessor::to_double(JSContextRef ctx, JSValueRef &val) { - return RJSValidatedValueToNumber(ctx, val); -} -template<> JSValueRef RJSAccessor::from_double(JSContextRef ctx, double d) { - return JSValueMakeNumber(ctx, d); -} - -template<> std::string RJSAccessor::to_string(JSContextRef ctx, JSValueRef &val) { - return RJSValidatedStringForValue(ctx, val); -} -template<> JSValueRef RJSAccessor::from_string(JSContextRef ctx, StringData s) { - return RJSValueForString(ctx, s); -} - -template<> std::string RJSAccessor::to_binary(JSContextRef ctx, JSValueRef &val) { - static JSStringRef arrayBufferString = JSStringCreateWithUTF8CString("ArrayBuffer"); - static JSStringRef bufferString = JSStringCreateWithUTF8CString("buffer"); - static JSStringRef byteLengthString = JSStringCreateWithUTF8CString("byteLength"); - static JSStringRef byteOffsetString = JSStringCreateWithUTF8CString("byteOffset"); - static JSStringRef isViewString = JSStringCreateWithUTF8CString("isView"); - static JSStringRef uint8ArrayString = JSStringCreateWithUTF8CString("Uint8Array"); - - JSObjectRef arrayBufferConstructor = RJSValidatedObjectProperty(ctx, JSContextGetGlobalObject(ctx), arrayBufferString); - JSObjectRef uint8ArrayContructor = RJSValidatedObjectProperty(ctx, JSContextGetGlobalObject(ctx), uint8ArrayString); - JSValueRef uint8ArrayArguments[3]; - size_t uint8ArrayArgumentsCount = 0; - - // Value should either be an ArrayBuffer or ArrayBufferView (i.e. TypedArray or DataView). - if (JSValueIsInstanceOfConstructor(ctx, val, arrayBufferConstructor, NULL)) { - uint8ArrayArguments[0] = val; - uint8ArrayArgumentsCount = 1; - } - else if (JSObjectRef object = JSValueToObject(ctx, val, NULL)) { - // Check if value is an ArrayBufferView by calling ArrayBuffer.isView(val). - JSObjectRef isViewMethod = RJSValidatedObjectProperty(ctx, arrayBufferConstructor, isViewString); - JSValueRef isView = JSObjectCallAsFunction(ctx, isViewMethod, arrayBufferConstructor, 1, &val, NULL); - - if (isView && JSValueToBoolean(ctx, isView)) { - uint8ArrayArguments[0] = RJSValidatedObjectProperty(ctx, object, bufferString); - uint8ArrayArguments[1] = RJSValidatedPropertyValue(ctx, object, byteOffsetString); - uint8ArrayArguments[2] = RJSValidatedPropertyValue(ctx, object, byteLengthString); - uint8ArrayArgumentsCount = 3; - } - } - - if (!uint8ArrayArgumentsCount) { - throw std::runtime_error("Can only convert ArrayBuffer and TypedArray objects to binary"); - } - - JSValueRef exception = NULL; - JSObjectRef uint8Array = JSObjectCallAsConstructor(ctx, uint8ArrayContructor, uint8ArrayArgumentsCount, uint8ArrayArguments, &exception); - if (exception) { - throw RJSException(ctx, exception); - } - - size_t byteCount = RJSValidatedListLength(ctx, uint8Array); - std::string bytes(byteCount, 0); - - for (size_t i = 0; i < byteCount; i++) { - JSValueRef byteValue = JSObjectGetPropertyAtIndex(ctx, uint8Array, (unsigned)i, NULL); - bytes[i] = JSValueToNumber(ctx, byteValue, NULL); - } - - return bytes; -} -template<> JSValueRef RJSAccessor::from_binary(JSContextRef ctx, BinaryData data) { - static JSStringRef bufferString = JSStringCreateWithUTF8CString("buffer"); - static JSStringRef uint8ArrayString = JSStringCreateWithUTF8CString("Uint8Array"); - - size_t byteCount = data.size(); - JSValueRef byteCountValue = JSValueMakeNumber(ctx, byteCount); - JSObjectRef uint8ArrayContructor = RJSValidatedObjectProperty(ctx, JSContextGetGlobalObject(ctx), uint8ArrayString); - JSObjectRef uint8Array = JSObjectCallAsConstructor(ctx, uint8ArrayContructor, 1, &byteCountValue, NULL); - - for (size_t i = 0; i < byteCount; i++) { - JSValueRef num = JSValueMakeNumber(ctx, data[i]); - JSObjectSetPropertyAtIndex(ctx, uint8Array, (unsigned)i, num, NULL); - } - - return RJSValidatedObjectProperty(ctx, uint8Array, bufferString); -} - -template<> DateTime RJSAccessor::to_datetime(JSContextRef ctx, JSValueRef &val) { - JSObjectRef object = RJSValidatedValueToDate(ctx, val); - double utc = RJSValidatedValueToNumber(ctx, object); - - return DateTime(utc); -} -template<> JSValueRef RJSAccessor::from_datetime(JSContextRef ctx, DateTime dt) { - JSValueRef time = JSValueMakeNumber(ctx, dt.get_datetime()); - return JSObjectMakeDate(ctx, 1, &time, NULL); -} - -template<> size_t RJSAccessor::to_existing_object_index(JSContextRef ctx, JSValueRef &val) { - JSObjectRef object = RJSValidatedValueToObject(ctx, val); - if (JSValueIsObjectOfClass(ctx, val, RJSObjectClass())) { - return RJSGetInternal(object)->row().get_index(); - } - throw std::runtime_error("object is not a Realm Object"); -} -template<> size_t RJSAccessor::to_object_index(JSContextRef ctx, SharedRealm realm, JSValueRef &val, const std::string &type, bool try_update) { - JSObjectRef object = RJSValidatedValueToObject(ctx, val); - if (JSValueIsObjectOfClass(ctx, val, RJSObjectClass())) { - return RJSGetInternal(object)->row().get_index(); - } - - auto object_schema = realm->config().schema->find(type); - if (RJSIsValueArray(ctx, object)) { - object = RJSDictForPropertyArray(ctx, *object_schema, object); - } - - Object child = Object::create(ctx, realm, *object_schema, (JSValueRef)object, try_update); - return child.row().get_index(); -} -template<> JSValueRef RJSAccessor::from_object(JSContextRef ctx, Object object) { - return RJSObjectCreate(ctx, object); -} - -template<> size_t RJSAccessor::list_size(JSContextRef ctx, JSValueRef &val) { - return RJSValidatedListLength(ctx, RJSValidatedValueToObject(ctx, val)); -} -template<> JSValueRef RJSAccessor::list_value_at_index(JSContextRef ctx, JSValueRef &val, size_t index) { - return RJSValidatedObjectAtIndex(ctx, RJSValidatedValueToObject(ctx, val), (unsigned int)index); -} -template<> JSValueRef RJSAccessor::from_list(JSContextRef ctx, List list) { - return RJSListCreate(ctx, list); -} - -} \ No newline at end of file diff --git a/src/js_object_accessor.hpp b/src/js_object_accessor.hpp new file mode 100644 index 00000000..9234b27f --- /dev/null +++ b/src/js_object_accessor.hpp @@ -0,0 +1,144 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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_list.hpp" +#include "js_realm_object.hpp" +#include "js_schema.hpp" + +namespace realm { +namespace js { + +template +struct NativeAccessor { + using ContextType = typename T::Context; + using ObjectType = typename T::Object; + using ValueType = typename T::Value; + using Object = js::Object; + using Value = js::Value; + + static bool dict_has_value_for_key(ContextType ctx, ValueType dict, const std::string &prop_name) { + ObjectType object = Value::validated_to_object(ctx, dict); + return Object::has_property(ctx, object, prop_name); + } + static ValueType dict_value_for_key(ContextType ctx, ValueType dict, const std::string &prop_name) { + ObjectType object = Value::validated_to_object(ctx, dict); + return Object::get_property(ctx, object, prop_name); + } + + static bool has_default_value_for_property(ContextType ctx, realm::Realm *realm, const ObjectSchema &object_schema, const std::string &prop_name) { + auto defaults = get_delegate(realm)->m_defaults[object_schema.name]; + return defaults.count(prop_name) != 0; + } + static ValueType default_value_for_property(ContextType ctx, realm::Realm *realm, const ObjectSchema &object_schema, const std::string &prop_name) { + auto defaults = get_delegate(realm)->m_defaults[object_schema.name]; + return defaults.at(prop_name); + } + + // These must be implemented for each JS engine. + static std::string to_binary(ContextType, ValueType &); + static ValueType from_binary(ContextType, BinaryData); + + static bool to_bool(ContextType ctx, ValueType &value) { + return Value::validated_to_boolean(ctx, value, "Property"); + } + static ValueType from_bool(ContextType ctx, bool boolean) { + return Value::from_boolean(ctx, boolean); + } + static long long to_long(ContextType ctx, ValueType &value) { + return Value::validated_to_number(ctx, value, "Property"); + } + static ValueType from_long(ContextType ctx, long long number) { + return Value::from_number(ctx, number); + } + static float to_float(ContextType ctx, ValueType &value) { + return Value::validated_to_number(ctx, value, "Property"); + } + static ValueType from_float(ContextType ctx, float number) { + return Value::from_number(ctx, number); + } + static double to_double(ContextType ctx, ValueType &value) { + return Value::validated_to_number(ctx, value, "Property"); + } + static ValueType from_double(ContextType ctx, double number) { + return Value::from_number(ctx, number); + } + static std::string to_string(ContextType ctx, ValueType &value) { + return Value::validated_to_string(ctx, value, "Property"); + } + static ValueType from_string(ContextType ctx, StringData string) { + return Value::from_string(ctx, string.data()); + } + static DateTime to_datetime(ContextType ctx, ValueType &value) { + ObjectType date = Value::validated_to_date(ctx, value, "Property"); + return DateTime(Value::to_number(ctx, date)); + } + static ValueType from_datetime(ContextType ctx, DateTime dt) { + return Object::create_date(ctx, dt.get_datetime()); + } + + static bool is_null(ContextType ctx, ValueType &value) { + return Value::is_null(ctx, value) || Value::is_undefined(ctx, value); + } + static ValueType null_value(ContextType ctx) { + return Value::from_null(ctx); + } + + static size_t to_object_index(ContextType ctx, SharedRealm realm, ValueType &value, const std::string &type, bool try_update) { + ObjectType object = Value::validated_to_object(ctx, value); + if (Object::template is_instance>(ctx, object)) { + return get_internal>(object)->row().get_index(); + } + + auto object_schema = realm->config().schema->find(type); + if (Value::is_array(ctx, object)) { + object = Schema::dict_for_property_array(ctx, *object_schema, object); + } + + auto child = realm::Object::create(ctx, realm, *object_schema, static_cast(object), try_update); + return child.row().get_index(); + } + static size_t to_existing_object_index(ContextType ctx, ValueType &value) { + ObjectType object = Value::validated_to_object(ctx, value); + if (Object::template is_instance>(ctx, object)) { + return get_internal>(object)->row().get_index(); + } + throw std::runtime_error("object is not a Realm Object"); + } + static ValueType from_object(ContextType ctx, realm::Object realm_object) { + return RealmObject::create_instance(ctx, realm_object); + } + + static size_t list_size(ContextType ctx, ValueType &value) { + return Object::validated_get_length(ctx, Value::validated_to_object(ctx, value)); + } + static ValueType list_value_at_index(ContextType ctx, ValueType &value, size_t index) { + return Object::validated_get_object(ctx, Value::validated_to_object(ctx, value), (uint32_t)index); + } + static ValueType from_list(ContextType ctx, realm::List list) { + return List::create_instance(ctx, list); + } + + static Mixed to_mixed(ContextType ctx, ValueType &val) { + throw std::runtime_error("'Any' type is unsupported"); + } +}; + +} // js +} // realm diff --git a/src/js_realm.cpp b/src/js_realm.cpp index 6648032c..83152c95 100644 --- a/src/js_realm.cpp +++ b/src/js_realm.cpp @@ -16,554 +16,29 @@ // //////////////////////////////////////////////////////////////////////////// -#include "js_realm.hpp" -#include "js_object.hpp" -#include "js_results.hpp" -#include "js_list.hpp" -#include "js_schema.hpp" #include "platform.hpp" +#include "realm_coordinator.hpp" -#include "shared_realm.hpp" -#include "impl/realm_coordinator.hpp" -#include "object_accessor.hpp" -#include "binding_context.hpp" +namespace realm { +namespace js { -#include -#include +static std::string s_default_path = ""; -using namespace realm; -using RJSAccessor = realm::NativeAccessor; - -class RJSRealmDelegate : public BindingContext { -public: - virtual void did_change(std::vector const& observers, std::vector const& invalidated) { - notify("change"); +std::string default_path() { + if (s_default_path.size() == 0) { + s_default_path = realm::default_realm_file_directory() + "/default.realm"; } - 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; + return s_default_path; } -std::map &RJSConstructors(Realm *realm) { - return static_cast(realm->m_binding_context.get())->m_constructors; +void set_default_path(std::string path) { + s_default_path = path; } -// 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; +void delete_all_realms() { + realm::_impl::RealmCoordinator::clear_all_caches(); + realm::remove_realm_files_from_directory(realm::default_realm_file_directory()); } -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; -} +} // js +} // realm diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 167e64bd..b3cb1351 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -18,19 +18,503 @@ #pragma once -#include "js_util.hpp" +#include #include +#include "js_class.hpp" +#include "js_types.hpp" +#include "js_util.hpp" +#include "js_realm_object.hpp" +#include "js_list.hpp" +#include "js_results.hpp" +#include "js_schema.hpp" + +#include "shared_realm.hpp" +#include "binding_context.hpp" +#include "object_accessor.hpp" +#include "platform.hpp" + namespace realm { - class Realm; - using ObjectDefaults = std::map; +namespace js { + +template +struct RealmClass; + +template +class RealmDelegate : public BindingContext { + public: + using GlobalContextType = typename T::GlobalContext; + using FunctionType = typename T::Function; + using ObjectType = typename T::Object; + using ValueType = typename T::Value; + using Value = js::Value; + + using ObjectDefaultsMap = typename Schema::ObjectDefaultsMap; + using ConstructorMap = typename Schema::ConstructorMap; + + 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) {} + + 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. + m_defaults.clear(); + m_constructors.clear(); + m_notifications.clear(); + } + + void add_notification(FunctionType notification) { + for (auto &handler : m_notifications) { + if (handler == notification) { + return; + } + } + m_notifications.emplace_back(m_context, notification); + } + void remove_notification(FunctionType 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(); + } + + ObjectDefaultsMap m_defaults; + ConstructorMap m_constructors; + + private: + Protected m_context; + std::list> m_notifications; + std::weak_ptr m_realm; + + void notify(const char *notification_name) { + SharedRealm realm = m_realm.lock(); + if (!realm) { + throw std::runtime_error("Realm no longer exists"); + } + + ObjectType realm_object = create_object>(m_context, new SharedRealm(realm)); + ValueType arguments[2]; + arguments[0] = realm_object; + arguments[1] = Value::from_string(m_context, notification_name); + + for (auto &callback : m_notifications) { + Function::call(m_context, callback, realm_object, 2, arguments); + } + } +}; + +std::string default_path(); +void set_default_path(std::string path); +void delete_all_realms(); + +template +class Realm { + using ContextType = typename T::Context; + using FunctionType = typename T::Function; + using ObjectType = typename T::Object; + using ValueType = typename T::Value; + using String = js::String; + using Object = js::Object; + using Value = js::Value; + using ReturnValue = js::ReturnValue; + using NativeAccessor = realm::NativeAccessor; + + public: + static FunctionType create_constructor(ContextType); + + // methods + static void objects(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void create(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void delete_one(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void delete_all(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void write(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void add_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void remove_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void remove_all_listeners(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void close(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + + // properties + static void get_path(ContextType, ObjectType, ReturnValue &); + static void get_schema_version(ContextType, ObjectType, ReturnValue &); + + // static methods + static void constructor(ContextType, ObjectType, size_t, const ValueType[]); + static void schema_version(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void clear_test_state(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + + // static properties + static void get_default_path(ContextType, ObjectType, ReturnValue &); + static void set_default_path(ContextType, ObjectType, ValueType value); + + private: + static std::string validated_notification_name(ContextType ctx, const ValueType &value) { + std::string name = Value::validated_to_string(ctx, value, "notification name"); + if (name != "change") { + throw std::runtime_error("Only the 'change' notification name is supported."); + } + return name; + } + + // converts constructor object or type name to type name + static std::string validated_object_type_for_value(SharedRealm &realm, ContextType ctx, const ValueType &value) { + if (Value::is_constructor(ctx, value)) { + FunctionType constructor = Value::to_constructor(ctx, value); + + auto delegate = get_delegate(realm.get()); + for (auto &pair : delegate->m_constructors) { + if (FunctionType(pair.second) == constructor) { + return pair.first; + } + } + throw std::runtime_error("Constructor was not registered in the schema for this Realm"); + } + return Value::validated_to_string(ctx, value, "objectType"); + } + + static std::string normalize_path(std::string path) { + if (path.size() && path[0] != '/') { + return default_realm_file_directory() + "/" + path; + } + return path; + } +}; + +template +struct RealmClass : ClassDefinition { + using Realm = js::Realm; + + std::string const name = "Realm"; + + ConstructorType* const constructor = Realm::constructor; + + MethodMap const static_methods = { + {"schemaVersion", wrap}, + {"clearTestState", wrap}, + }; + + PropertyMap const static_properties = { + {"defaultPath", {wrap, wrap}}, + }; + + MethodMap const methods = { + {"objects", wrap}, + {"create", wrap}, + {"delete", wrap}, + {"deleteAll", wrap}, + {"write", wrap}, + {"addListener", wrap}, + {"removeListener", wrap}, + {"removeAllListeners", wrap}, + {"close", wrap}, + }; + + PropertyMap const properties = { + {"path", {wrap, nullptr}}, + {"schemaVersion", {wrap, nullptr}}, + }; +}; + +template +inline typename T::Function Realm::create_constructor(ContextType ctx) { + FunctionType realm_constructor = ObjectWrap>::create_constructor(ctx); + FunctionType collection_constructor = ObjectWrap>::create_constructor(ctx); + FunctionType list_constructor = ObjectWrap>::create_constructor(ctx); + FunctionType results_constructor = ObjectWrap>::create_constructor(ctx); + + PropertyAttributes attributes = PropertyAttributes(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); + + return realm_constructor; } -JSClassRef RJSRealmClass(); -JSClassRef RJSRealmConstructorClass(); +template +void Realm::constructor(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[]) { + static const String path_string = "path"; + static const String schema_string = "schema"; + static const String schema_version_string = "schemaVersion"; + static const String encryption_key_string = "encryptionKey"; -std::string RJSDefaultPath(); -void RJSSetDefaultPath(std::string path); + realm::Realm::Config config; + typename Schema::ObjectDefaultsMap defaults; + typename Schema::ConstructorMap constructors; -std::map &RJSDefaults(realm::Realm *realm); -std::map &RJSConstructors(realm::Realm *realm); + if (argc == 0) { + config.path = default_path(); + } + else if (argc == 1) { + ValueType value = arguments[0]; + if (Value::is_string(ctx, value)) { + config.path = Value::validated_to_string(ctx, value, "path"); + } + else if (Value::is_object(ctx, value)) { + ObjectType object = Value::validated_to_object(ctx, value); + + ValueType path_value = Object::get_property(ctx, object, path_string); + if (!Value::is_undefined(ctx, path_value)) { + config.path = Value::validated_to_string(ctx, path_value, "path"); + } + else { + config.path = js::default_path(); + } + + ValueType schema_value = Object::get_property(ctx, object, schema_string); + 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))); + } + + ValueType version_value = Object::get_property(ctx, object, schema_version_string); + if (!Value::is_undefined(ctx, version_value)) { + config.schema_version = Value::validated_to_number(ctx, version_value, "schemaVersion"); + } + else { + config.schema_version = 0; + } + + ValueType encryption_key_value = Object::get_property(ctx, object, encryption_key_string); + if (!Value::is_undefined(ctx, encryption_key_value)) { + std::string encryption_key = NativeAccessor::to_binary(ctx, encryption_key_value); + config.encryption_key = std::vector(encryption_key.begin(), encryption_key.end()); + } + } + } + else { + throw std::runtime_error("Invalid arguments when constructing 'Realm'"); + } + + config.path = normalize_path(config.path); + ensure_directory_exists_for_file(config.path); + + SharedRealm realm = realm::Realm::get_shared_realm(config); + auto delegate = new RealmDelegate(realm, Context::get_global_context(ctx)); + + if (!realm->m_binding_context) { + realm->m_binding_context.reset(delegate); + } + + delegate->m_defaults = std::move(defaults); + delegate->m_constructors = std::move(constructors); + + set_internal>(this_object, new SharedRealm(realm)); +} + +template +void Realm::schema_version(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1, 2); + + realm::Realm::Config config; + config.path = normalize_path(Value::validated_to_string(ctx, arguments[0])); + if (argc == 2) { + auto encryptionKeyValue = arguments[1]; + std::string encryptionKey = NativeAccessor::to_binary(ctx, encryptionKeyValue); + config.encryption_key = std::vector(encryptionKey.begin(), encryptionKey.end()); + } + + auto version = realm::Realm::get_schema_version(config); + if (version == ObjectStore::NotVersioned) { + return_value.set(-1); + } + else { + return_value.set((double)version); + } +} + +template +void Realm::clear_test_state(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 0); + delete_all_realms(); +} + +template +void Realm::get_default_path(ContextType ctx, ObjectType object, ReturnValue &return_value) { + return_value.set(realm::js::default_path()); +} + +template +void Realm::set_default_path(ContextType ctx, ObjectType object, ValueType value) { + js::set_default_path(Value::validated_to_string(ctx, value, "defaultPath")); +} + +template +void Realm::get_path(ContextType ctx, ObjectType object, ReturnValue &return_value) { + std::string path = get_internal>(object)->get()->config().path; + return_value.set(path); +} + +template +void Realm::get_schema_version(ContextType ctx, ObjectType object, ReturnValue &return_value) { + double version = get_internal>(object)->get()->config().schema_version; + return_value.set(version); +} + +template +void Realm::objects(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1); + + SharedRealm realm = *get_internal>(this_object); + std::string type = validated_object_type_for_value(realm, ctx, arguments[0]); + + return_value.set(Results::create_instance(ctx, realm, type)); +} + +template +void Realm::create(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 2, 3); + + SharedRealm realm = *get_internal>(this_object); + std::string className = validated_object_type_for_value(realm, ctx, arguments[0]); + auto &schema = realm->config().schema; + auto object_schema = schema->find(className); + + if (object_schema == schema->end()) { + throw std::runtime_error("Object type '" + className + "' not found in schema."); + } + + ObjectType object = Value::validated_to_object(ctx, arguments[1], "properties"); + if (Value::is_array(ctx, arguments[1])) { + object = Schema::dict_for_property_array(ctx, *object_schema, object); + } + + bool update = false; + if (argc == 3) { + update = Value::validated_to_boolean(ctx, arguments[2], "update"); + } + + auto realm_object = realm::Object::create(ctx, realm, *object_schema, object, update); + return_value.set(RealmObject::create_instance(ctx, realm_object)); +} + +template +void Realm::delete_one(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1); + + SharedRealm realm = *get_internal>(this_object); + if (!realm->is_in_transaction()) { + throw std::runtime_error("Can only delete objects within a transaction."); + } + + ObjectType arg = Value::validated_to_object(ctx, arguments[0]); + + if (Object::template is_instance>(ctx, arg)) { + auto object = get_internal>(arg); + realm::TableRef table = ObjectStore::table_for_object_type(realm->read_group(), object->get_object_schema().name); + table->move_last_over(object->row().get_index()); + } + else if (Value::is_array(ctx, arg)) { + uint32_t length = Object::validated_get_length(ctx, arg); + for (uint32_t i = length; i--;) { + ObjectType object = Object::validated_get_object(ctx, arg, i); + + if (!Object::template is_instance>(ctx, object)) { + throw std::runtime_error("Argument to 'delete' must be a Realm object or a collection of Realm objects."); + } + + auto realm_object = get_internal>(object); + realm::TableRef table = ObjectStore::table_for_object_type(realm->read_group(), realm_object->get_object_schema().name); + table->move_last_over(realm_object->row().get_index()); + } + } + else if (Object::template is_instance>(ctx, arg)) { + auto results = get_internal>(arg); + results->clear(); + } + else if (Object::template is_instance>(ctx, arg)) { + auto list = get_internal>(arg); + list->delete_all(); + } + else { + throw std::runtime_error("Argument to 'delete' must be a Realm object or a collection of Realm objects."); + } +} + +template +void Realm::delete_all(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 0); + + SharedRealm realm = *get_internal>(this_object); + + 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(); + } +} + +template +void Realm::write(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1); + + SharedRealm realm = *get_internal>(this_object); + FunctionType callback = Value::validated_to_function(ctx, arguments[0]); + + realm->begin_transaction(); + + try { + Function::call(ctx, callback, this_object, 0, nullptr); + } + catch (std::exception &e) { + realm->cancel_transaction(); + throw e; + } + + realm->commit_transaction(); +} + +template +void Realm::add_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 2); + + __unused std::string name = validated_notification_name(ctx, arguments[0]); + auto callback = Value::validated_to_function(ctx, arguments[1]); + + SharedRealm realm = *get_internal>(this_object); + get_delegate(realm.get())->add_notification(callback); +} + +template +void Realm::remove_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 2); + + __unused std::string name = validated_notification_name(ctx, arguments[0]); + auto callback = Value::validated_to_function(ctx, arguments[1]); + + SharedRealm realm = *get_internal>(this_object); + get_delegate(realm.get())->remove_notification(callback); +} + +template +void Realm::remove_all_listeners(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 0, 1); + if (argc) { + validated_notification_name(ctx, arguments[0]); + } + + SharedRealm realm = *get_internal>(this_object); + get_delegate(realm.get())->remove_all_notifications(); +} + +template +void Realm::close(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 0); + + SharedRealm realm = *get_internal>(this_object); + realm->close(); +} + +} // js +} // realm diff --git a/src/js_realm_object.hpp b/src/js_realm_object.hpp new file mode 100644 index 00000000..c5df2226 --- /dev/null +++ b/src/js_realm_object.hpp @@ -0,0 +1,122 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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_class.hpp" +#include "js_types.hpp" +#include "js_util.hpp" + +#include "object_accessor.hpp" +#include "object_store.hpp" + +namespace realm { +namespace js { + +template +class RealmObject { + using ContextType = typename T::Context; + using FunctionType = typename T::Function; + using ObjectType = typename T::Object; + using ValueType = typename T::Value; + using String = js::String; + using Value = js::Value; + using Object = js::Object; + using Function = js::Function; + using ReturnValue = js::ReturnValue; + + public: + static ObjectType create_instance(ContextType, realm::Object &); + + static void get_property(ContextType, ObjectType, const String &, ReturnValue &); + static bool set_property(ContextType, ObjectType, const String &, ValueType); + static std::vector get_property_names(ContextType, ObjectType); +}; + +template +struct RealmObjectClass : ClassDefinition { + using RealmObject = js::RealmObject; + + const std::string name = "RealmObject"; + + const StringPropertyType string_accessor = { + wrap, + wrap, + wrap, + }; +}; + +template +typename T::Object RealmObject::create_instance(ContextType ctx, realm::Object &realm_object) { + static String prototype_string = "prototype"; + + auto delegate = get_delegate(realm_object.realm().get()); + auto name = realm_object.get_object_schema().name; + auto object = create_object>(ctx, new realm::Object(realm_object)); + + if (!delegate->m_constructors.count(name)) { + return object; + } + + FunctionType constructor = delegate->m_constructors.at(name); + ObjectType prototype = Object::validated_get_object(ctx, constructor, prototype_string); + Object::set_prototype(ctx, object, prototype); + + ValueType result = Function::call(ctx, constructor, object, 0, NULL); + if (result != object && !Value::is_null(ctx, result) && !Value::is_undefined(ctx, result)) { + throw std::runtime_error("Realm object constructor must not return another value"); + } + + return object; +} + +template +void RealmObject::get_property(ContextType ctx, ObjectType object, const String &property, ReturnValue &return_value) { + try { + auto realm_object = get_internal>(object); + auto result = realm_object->template get_property_value(ctx, property); + return_value.set(result); + } catch (InvalidPropertyException &ex) { + // getters for nonexistent properties in JS should always return undefined + } +} + +template +bool RealmObject::set_property(ContextType ctx, ObjectType object, const String &property, ValueType value) { + auto realm_object = get_internal>(object); + realm_object->set_property_value(ctx, property, value, true); + return true; +} + +template +std::vector> RealmObject::get_property_names(ContextType ctx, ObjectType object) { + auto realm_object = get_internal>(object); + auto &properties = realm_object->get_object_schema().properties; + + std::vector names; + names.reserve(properties.size()); + + for (auto &prop : properties) { + names.push_back(prop.name); + } + + return names; +} + +} // js +} // realm diff --git a/src/js_results.cpp b/src/js_results.cpp deleted file mode 100644 index a332f224..00000000 --- a/src/js_results.cpp +++ /dev/null @@ -1,249 +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 "js_results.hpp" -#include "js_collection.hpp" -#include "js_object.hpp" -#include "object_accessor.hpp" -#include "results.hpp" -#include "parser.hpp" -#include "query_builder.hpp" - -using namespace realm; - -JSValueRef ResultsGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* jsException) { - try { - Results *results = RJSGetInternal(object); - std::string indexStr = RJSStringForJSString(propertyName); - if (indexStr == "length") { - return JSValueMakeNumber(ctx, results->size()); - } - - auto row = results->get(RJSValidatedPositiveIndex(indexStr)); - if (!row.is_attached()) { - return JSValueMakeNull(ctx); - } - - return RJSObjectCreate(ctx, Object(results->get_realm(), results->get_object_schema(), row)); - } - catch (std::out_of_range &exp) { - // getters for nonexistent properties in JS should always return undefined - return JSValueMakeUndefined(ctx); - } - catch (std::invalid_argument &exp) { - // for stol failure this could be another property that is handled externally, so ignore - return NULL; - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - return NULL; - } -} - -bool ResultsSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef *jsException) { - try { - std::string indexStr = RJSStringForJSString(propertyName); - if (indexStr != "length") { - stot(RJSStringForJSString(propertyName)); - } - - // attempts to assign to 'length' or an index should throw an exception - if (jsException) { - *jsException = RJSMakeError(ctx, "Results objects are readonly"); - } - } - catch (std::invalid_argument &exp) { - // for stol failure this could be another property that is handled externally, so ignore - } - return false; -} - -void ResultsPropertyNames(JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames) { - Results *results = RJSGetInternal(object); - size_t size = results->size(); - - char str[32]; - for (size_t i = 0; i < size; i++) { - sprintf(str, "%zu", i); - JSStringRef name = JSStringCreateWithUTF8CString(str); - JSPropertyNameAccumulatorAddName(propertyNames, name); - JSStringRelease(name); - } -} - -JSValueRef ResultsStaticCopy(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { - Results *results = RJSGetInternal(thisObject); - RJSValidateArgumentCount(argumentCount, 0); - - Results *copy = new Results(*results); - copy->set_live(false); - - return RJSWrapObject(ctx, RJSResultsClass(), copy); - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - } - return NULL; -} - -JSValueRef ResultsSorted(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { - Results *results = RJSGetInternal(thisObject); - RJSValidateArgumentRange(argumentCount, 1, 2); - - SharedRealm sharedRealm = *RJSGetInternal(thisObject); - return RJSResultsCreateSorted(ctx, sharedRealm, results->get_object_schema(), std::move(results->get_query()), argumentCount, arguments); - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - } - return NULL; -} - -JSValueRef ResultsFiltered(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { - Results *results = RJSGetInternal(thisObject); - RJSValidateArgumentCountIsAtLeast(argumentCount, 1); - - SharedRealm sharedRealm = *RJSGetInternal(thisObject); - return RJSResultsCreateFiltered(ctx, sharedRealm, results->get_object_schema(), std::move(results->get_query()), argumentCount, arguments); - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - } - return NULL; -} - -JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string className) { - TableRef table = ObjectStore::table_for_object_type(realm->read_group(), className); - auto object_schema = realm->config().schema->find(className); - if (object_schema == realm->config().schema->end()) { - throw std::runtime_error("Object type '" + className + "' not present in Realm."); - } - return RJSWrapObject(ctx, RJSResultsClass(), new Results(realm, *object_schema, *table)); -} - -JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string className, std::string queryString, std::vector args) { - TableRef table = ObjectStore::table_for_object_type(realm->read_group(), className); - Query query = table->where(); - const Schema &schema = *realm->config().schema; - auto object_schema = schema.find(className); - if (object_schema == schema.end()) { - throw std::runtime_error("Object type '" + className + "' not present in Realm."); - } - parser::Predicate predicate = parser::parse(queryString); - query_builder::ArgumentConverter arguments(ctx, args); - query_builder::apply_predicate(query, predicate, arguments, schema, object_schema->name); - - return RJSWrapObject(ctx, RJSResultsClass(), new Results(realm, *object_schema, std::move(query))); -} - -JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, const ObjectSchema &objectSchema, Query query, bool live) { - Results *results = new Results(realm, objectSchema, std::move(query)); - results->set_live(live); - - return RJSWrapObject(ctx, RJSResultsClass(), results); -} - -JSObjectRef RJSResultsCreateFiltered(JSContextRef ctx, SharedRealm realm, const ObjectSchema &objectSchema, Query query, size_t argumentCount, const JSValueRef arguments[]) { - std::string queryString = RJSValidatedStringForValue(ctx, arguments[0], "predicate"); - std::vector args(argumentCount - 1); - for (size_t i = 1; i < argumentCount; i++) { - args[i-1] = arguments[i]; - } - - parser::Predicate predicate = parser::parse(queryString); - query_builder::ArgumentConverter queryArgs(ctx, args); - query_builder::apply_predicate(query, predicate, queryArgs, *realm->config().schema, objectSchema.name); - - return RJSResultsCreate(ctx, realm, objectSchema, std::move(query)); -} - -JSObjectRef RJSResultsCreateSorted(JSContextRef ctx, SharedRealm realm, const ObjectSchema &objectSchema, Query query, size_t argumentCount, const JSValueRef arguments[]) { - size_t prop_count; - std::vector prop_names; - std::vector ascending; - - if (RJSIsValueArray(ctx, arguments[0])) { - RJSValidateArgumentCount(argumentCount, 1, "Second argument is not allowed if passed an array of sort descriptors"); - - JSObjectRef js_prop_names = RJSValidatedValueToObject(ctx, arguments[0]); - prop_count = RJSValidatedListLength(ctx, js_prop_names); - if (!prop_count) { - throw std::invalid_argument("Sort descriptor array must not be empty"); - } - - prop_names.resize(prop_count); - ascending.resize(prop_count); - - for (unsigned int i = 0; i < prop_count; i++) { - JSValueRef val = RJSValidatedPropertyAtIndex(ctx, js_prop_names, i); - - if (RJSIsValueArray(ctx, val)) { - prop_names[i] = RJSValidatedStringForValue(ctx, RJSValidatedPropertyAtIndex(ctx, (JSObjectRef)val, 0)); - ascending[i] = !JSValueToBoolean(ctx, RJSValidatedPropertyAtIndex(ctx, (JSObjectRef)val, 1)); - } - else { - prop_names[i] = RJSValidatedStringForValue(ctx, val); - ascending[i] = true; - } - } - } - else { - RJSValidateArgumentRange(argumentCount, 1, 2); - - prop_count = 1; - prop_names.push_back(RJSValidatedStringForValue(ctx, arguments[0])); - ascending.push_back(argumentCount == 1 ? true : !JSValueToBoolean(ctx, arguments[1])); - } - - std::vector columns(prop_count); - size_t index = 0; - - for (std::string prop_name : prop_names) { - const Property *prop = objectSchema.property_for_name(prop_name); - if (!prop) { - throw std::runtime_error("Property '" + prop_name + "' does not exist on object type '" + objectSchema.name + "'"); - } - columns[index++] = prop->table_column; - } - - Results *results = new Results(realm, objectSchema, std::move(query), {std::move(columns), std::move(ascending)}); - return RJSWrapObject(ctx, RJSResultsClass(), results); -} - -static const JSStaticFunction RJSResultsFuncs[] = { - {"snapshot", ResultsStaticCopy, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"sorted", ResultsSorted, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"filtered", ResultsFiltered, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {NULL, NULL}, -}; - -JSClassRef RJSResultsClass() { - static JSClassRef s_objectClass = RJSCreateWrapperClass("Results", ResultsGetProperty, ResultsSetProperty, RJSResultsFuncs, ResultsPropertyNames, RJSCollectionClass()); - return s_objectClass; -} diff --git a/src/js_results.hpp b/src/js_results.hpp index 9408e436..0318fb53 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -18,18 +18,226 @@ #pragma once -#include "js_util.hpp" -#include +#include "js_collection.hpp" +#include "js_realm_object.hpp" + +#include "results.hpp" +#include "list.hpp" +#include "parser.hpp" +#include "query_builder.hpp" namespace realm { - class Realm; - class Query; - typedef std::shared_ptr SharedRealm; +namespace js { + +template +class Results { + using ContextType = typename T::Context; + using ObjectType = typename T::Object; + using ValueType = typename T::Value; + using Object = js::Object; + using Value = js::Value; + using ReturnValue = js::ReturnValue; + + public: + static ObjectType create_instance(ContextType, const realm::Results &, bool live = true); + static ObjectType create_instance(ContextType, const realm::List &, bool live = true); + static ObjectType create_instance(ContextType, SharedRealm, const std::string &type, bool live = true); + static ObjectType create_instance(ContextType, SharedRealm, const ObjectSchema &, Query, bool live = true); + + template + static ObjectType create_filtered(ContextType, const U &, size_t, const ValueType[]); + + template + static ObjectType create_sorted(ContextType, const U &, size_t, const ValueType[]); + + static void get_length(ContextType, ObjectType, ReturnValue &); + static void get_index(ContextType, ObjectType, uint32_t, ReturnValue &); + + static void snapshot(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void filtered(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void sorted(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); +}; + +template +struct ResultsClass : ClassDefinition> { + using Results = js::Results; + + std::string const name = "Results"; + + MethodMap const methods = { + {"snapshot", wrap}, + {"filtered", wrap}, + {"sorted", wrap}, + }; + + PropertyMap const properties = { + {"length", {wrap, nullptr}}, + }; + + IndexPropertyType const index_accessor = {wrap, nullptr}; +}; + +template +typename T::Object Results::create_instance(ContextType ctx, const realm::Results &results, bool live) { + auto new_results = new realm::Results(results); + new_results->set_live(live); + + return create_object>(ctx, new_results); } -JSClassRef RJSResultsClass(); -JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className); -JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className, std::string query, std::vector args); -JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, const realm::ObjectSchema &objectSchema, realm::Query query, bool live = true); -JSObjectRef RJSResultsCreateFiltered(JSContextRef ctx, realm::SharedRealm realm, const realm::ObjectSchema &objectSchema, realm::Query query, size_t argumentCount, const JSValueRef arguments[]); -JSObjectRef RJSResultsCreateSorted(JSContextRef ctx, realm::SharedRealm realm, const realm::ObjectSchema &objectSchema, realm::Query query, size_t argumentCount, const JSValueRef arguments[]); +template +typename T::Object Results::create_instance(ContextType ctx, const realm::List &list, bool live) { + return create_instance(ctx, list.get_realm(), list.get_object_schema(), list.get_query(), live); +} + +template +typename T::Object Results::create_instance(ContextType ctx, SharedRealm realm, const std::string &type, bool live) { + auto table = ObjectStore::table_for_object_type(realm->read_group(), type); + auto &schema = realm->config().schema; + auto object_schema = schema->find(type); + + if (object_schema == schema->end()) { + throw std::runtime_error("Object type '" + type + "' not present in Realm."); + } + + auto results = new realm::Results(realm, *object_schema, *table); + results->set_live(live); + + return create_object>(ctx, results); +} + +template +typename T::Object Results::create_instance(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, Query query, bool live) { + auto results = new realm::Results(realm, object_schema, std::move(query)); + results->set_live(live); + + return create_object>(ctx, results); +} + +template +template +typename T::Object Results::create_filtered(ContextType ctx, const U &collection, size_t argc, const ValueType arguments[]) { + auto query_string = Value::validated_to_string(ctx, arguments[0], "predicate"); + auto query = collection.get_query(); + auto const &realm = collection.get_realm(); + auto const &object_schema = collection.get_object_schema(); + + std::vector args; + args.reserve(argc - 1); + + for (size_t i = 1; i < argc; i++) { + args.push_back(arguments[i]); + } + + parser::Predicate predicate = parser::parse(query_string); + query_builder::ArgumentConverter converter(ctx, args); + query_builder::apply_predicate(query, predicate, converter, *realm->config().schema, object_schema.name); + + return create_instance(ctx, realm, object_schema, std::move(query)); +} + +template +template +typename T::Object Results::create_sorted(ContextType ctx, const U &collection, size_t argc, const ValueType arguments[]) { + auto const &realm = collection.get_realm(); + auto const &object_schema = collection.get_object_schema(); + std::vector prop_names; + std::vector ascending; + size_t prop_count; + + if (Value::is_array(ctx, arguments[0])) { + validate_argument_count(argc, 1, "Second argument is not allowed if passed an array of sort descriptors"); + + ObjectType js_prop_names = Value::validated_to_object(ctx, arguments[0]); + prop_count = Object::validated_get_length(ctx, js_prop_names); + if (!prop_count) { + throw std::invalid_argument("Sort descriptor array must not be empty"); + } + + prop_names.resize(prop_count); + ascending.resize(prop_count); + + for (unsigned int i = 0; i < prop_count; i++) { + ValueType value = Object::validated_get_property(ctx, js_prop_names, i); + + if (Value::is_array(ctx, value)) { + ObjectType array = Value::to_array(ctx, value); + prop_names[i] = Object::validated_get_string(ctx, array, 0); + ascending[i] = !Object::validated_get_boolean(ctx, array, 1); + } + else { + prop_names[i] = Value::validated_to_string(ctx, value); + ascending[i] = true; + } + } + } + else { + validate_argument_count(argc, 1, 2); + + prop_count = 1; + prop_names.push_back(Value::validated_to_string(ctx, arguments[0])); + ascending.push_back(argc == 1 ? true : !Value::to_boolean(ctx, arguments[1])); + } + + std::vector columns; + columns.reserve(prop_count); + + for (std::string &prop_name : prop_names) { + const Property *prop = object_schema.property_for_name(prop_name); + if (!prop) { + throw std::runtime_error("Property '" + prop_name + "' does not exist on object type '" + object_schema.name + "'"); + } + columns.push_back(prop->table_column); + } + + auto results = new realm::Results(realm, object_schema, collection.get_query(), {std::move(columns), std::move(ascending)}); + return create_object>(ctx, results); +} + +template +void Results::get_length(ContextType ctx, ObjectType object, ReturnValue &return_value) { + auto results = get_internal>(object); + return_value.set((uint32_t)results->size()); +} + +template +void Results::get_index(ContextType ctx, ObjectType object, uint32_t index, ReturnValue &return_value) { + auto results = get_internal>(object); + auto row = results->get(index); + + // Return null for deleted objects in a snapshot. + if (!row.is_attached()) { + return_value.set_null(); + return; + } + + auto realm_object = realm::Object(results->get_realm(), results->get_object_schema(), results->get(index)); + return_value.set(RealmObject::create_instance(ctx, realm_object)); +} + +template +void Results::snapshot(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 0); + + auto results = get_internal>(this_object); + return_value.set(Results::create_instance(ctx, *results, false)); +} + +template +void Results::filtered(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count_at_least(argc, 1); + + auto results = get_internal>(this_object); + return_value.set(create_filtered(ctx, *results, argc, arguments)); +} + +template +void Results::sorted(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1, 2); + + auto results = get_internal>(this_object); + return_value.set(create_sorted(ctx, *results, argc, arguments)); +} + +} // js +} // realm diff --git a/src/js_schema.cpp b/src/js_schema.cpp deleted file mode 100644 index 1bbc83da..00000000 --- a/src/js_schema.cpp +++ /dev/null @@ -1,206 +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 "js_schema.hpp" -#include "object_store.hpp" - -namespace realm { - struct SchemaWrapper { - Schema *schema; - bool owned; - ~SchemaWrapper() { - if (owned) { - delete schema; - } - } - }; -} - -using namespace realm; - -JSClassRef RJSSchemaClass() { - static JSClassRef s_schemaClass = RJSCreateWrapperClass("Schema"); - return s_schemaClass; -} - -JSObjectRef RJSSchemaCreate(JSContextRef ctx, Schema &schema) { - SchemaWrapper *wrapper = new SchemaWrapper(); - wrapper->schema = &schema; - wrapper->owned = false; - return RJSWrapObject(ctx, RJSSchemaClass(), wrapper); -} - -static inline Property RJSParseProperty(JSContextRef ctx, JSValueRef propertyAttributes, std::string propertyName, ObjectDefaults &objectDefaults) { - static JSStringRef defaultString = JSStringCreateWithUTF8CString("default"); - static JSStringRef indexedString = JSStringCreateWithUTF8CString("indexed"); - static JSStringRef typeString = JSStringCreateWithUTF8CString("type"); - static JSStringRef objectTypeString = JSStringCreateWithUTF8CString("objectType"); - static JSStringRef optionalString = JSStringCreateWithUTF8CString("optional"); - - Property prop; - prop.name = propertyName; - - JSObjectRef propertyObject = NULL; - std::string type; - - if (JSValueIsObject(ctx, propertyAttributes)) { - propertyObject = RJSValidatedValueToObject(ctx, propertyAttributes); - type = RJSValidatedStringProperty(ctx, propertyObject, typeString); - - JSValueRef optionalValue = JSObjectGetProperty(ctx, propertyObject, optionalString, NULL); - if (!JSValueIsUndefined(ctx, optionalValue)) { - if (!JSValueIsBoolean(ctx, optionalValue)) { - throw std::runtime_error("'optional' designation expected to be of type boolean"); - } - prop.is_nullable = JSValueToBoolean(ctx, optionalValue); - } - } - else { - type = RJSValidatedStringForValue(ctx, propertyAttributes); - } - - if (type == "bool") { - prop.type = PropertyTypeBool; - } - else if (type == "int") { - prop.type = PropertyTypeInt; - } - else if (type == "float") { - prop.type = PropertyTypeFloat; - } - else if (type == "double") { - prop.type = PropertyTypeDouble; - } - else if (type == "string") { - prop.type = PropertyTypeString; - } - else if (type == "date") { - prop.type = PropertyTypeDate; - } - else if (type == "data") { - prop.type = PropertyTypeData; - } - else if (type == "list") { - if (!propertyObject) { - throw std::runtime_error("List property must specify 'objectType'"); - } - prop.type = PropertyTypeArray; - prop.object_type = RJSValidatedStringProperty(ctx, propertyObject, objectTypeString); - } - else { - prop.type = PropertyTypeObject; - prop.is_nullable = true; - - // The type could either be 'object' or the name of another object type in the same schema. - if (type == "object") { - if (!propertyObject) { - throw std::runtime_error("Object property must specify 'objectType'"); - } - prop.object_type = RJSValidatedStringProperty(ctx, propertyObject, objectTypeString); - } - else { - prop.object_type = type; - } - } - - if (propertyObject) { - JSValueRef defaultValue = RJSValidatedPropertyValue(ctx, propertyObject, defaultString); - if (!JSValueIsUndefined(ctx, defaultValue)) { - JSValueProtect(ctx, defaultValue); - objectDefaults.emplace(prop.name, defaultValue); - } - - JSValueRef indexedValue = RJSValidatedPropertyValue(ctx, propertyObject, indexedString); - if (!JSValueIsUndefined(ctx, indexedValue)) { - prop.is_indexed = JSValueToBoolean(ctx, indexedValue); - } - } - - return prop; -} - -static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef objectSchemaObject, std::map &defaults, std::map &constructors) { - static JSStringRef nameString = JSStringCreateWithUTF8CString("name"); - static JSStringRef primaryString = JSStringCreateWithUTF8CString("primaryKey"); - static JSStringRef propertiesString = JSStringCreateWithUTF8CString("properties"); - static JSStringRef schemaString = JSStringCreateWithUTF8CString("schema"); - - JSObjectRef objectConstructor = NULL; - - if (JSObjectIsConstructor(ctx, objectSchemaObject)) { - objectConstructor = objectSchemaObject; - objectSchemaObject = RJSValidatedObjectProperty(ctx, objectConstructor, schemaString, "Realm object constructor must have a 'schema' property."); - } - - ObjectDefaults objectDefaults; - ObjectSchema objectSchema; - objectSchema.name = RJSValidatedStringProperty(ctx, objectSchemaObject, nameString); - - JSObjectRef propertiesObject = RJSValidatedObjectProperty(ctx, objectSchemaObject, propertiesString, "ObjectSchema must have a 'properties' object."); - if (RJSIsValueArray(ctx, propertiesObject)) { - size_t propertyCount = RJSValidatedListLength(ctx, propertiesObject); - for (size_t i = 0; i < propertyCount; i++) { - JSObjectRef propertyObject = RJSValidatedObjectAtIndex(ctx, propertiesObject, (unsigned int)i); - std::string propertyName = RJSValidatedStringProperty(ctx, propertyObject, nameString); - objectSchema.properties.emplace_back(RJSParseProperty(ctx, propertyObject, propertyName, objectDefaults)); - } - } - else { - JSPropertyNameArrayRef propertyNames = JSObjectCopyPropertyNames(ctx, propertiesObject); - size_t propertyCount = JSPropertyNameArrayGetCount(propertyNames); - for (size_t i = 0; i < propertyCount; i++) { - JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNames, i); - JSValueRef propertyValue = RJSValidatedPropertyValue(ctx, propertiesObject, propertyName); - objectSchema.properties.emplace_back(RJSParseProperty(ctx, propertyValue, RJSStringForJSString(propertyName), objectDefaults)); - } - JSPropertyNameArrayRelease(propertyNames); - } - - JSValueRef primaryValue = RJSValidatedPropertyValue(ctx, objectSchemaObject, primaryString); - if (!JSValueIsUndefined(ctx, primaryValue)) { - objectSchema.primary_key = RJSValidatedStringForValue(ctx, primaryValue); - Property *property = objectSchema.primary_key_property(); - if (!property) { - throw std::runtime_error("Missing primary key property '" + objectSchema.primary_key + "'"); - } - property->is_primary = true; - } - - // Store prototype so that objects of this type will have their prototype set to this prototype object. - if (objectConstructor) { - JSValueProtect(ctx, objectConstructor); - constructors[objectSchema.name] = std::move(objectConstructor); - } - - defaults.emplace(objectSchema.name, std::move(objectDefaults)); - - return objectSchema; -} - -realm::Schema RJSParseSchema(JSContextRef ctx, JSObjectRef jsonObject, std::map &defaults, std::map &constructors) { - std::vector schema; - size_t length = RJSValidatedListLength(ctx, jsonObject); - for (unsigned int i = 0; i < length; i++) { - JSObjectRef jsonObjectSchema = RJSValidatedObjectAtIndex(ctx, jsonObject, i); - ObjectSchema objectSchema = RJSParseObjectSchema(ctx, jsonObjectSchema, defaults, constructors); - schema.emplace_back(std::move(objectSchema)); - } - - return Schema(schema); -} - diff --git a/src/js_schema.hpp b/src/js_schema.hpp index 518e432a..92e5c673 100644 --- a/src/js_schema.hpp +++ b/src/js_schema.hpp @@ -18,15 +18,205 @@ #pragma once -#include "js_util.hpp" #include +#include "js_types.hpp" +#include "schema.hpp" + namespace realm { - class Schema; - using ObjectDefaults = std::map; +namespace js { + +template +struct Schema { + using ContextType = typename T::Context; + using FunctionType = typename T::Function; + using ObjectType = typename T::Object; + using ValueType = typename T::Value; + using String = js::String; + using Object = js::Object; + using Value = js::Value; + + using ObjectDefaults = std::map>; + using ObjectDefaultsMap = std::map; + using ConstructorMap = std::map>; + + static ObjectType dict_for_property_array(ContextType, const ObjectSchema &, ObjectType); + static Property parse_property(ContextType, ValueType, std::string, ObjectDefaults &); + static ObjectSchema parse_object_schema(ContextType, ObjectType, ObjectDefaultsMap &, ConstructorMap &); + static realm::Schema parse_schema(ContextType, ObjectType, ObjectDefaultsMap &, ConstructorMap &); +}; + +template +typename T::Object Schema::dict_for_property_array(ContextType ctx, const ObjectSchema &object_schema, ObjectType array) { + size_t count = object_schema.properties.size(); + + if (count != Object::validated_get_length(ctx, array)) { + throw std::runtime_error("Array must contain values for all object properties"); + } + + ObjectType dict = Object::create_empty(ctx); + + for (uint32_t i = 0; i < count; i++) { + ValueType value = Object::get_property(ctx, array, i); + Object::set_property(ctx, dict, object_schema.properties[i].name, value); + } + + return dict; } -JSClassRef RJSSchemaClass(); -JSObjectRef RJSSchemaCreate(JSContextRef ctx, realm::Schema *schema); +template +Property Schema::parse_property(ContextType ctx, ValueType attributes, std::string property_name, ObjectDefaults &object_defaults) { + static const String default_string = "default"; + static const String indexed_string = "indexed"; + static const String type_string = "type"; + static const String object_type_string = "objectType"; + static const String optional_string = "optional"; + + Property prop; + prop.name = property_name; + + ObjectType property_object = {}; + std::string type; + + if (Value::is_object(ctx, attributes)) { + property_object = Value::validated_to_object(ctx, attributes); + type = Object::validated_get_string(ctx, property_object, type_string); + + ValueType optional_value = Object::get_property(ctx, property_object, optional_string); + if (!Value::is_undefined(ctx, optional_value)) { + prop.is_nullable = Value::validated_to_boolean(ctx, optional_value, "optional"); + } + } + else { + type = Value::validated_to_string(ctx, attributes); + } + + if (type == "bool") { + prop.type = PropertyTypeBool; + } + else if (type == "int") { + prop.type = PropertyTypeInt; + } + else if (type == "float") { + prop.type = PropertyTypeFloat; + } + else if (type == "double") { + prop.type = PropertyTypeDouble; + } + else if (type == "string") { + prop.type = PropertyTypeString; + } + else if (type == "date") { + prop.type = PropertyTypeDate; + } + else if (type == "data") { + prop.type = PropertyTypeData; + } + else if (type == "list") { + if (!Value::is_valid(property_object)) { + throw std::runtime_error("List property must specify 'objectType'"); + } + prop.type = PropertyTypeArray; + prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string); + } + else { + prop.type = PropertyTypeObject; + prop.is_nullable = true; + + // The type could either be 'object' or the name of another object type in the same schema. + if (type == "object") { + if (!Value::is_valid(property_object)) { + throw std::runtime_error("Object property must specify 'objectType'"); + } + prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string); + } + else { + prop.object_type = type; + } + } + + if (Value::is_valid(property_object)) { + ValueType default_value = Object::get_property(ctx, property_object, default_string); + if (!Value::is_undefined(ctx, default_value)) { + object_defaults.emplace(prop.name, Protected(ctx, default_value)); + } + + ValueType indexed_value = Object::get_property(ctx, property_object, indexed_string); + if (!Value::is_undefined(ctx, indexed_value)) { + prop.is_indexed = Value::validated_to_boolean(ctx, indexed_value); + } + } + + return prop; +} -realm::Schema RJSParseSchema(JSContextRef ctx, JSObjectRef jsonObject, std::map &defaults, std::map &constructors); +template +ObjectSchema Schema::parse_object_schema(ContextType ctx, ObjectType object_schema_object, ObjectDefaultsMap &defaults, ConstructorMap &constructors) { + static const String name_string = "name"; + static const String primary_string = "primaryKey"; + static const String properties_string = "properties"; + static const String schema_string = "schema"; + + FunctionType object_constructor = {}; + if (Value::is_constructor(ctx, object_schema_object)) { + object_constructor = Value::to_constructor(ctx, object_schema_object); + object_schema_object = Object::validated_get_object(ctx, object_constructor, schema_string, "Realm object constructor must have a 'schema' property."); + } + + ObjectDefaults object_defaults; + ObjectSchema object_schema; + object_schema.name = Object::validated_get_string(ctx, object_schema_object, name_string); + + ObjectType properties_object = Object::validated_get_object(ctx, object_schema_object, properties_string, "ObjectSchema must have a 'properties' object."); + if (Value::is_array(ctx, properties_object)) { + uint32_t length = Object::validated_get_length(ctx, properties_object); + for (uint32_t i = 0; i < length; i++) { + ObjectType property_object = Object::validated_get_object(ctx, properties_object, i); + std::string property_name = Object::validated_get_string(ctx, property_object, name_string); + object_schema.properties.emplace_back(parse_property(ctx, property_object, property_name, object_defaults)); + } + } + else { + auto property_names = Object::get_property_names(ctx, properties_object); + for (auto &property_name : property_names) { + ValueType property_value = Object::get_property(ctx, properties_object, property_name); + object_schema.properties.emplace_back(parse_property(ctx, property_value, property_name, object_defaults)); + } + } + + ValueType primary_value = Object::get_property(ctx, object_schema_object, primary_string); + if (!Value::is_undefined(ctx, primary_value)) { + object_schema.primary_key = Value::validated_to_string(ctx, primary_value); + Property *property = object_schema.primary_key_property(); + if (!property) { + throw std::runtime_error("Missing primary key property '" + object_schema.primary_key + "'"); + } + property->is_primary = true; + } + + // Store prototype so that objects of this type will have their prototype set to this prototype object. + if (Value::is_valid(object_constructor)) { + constructors.emplace(object_schema.name, Protected(ctx, object_constructor)); + } + + defaults.emplace(object_schema.name, std::move(object_defaults)); + + return object_schema; +} + +template +realm::Schema Schema::parse_schema(ContextType ctx, ObjectType schema_object, ObjectDefaultsMap &defaults, ConstructorMap &constructors) { + std::vector schema; + uint32_t length = Object::validated_get_length(ctx, schema_object); + + for (uint32_t i = 0; i < length; i++) { + ObjectType object_schema_object = Object::validated_get_object(ctx, schema_object, i); + ObjectSchema object_schema = parse_object_schema(ctx, object_schema_object, defaults, constructors); + schema.emplace_back(std::move(object_schema)); + } + + return realm::Schema(schema); +} + +} // js +} // realm diff --git a/src/js_types.hpp b/src/js_types.hpp new file mode 100644 index 00000000..2147039e --- /dev/null +++ b/src/js_types.hpp @@ -0,0 +1,301 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 + +#include + +#if defined(__GNUC__) && !(defined(DEBUG) && DEBUG) +# define REALM_JS_INLINE inline __attribute__((always_inline)) +#elif defined(_MSC_VER) && !(defined(DEBUG) && DEBUG) +# define REALM_JS_INLINE __forceinline +#else +# define REALM_JS_INLINE inline +#endif + +namespace realm { +namespace js { + +enum PropertyAttributes { + None = 0, + ReadOnly = 1 << 0, + DontEnum = 1 << 1, + DontDelete = 1 << 2 +}; + +template +struct String { + using StringType = typename T::String; + + public: + String(const char *); + String(const StringType &); + String(StringType &&); + String(const std::string &); + + operator StringType() const; + operator std::string() const; +}; + +template +struct Context { + using ContextType = typename T::Context; + using GlobalContextType = typename T::GlobalContext; + + static GlobalContextType get_global_context(ContextType); +}; + +template +struct Value { + using ContextType = typename T::Context; + using FunctionType = typename T::Function; + using ObjectType = typename T::Object; + using ValueType = typename T::Value; + + static bool is_array(ContextType, const ValueType &); + static bool is_array_buffer(ContextType, const ValueType &); + static bool is_array_buffer_view(ContextType, const ValueType &); + static bool is_boolean(ContextType, const ValueType &); + static bool is_constructor(ContextType, const ValueType &); + static bool is_date(ContextType, const ValueType &); + static bool is_function(ContextType, const ValueType &); + static bool is_null(ContextType, const ValueType &); + static bool is_number(ContextType, const ValueType &); + static bool is_object(ContextType, const ValueType &); + static bool is_string(ContextType, const ValueType &); + static bool is_undefined(ContextType, const ValueType &); + static bool is_valid(const ValueType &); + + static ValueType from_boolean(ContextType, bool); + static ValueType from_null(ContextType); + static ValueType from_number(ContextType, double); + static ValueType from_string(ContextType, const String &); + static ValueType from_undefined(ContextType); + + static ObjectType to_array(ContextType, const ValueType &); + static bool to_boolean(ContextType, const ValueType &); + static FunctionType to_constructor(ContextType, const ValueType &); + static ObjectType to_date(ContextType, const ValueType &); + static FunctionType to_function(ContextType, const ValueType &); + static double to_number(ContextType, const ValueType &); + static ObjectType to_object(ContextType, const ValueType &); + static String to_string(ContextType, const ValueType &); + +#define VALIDATED(return_t, type) \ + static return_t validated_to_##type(ContextType ctx, const ValueType &value, const char *name = nullptr) { \ + if (!is_##type(ctx, value)) { \ + std::string prefix = name ? std::string("'") + name + "'" : "JS value"; \ + throw std::invalid_argument(prefix + " must be: " #type); \ + } \ + return to_##type(ctx, value); \ + } + + VALIDATED(ObjectType, array) + VALIDATED(bool, boolean) + VALIDATED(FunctionType, constructor) + VALIDATED(ObjectType, date) + VALIDATED(FunctionType, function) + VALIDATED(double, number) + VALIDATED(ObjectType, object) + VALIDATED(String, string) + +#undef VALIDATED +}; + +template +struct Function { + using ContextType = typename T::Context; + using FunctionType = typename T::Function; + using ObjectType = typename T::Object; + using ValueType = typename T::Value; + + static ValueType call(ContextType, const FunctionType &, const ObjectType &, size_t, const ValueType[]); + static ValueType call(ContextType ctx, const FunctionType &function, const ObjectType &this_object, const std::vector &arguments) { + return call(ctx, function, this_object, arguments.size(), arguments.data()); + } + + static ObjectType construct(ContextType, const FunctionType &, size_t, const ValueType[]); + static ValueType construct(ContextType ctx, const FunctionType &function, const std::vector &arguments) { + return construct(ctx, function, arguments.size(), arguments.data()); + } +}; + +template +struct Object { + using ContextType = typename T::Context; + using FunctionType = typename T::Function; + using ObjectType = typename T::Object; + using ValueType = typename T::Value; + + public: + static ValueType get_prototype(ContextType, const ObjectType &); + static void set_prototype(ContextType, const ObjectType &, const ValueType &); + + static bool has_property(ContextType, const ObjectType &, const String &); + static bool has_property(ContextType, const ObjectType &, uint32_t); + static ValueType get_property(ContextType, const ObjectType &, const String &); + static ValueType get_property(ContextType, const ObjectType &, uint32_t); + static void set_property(ContextType, const ObjectType &, const String &, const ValueType &, PropertyAttributes attributes = None); + static void set_property(ContextType, const ObjectType &, uint32_t, const ValueType &); + static std::vector> get_property_names(ContextType, const ObjectType &); + + template + static ValueType validated_get_property(ContextType ctx, const ObjectType &object, const P &property, const char *message = nullptr) { + if (!has_property(ctx, object, property)) { + throw std::out_of_range(message ?: "Object missing expected property: " + util::to_string(property)); + } + return get_property(ctx, object, property); + } + + static uint32_t validated_get_length(ContextType ctx, const ObjectType &object) { + static const String length_string = "length"; + return Value::validated_to_number(ctx, get_property(ctx, object, length_string)); + } + +#define VALIDATED(return_t, type) \ + static return_t validated_get_##type(ContextType ctx, const ObjectType &object, const String &key, const char *message = nullptr) { \ + try { \ + return Value::validated_to_##type(ctx, get_property(ctx, object, key), std::string(key).c_str()); \ + } \ + catch (std::invalid_argument &e) { \ + throw message ? std::invalid_argument(message) : e; \ + } \ + } \ + static return_t validated_get_##type(ContextType ctx, const ObjectType &object, uint32_t index, const char *message = nullptr) { \ + try { \ + return Value::validated_to_##type(ctx, get_property(ctx, object, index)); \ + } \ + catch (std::invalid_argument &e) { \ + throw message ? std::invalid_argument(message) : e; \ + } \ + } + + VALIDATED(ObjectType, array) + VALIDATED(bool, boolean) + VALIDATED(FunctionType, constructor) + VALIDATED(ObjectType, date) + VALIDATED(FunctionType, function) + VALIDATED(double, number) + VALIDATED(ObjectType, object) + VALIDATED(String, string) + +#undef VALIDATED + + static ValueType call_method(ContextType ctx, const ObjectType &object, const String &name, uint32_t argc, const ValueType arguments[]) { + FunctionType method = validated_get_function(ctx, object, name); + return Function::call(ctx, method, object, argc, arguments); + } + static ValueType call_method(ContextType ctx, const ObjectType &object, const String &name, const std::vector &arguments) { + return call_method(ctx, object, name, (uint32_t)arguments.size(), arguments.data()); + } + + static ObjectType create_empty(ContextType); + static ObjectType create_array(ContextType, uint32_t, const ValueType[]); + + static ObjectType create_array(ContextType ctx, const std::vector &values) { + return create_array(ctx, (uint32_t)values.size(), values.data()); + } + static ObjectType create_array(ContextType ctx) { + return create_array(ctx, 0, nullptr); + } + + static ObjectType create_date(ContextType, double); + + template + static ObjectType create_instance(ContextType, typename ClassType::Internal*); + + template + static bool is_instance(ContextType, const ObjectType &); + + template + static typename ClassType::Internal* get_internal(const ObjectType &); + + template + static void set_internal(const ObjectType &, typename ClassType::Internal*); +}; + +template +class Protected { + operator ValueType() const; + bool operator==(const ValueType &) const; + bool operator!=(const ValueType &) const; + bool operator==(const Protected &) const; + bool operator!=(const Protected &) const; +}; + +template +struct Exception : public std::runtime_error { + using ContextType = typename T::Context; + using ValueType = typename T::Value; + + const Protected m_value; + + Exception(ContextType ctx, const std::string &message) + : std::runtime_error(message), m_value(value(ctx, message)) {} + Exception(ContextType ctx, const ValueType &val) + : std::runtime_error(std::string(Value::to_string(ctx, val))), m_value(ctx, val) {} + + operator ValueType() const { + return m_value; + } + + static ValueType value(ContextType ctx, const std::string &message); + + static ValueType value(ContextType ctx, const std::exception &exp) { + if (const Exception *js_exp = dynamic_cast *>(&exp)) { + return *js_exp; + } + return value(ctx, exp.what()); + } +}; + +template +struct ReturnValue { + using ValueType = typename T::Value; + + void set(const ValueType &); + void set(const std::string &); + void set(bool); + void set(double); + void set(int32_t); + void set(uint32_t); + void set_null(); + void set_undefined(); +}; + +template +REALM_JS_INLINE typename T::Object create_object(typename T::Context ctx, typename ClassType::Internal* internal = nullptr) { + return Object::template create_instance(ctx, internal); +} + +template +REALM_JS_INLINE typename ClassType::Internal* get_internal(const typename T::Object &object) { + return Object::template get_internal(object); +} + +template +REALM_JS_INLINE void set_internal(const typename T::Object &object, typename ClassType::Internal* ptr) { + Object::template set_internal(object, ptr); +} + +} // js +} // realm diff --git a/src/js_util.cpp b/src/js_util.cpp deleted file mode 100644 index 905a1f14..00000000 --- a/src/js_util.cpp +++ /dev/null @@ -1,99 +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 "js_util.hpp" -#include - -using namespace realm; - -JSValueRef RJSMakeError(JSContextRef ctx, RJSException &exp) { - JSValueRef value = exp.exception(); - return JSObjectMakeError(ctx, 1, &value, NULL); -} - -JSValueRef RJSMakeError(JSContextRef ctx, std::exception &exp) { - if (RJSException *rjsExp = dynamic_cast(&exp)) { - return RJSMakeError(ctx, *rjsExp); - } - return RJSMakeError(ctx, exp.what()); -} - -JSValueRef RJSMakeError(JSContextRef ctx, const std::string &message) { - JSValueRef value = RJSValueForString(ctx, message); - return JSObjectMakeError(ctx, 1, &value, NULL); -} - -std::string RJSStringForJSString(JSStringRef jsString) { - std::string str; - size_t maxSize = JSStringGetMaximumUTF8CStringSize(jsString); - str.resize(maxSize); - str.resize(JSStringGetUTF8CString(jsString, &str[0], maxSize) - 1); - return str; -} - -std::string RJSStringForValue(JSContextRef ctx, JSValueRef value) { - JSValueRef exception = nullptr; - JSStringRef jsString = JSValueToStringCopy(ctx, value, &exception); - if (!jsString) { - throw RJSException(ctx, exception); - } - - std::string string = RJSStringForJSString(jsString); - JSStringRelease(jsString); - - return string; -} - -std::string RJSValidatedStringForValue(JSContextRef ctx, JSValueRef value, const char * name) { - if (!JSValueIsString(ctx, value)) { - if (name) { - throw std::invalid_argument((std::string)"'" + name + "' must be of type 'String'"); - } - else { - throw std::invalid_argument("JSValue must be of type 'String'"); - } - } - - return RJSStringForValue(ctx, value); -} - -JSStringRef RJSStringForString(const std::string &str) { - return JSStringCreateWithUTF8CString(str.c_str()); -} - -JSValueRef RJSValueForString(JSContextRef ctx, const std::string &str) { - JSStringRef jsStr = RJSStringForString(str); - JSValueRef value = JSValueMakeString(ctx, jsStr); - JSStringRelease(jsStr); - return value; -} - -bool RJSIsValueArray(JSContextRef ctx, JSValueRef value) { - static JSStringRef arrayString = JSStringCreateWithUTF8CString("Array"); - return RJSIsValueObjectOfType(ctx, value, arrayString); -} - -bool RJSIsValueArrayBuffer(JSContextRef ctx, JSValueRef value) { - static JSStringRef arrayString = JSStringCreateWithUTF8CString("ArrayBuffer"); - return RJSIsValueObjectOfType(ctx, value, arrayString); -} - -bool RJSIsValueDate(JSContextRef ctx, JSValueRef value) { - static JSStringRef dateString = JSStringCreateWithUTF8CString("Date"); - return RJSIsValueObjectOfType(ctx, value, dateString); -} diff --git a/src/js_util.hpp b/src/js_util.hpp index 0b219337..4a51fe51 100644 --- a/src/js_util.hpp +++ b/src/js_util.hpp @@ -18,200 +18,25 @@ #pragma once -#include -#include -#include - -#include -#include +#include #include - #include -#include -#include "property.hpp" -#include "schema.hpp" +#include "shared_realm.hpp" + +namespace realm { +namespace js { template -inline void RJSFinalize(JSObjectRef object) { - delete static_cast(JSObjectGetPrivate(object)); - JSObjectSetPrivate(object, NULL); +class RealmDelegate; + +template +static inline RealmDelegate *get_delegate(Realm *realm) { + return static_cast *>(realm->m_binding_context.get()); } template -inline JSObjectRef RJSWrapObject(JSContextRef ctx, JSClassRef jsClass, T object, JSValueRef prototype = NULL) { - JSObjectRef ref = JSObjectMake(ctx, jsClass, (void *)object); - if (prototype) { - JSObjectSetPrototype(ctx, ref, prototype); - } - return ref; -} - -template -inline T RJSGetInternal(JSObjectRef jsObject) { - return static_cast(JSObjectGetPrivate(jsObject)); -} - -template -JSClassRef RJSCreateWrapperClass(const char * name, JSObjectGetPropertyCallback getter = NULL, JSObjectSetPropertyCallback setter = NULL, const JSStaticFunction *funcs = NULL, - JSObjectGetPropertyNamesCallback propertyNames = NULL, JSClassRef parentClass = NULL) { - JSClassDefinition classDefinition = kJSClassDefinitionEmpty; - classDefinition.className = name; - classDefinition.finalize = RJSFinalize; - classDefinition.getProperty = getter; - classDefinition.setProperty = setter; - classDefinition.staticFunctions = funcs; - classDefinition.getPropertyNames = propertyNames; - classDefinition.parentClass = parentClass; - return JSClassCreate(&classDefinition); -} - -std::string RJSStringForJSString(JSStringRef jsString); -std::string RJSStringForValue(JSContextRef ctx, JSValueRef value); -std::string RJSValidatedStringForValue(JSContextRef ctx, JSValueRef value, const char * name = nullptr); - -JSStringRef RJSStringForString(const std::string &str); -JSValueRef RJSValueForString(JSContextRef ctx, const std::string &str); - -inline void RJSValidateArgumentCount(size_t argumentCount, size_t expected, const char *message = NULL) { - if (argumentCount != expected) { - throw std::invalid_argument(message ?: "Invalid arguments"); - } -} - -inline void RJSValidateArgumentCountIsAtLeast(size_t argumentCount, size_t expected, const char *message = NULL) { - if (argumentCount < expected) { - throw std::invalid_argument(message ?: "Invalid arguments"); - } -} - -inline void RJSValidateArgumentRange(size_t argumentCount, size_t min, size_t max, const char *message = NULL) { - if (argumentCount < min || argumentCount > max) { - throw std::invalid_argument(message ?: "Invalid arguments"); - } -} - -class RJSException : public std::runtime_error { -public: - RJSException(JSContextRef ctx, JSValueRef &ex) : std::runtime_error(RJSStringForValue(ctx, ex)), - m_jsException(ex) {} - JSValueRef exception() { return m_jsException; } - -private: - JSValueRef m_jsException; -}; - -JSValueRef RJSMakeError(JSContextRef ctx, RJSException &exp); -JSValueRef RJSMakeError(JSContextRef ctx, std::exception &exp); -JSValueRef RJSMakeError(JSContextRef ctx, const std::string &message); - -bool RJSIsValueArray(JSContextRef ctx, JSValueRef value); -bool RJSIsValueArrayBuffer(JSContextRef ctx, JSValueRef value); -bool RJSIsValueDate(JSContextRef ctx, JSValueRef value); - -static inline JSObjectRef RJSValidatedValueToObject(JSContextRef ctx, JSValueRef value, const char *message = NULL) { - JSObjectRef object = JSValueToObject(ctx, value, NULL); - if (!object) { - throw std::runtime_error(message ?: "Value is not an object."); - } - return object; -} - -static inline JSObjectRef RJSValidatedValueToDate(JSContextRef ctx, JSValueRef value, const char *message = NULL) { - JSObjectRef object = JSValueToObject(ctx, value, NULL); - if (!object || !RJSIsValueDate(ctx, object)) { - throw std::runtime_error(message ?: "Value is not a date."); - } - return object; -} - -static inline JSObjectRef RJSValidatedValueToFunction(JSContextRef ctx, JSValueRef value, const char *message = NULL) { - JSObjectRef object = JSValueToObject(ctx, value, NULL); - if (!object || !JSObjectIsFunction(ctx, object)) { - throw std::runtime_error(message ?: "Value is not a function."); - } - return object; -} - -static inline double RJSValidatedValueToNumber(JSContextRef ctx, JSValueRef value) { - if (JSValueIsNull(ctx, value)) { - throw std::invalid_argument("`null` is not a number."); - } - - JSValueRef exception = NULL; - double number = JSValueToNumber(ctx, value, &exception); - if (exception) { - throw RJSException(ctx, exception); - } - if (isnan(number)) { - throw std::invalid_argument("Value not convertible to a number."); - } - return number; -} - -static inline JSValueRef RJSValidatedPropertyValue(JSContextRef ctx, JSObjectRef object, JSStringRef property) { - JSValueRef exception = NULL; - JSValueRef propertyValue = JSObjectGetProperty(ctx, object, property, &exception); - if (exception) { - throw RJSException(ctx, exception); - } - return propertyValue; -} - -static inline JSValueRef RJSValidatedPropertyAtIndex(JSContextRef ctx, JSObjectRef object, unsigned int index) { - JSValueRef exception = NULL; - JSValueRef propertyValue = JSObjectGetPropertyAtIndex(ctx, object, index, &exception); - if (exception) { - throw RJSException(ctx, exception); - } - return propertyValue; -} - -static inline JSObjectRef RJSValidatedObjectProperty(JSContextRef ctx, JSObjectRef object, JSStringRef property, const char *err = NULL) { - JSValueRef propertyValue = RJSValidatedPropertyValue(ctx, object, property); - if (JSValueIsUndefined(ctx, propertyValue)) { - throw std::runtime_error(err ?: "Object property '" + RJSStringForJSString(property) + "' is undefined"); - } - return RJSValidatedValueToObject(ctx, propertyValue, err); -} - -static inline JSObjectRef RJSValidatedObjectAtIndex(JSContextRef ctx, JSObjectRef object, unsigned int index) { - return RJSValidatedValueToObject(ctx, RJSValidatedPropertyAtIndex(ctx, object, index)); -} - -static inline std::string RJSValidatedStringProperty(JSContextRef ctx, JSObjectRef object, JSStringRef property) { - JSValueRef exception = NULL; - JSValueRef propertyValue = JSObjectGetProperty(ctx, object, property, &exception); - if (exception) { - throw RJSException(ctx, exception); - } - return RJSValidatedStringForValue(ctx, propertyValue, RJSStringForJSString(property).c_str()); -} - -static inline size_t RJSValidatedListLength(JSContextRef ctx, JSObjectRef object) { - JSValueRef exception = NULL; - static JSStringRef lengthString = JSStringCreateWithUTF8CString("length"); - JSValueRef lengthValue = JSObjectGetProperty(ctx, object, lengthString, &exception); - if (exception) { - throw RJSException(ctx, exception); - } - if (!JSValueIsNumber(ctx, lengthValue)) { - throw std::runtime_error("Missing property 'length'"); - } - - return RJSValidatedValueToNumber(ctx, lengthValue); -} - -static inline void RJSValidatedSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSPropertyAttributes attributes = 0) { - JSValueRef exception = NULL; - JSObjectSetProperty(ctx, object, propertyName, value, attributes, &exception); - if (exception) { - throw RJSException(ctx, exception); - } -} - -template -T stot(const std::string s) { +static inline T stot(const std::string &s) { std::istringstream iss(s); T value; iss >> value; @@ -221,27 +46,34 @@ T stot(const std::string s) { return value; } -static inline size_t RJSValidatedPositiveIndex(std::string indexStr) { - long index = stot(indexStr); +static inline uint32_t validated_positive_index(std::string string) { + int64_t index = stot(string); if (index < 0) { - throw std::out_of_range(std::string("Index ") + indexStr + " cannot be less than zero."); + throw std::out_of_range(std::string("Index ") + string + " cannot be less than zero."); } - return index; + if (index > std::numeric_limits::max()) { + throw std::out_of_range(std::string("Index ") + string + " must be a 32-bit unsigned integer"); + } + return static_cast(index); } -static inline bool RJSIsValueObjectOfType(JSContextRef ctx, JSValueRef value, JSStringRef type) { - JSObjectRef globalObject = JSContextGetGlobalObject(ctx); - - JSValueRef exception = NULL; - JSValueRef constructorValue = JSObjectGetProperty(ctx, globalObject, type, &exception); - if (exception) { - throw RJSException(ctx, exception); +static inline void validate_argument_count(size_t count, size_t expected, const char *message = nullptr) { + if (count != expected) { + throw std::invalid_argument(message ?: "Invalid arguments"); } - - bool ret = JSValueIsInstanceOfConstructor(ctx, value, RJSValidatedValueToObject(ctx, constructorValue), &exception); - if (exception) { - throw RJSException(ctx, exception); - } - - return ret; } + +static inline void validate_argument_count(size_t count, size_t min, size_t max, const char *message = nullptr) { + if (count < min || count > max) { + throw std::invalid_argument(message ?: "Invalid arguments"); + } +} + +static inline void validate_argument_count_at_least(size_t count, size_t expected, const char *message = nullptr) { + if (count < expected) { + throw std::invalid_argument(message ?: "Invalid arguments"); + } +} + +} // js +} // realm diff --git a/src/jsc/jsc_class.hpp b/src/jsc/jsc_class.hpp new file mode 100644 index 00000000..f60080f6 --- /dev/null +++ b/src/jsc/jsc_class.hpp @@ -0,0 +1,426 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "jsc_types.hpp" + +#include "js_class.hpp" +#include "js_util.hpp" + +namespace realm { +namespace jsc { + +template +using ClassDefinition = js::ClassDefinition; + +using ConstructorType = js::ConstructorType; +using MethodType = js::MethodType; +using PropertyType = js::PropertyType; +using IndexPropertyType = js::IndexPropertyType; +using StringPropertyType = js::StringPropertyType; +using MethodMap = js::MethodMap; +using PropertyMap = js::PropertyMap; + +template +class ObjectWrap { + using Internal = typename ClassType::Internal; + using ParentClassType = typename ClassType::Parent; + + public: + 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); + } + return JSObjectMakeConstructor(ctx, get_class(), construct); + } + + 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 bool has_instance(JSContextRef ctx, JSValueRef value) { + return JSValueIsObjectOfClass(ctx, value, get_class()); + } + + operator Internal*() const { + return m_object.get(); + } + + ObjectWrap& operator=(Internal* object) { + if (m_object.get() != object) { + m_object = std::unique_ptr(object); + } + return *this; + } + + private: + static ClassType s_class; + + std::unique_ptr m_object; + + ObjectWrap(Internal* object = nullptr) : m_object(object) {} + + static JSClassRef create_constructor_class(); + static JSClassRef create_class(); + + static std::vector get_methods(const MethodMap &); + static std::vector get_properties(const PropertyMap &); + + static JSObjectRef construct(JSContextRef, JSObjectRef, size_t, const JSValueRef[], JSValueRef*); + static void finalize(JSObjectRef); + static void get_property_names(JSContextRef, JSObjectRef, JSPropertyNameAccumulatorRef); + static JSValueRef get_property(JSContextRef, JSObjectRef, JSStringRef, JSValueRef*); + static bool set_property(JSContextRef, JSObjectRef, JSStringRef, JSValueRef, JSValueRef*); + + static bool set_readonly_property(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef value, JSValueRef* exception) { + *exception = Exception::value(ctx, std::string("Cannot assign to read only property '") + std::string(String(property)) + "'"); + return false; + } + + static bool has_instance(JSContextRef ctx, JSObjectRef constructor, JSValueRef value, JSValueRef* exception) { + return JSValueIsObjectOfClass(ctx, value, get_class()); + } +}; + +template<> +class ObjectWrap { +public: + using Internal = void; + + static JSClassRef get_class() { + return nullptr; + } +}; + +// The static class variable must be defined as well. +template +ClassType ObjectWrap::s_class; + +template +inline JSClassRef ObjectWrap::create_class() { + JSClassDefinition definition = kJSClassDefinitionEmpty; + std::vector methods; + std::vector properties; + + definition.parentClass = ObjectWrap::get_class(); + definition.className = s_class.name.c_str(); + definition.finalize = finalize; + + if (!s_class.methods.empty()) { + methods = get_methods(s_class.methods); + definition.staticFunctions = methods.data(); + } + if (!s_class.properties.empty()) { + properties = get_properties(s_class.properties); + definition.staticValues = properties.data(); + } + + if (s_class.index_accessor.getter || s_class.string_accessor.getter) { + definition.getProperty = get_property; + definition.setProperty = set_property; + } + else if (s_class.index_accessor.setter || s_class.string_accessor.setter) { + definition.setProperty = set_property; + } + + if (s_class.index_accessor.getter || s_class.string_accessor.enumerator) { + definition.getPropertyNames = get_property_names; + } + + return JSClassCreate(&definition); +} + +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.hasInstance = has_instance; + + if (s_class.constructor) { + definition.callAsConstructor = construct; + } + if (!s_class.static_methods.empty()) { + methods = get_methods(s_class.static_methods); + definition.staticFunctions = methods.data(); + } + if (!s_class.static_properties.empty()) { + properties = get_properties(s_class.static_properties); + definition.staticValues = properties.data(); + } + + return JSClassCreate(&definition); +} + +template +inline std::vector ObjectWrap::get_methods(const MethodMap &methods) { + std::vector functions; + functions.reserve(methods.size() + 1); + + JSPropertyAttributes attributes = kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete; + size_t index = 0; + + for (auto &pair : methods) { + functions[index++] = {pair.first.c_str(), pair.second, attributes}; + } + + functions[index] = {0}; + return functions; +} + +template +inline std::vector ObjectWrap::get_properties(const PropertyMap &properties) { + std::vector values; + values.reserve(properties.size() + 1); + + JSPropertyAttributes attributes = kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete; + size_t index = 0; + + for (auto &pair : properties) { + auto &prop = pair.second; + values[index++] = {pair.first.c_str(), prop.getter, prop.setter ?: set_readonly_property, attributes}; + } + + values[index] = {0}; + return values; +} + +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"); + return nullptr; + } + + JSObjectRef this_object = ObjectWrap::create_instance(ctx); + try { + s_class.constructor(ctx, this_object, argc, arguments); + } + catch (std::exception &e) { + *exception = jsc::Exception::value(ctx, e); + } + return this_object; +} + +template +inline void ObjectWrap::finalize(JSObjectRef object) { + // This is called for the most derived class before superclasses. + if (auto wrap = static_cast *>(JSObjectGetPrivate(object))) { + delete wrap; + JSObjectSetPrivate(object, nullptr); + } +} + +template +inline void ObjectWrap::get_property_names(JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef accumulator) { + if (s_class.index_accessor.getter) { + try { + uint32_t length = Object::validated_get_length(ctx, object); + char string[32]; + for (uint32_t i = 0; i < length; i++) { + sprintf(string, "%u", i); + JSPropertyNameAccumulatorAddName(accumulator, jsc::String(string)); + } + } + catch (std::exception &) { + // Enumerating properties should never throw an exception into JS. + } + } + if (auto string_enumerator = s_class.string_accessor.enumerator) { + string_enumerator(ctx, object, accumulator); + } +} + +template +inline JSValueRef ObjectWrap::get_property(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef* exception) { + if (auto index_getter = s_class.index_accessor.getter) { + try { + uint32_t index = validated_positive_index(jsc::String(property)); + return index_getter(ctx, object, index, exception); + } + catch (std::out_of_range &) { + // Out-of-bounds index getters should just return undefined in JS. + return Value::from_undefined(ctx); + } + catch (std::invalid_argument &) { + // Property is not a number. + } + } + if (auto string_getter = s_class.string_accessor.getter) { + return string_getter(ctx, object, property, exception); + } + return nullptr; +} + +template +inline bool ObjectWrap::set_property(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef value, JSValueRef* exception) { + auto index_setter = s_class.index_accessor.setter; + + if (index_setter || s_class.index_accessor.getter) { + try { + uint32_t index = validated_positive_index(jsc::String(property)); + + if (index_setter) { + return index_setter(ctx, object, index, value, exception); + } + else { + *exception = Exception::value(ctx, std::string("Cannot assign to read only index ") + util::to_string(index)); + return false; + } + } + catch (std::out_of_range &e) { + *exception = Exception::value(ctx, e); + return false; + } + catch (std::invalid_argument &) { + // Property is not a number. + } + } + if (auto string_setter = s_class.string_accessor.setter) { + return string_setter(ctx, object, property, value, exception); + } + return false; +} + +} // jsc + +namespace js { + +template +class ObjectWrap : public jsc::ObjectWrap {}; + +template +JSValueRef wrap(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], JSValueRef* exception) { + jsc::ReturnValue return_value(ctx); + try { + F(ctx, this_object, argc, arguments, return_value); + return return_value; + } + catch (std::exception &e) { + *exception = jsc::Exception::value(ctx, e); + return nullptr; + } +} + +template +JSValueRef wrap(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef* exception) { + jsc::ReturnValue return_value(ctx); + try { + F(ctx, object, return_value); + return return_value; + } + catch (std::exception &e) { + *exception = jsc::Exception::value(ctx, e); + return nullptr; + } +} + +template +bool wrap(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef value, JSValueRef* exception) { + try { + F(ctx, object, value); + return true; + } + catch (std::exception &e) { + *exception = jsc::Exception::value(ctx, e); + return false; + } +} + +template +JSValueRef wrap(JSContextRef ctx, JSObjectRef object, uint32_t index, JSValueRef* exception) { + jsc::ReturnValue return_value(ctx); + try { + F(ctx, object, index, return_value); + return return_value; + } + catch (std::out_of_range &) { + // Out-of-bounds index getters should just return undefined in JS. + return jsc::Value::from_undefined(ctx); + } + catch (std::exception &e) { + *exception = jsc::Exception::value(ctx, e); + return nullptr; + } +} + +template +bool wrap(JSContextRef ctx, JSObjectRef object, uint32_t index, JSValueRef value, JSValueRef* exception) { + try { + return F(ctx, object, index, value); + } + catch (std::exception &e) { + *exception = jsc::Exception::value(ctx, e); + return false; + } +} + +template +JSValueRef wrap(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef* exception) { + jsc::ReturnValue return_value(ctx); + try { + F(ctx, object, property, return_value); + return return_value; + } + catch (std::exception &e) { + *exception = jsc::Exception::value(ctx, e); + return nullptr; + } +} + +template +bool wrap(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef value, JSValueRef* exception) { + try { + return F(ctx, object, property, value); + } + catch (std::exception &e) { + *exception = jsc::Exception::value(ctx, e); + return false; + } +} + +template +void wrap(JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef accumulator) { + auto names = F(ctx, object); + for (auto &name : names) { + JSPropertyNameAccumulatorAddName(accumulator, name); + } +} + +} // js +} // realm + diff --git a/src/js_object.hpp b/src/jsc/jsc_context.hpp similarity index 80% rename from src/js_object.hpp rename to src/jsc/jsc_context.hpp index 8a9a472c..563bbdfe 100644 --- a/src/js_object.hpp +++ b/src/jsc/jsc_context.hpp @@ -18,11 +18,15 @@ #pragma once -#include "js_util.hpp" +#include "jsc_types.hpp" namespace realm { - class Object; +namespace js { + +template<> +inline JSGlobalContextRef jsc::Context::get_global_context(JSContextRef ctx) { + return JSContextGetGlobalContext(ctx); } -JSClassRef RJSObjectClass(); -JSObjectRef RJSObjectCreate(JSContextRef ctx, realm::Object object); +} // js +} // realm diff --git a/src/js_collection.cpp b/src/jsc/jsc_exception.hpp similarity index 67% rename from src/js_collection.cpp rename to src/jsc/jsc_exception.hpp index 968404f2..c6b315ac 100644 --- a/src/js_collection.cpp +++ b/src/jsc/jsc_exception.hpp @@ -16,15 +16,18 @@ // //////////////////////////////////////////////////////////////////////////// -#include "js_collection.hpp" +#pragma once -static JSClassRef RJSCreateCollectionClass() { - JSClassDefinition classDefinition = kJSClassDefinitionEmpty; - classDefinition.className = "Collection"; - return JSClassCreate(&classDefinition); -} +#include "jsc_types.hpp" -JSClassRef RJSCollectionClass() { - static JSClassRef s_collectionClass = RJSCreateCollectionClass(); - return s_collectionClass; +namespace realm { +namespace js { + +template<> +inline JSValueRef jsc::Exception::value(JSContextRef ctx, const std::string &message) { + JSValueRef value = jsc::Value::from_string(ctx, message); + return JSObjectMakeError(ctx, 1, &value, NULL); } + +} // js +} // realm diff --git a/src/jsc/jsc_function.hpp b/src/jsc/jsc_function.hpp new file mode 100644 index 00000000..866c973f --- /dev/null +++ b/src/jsc/jsc_function.hpp @@ -0,0 +1,47 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "jsc_types.hpp" + +namespace realm { +namespace js { + +template<> +inline JSValueRef jsc::Function::call(JSContextRef ctx, const JSObjectRef &function, const JSObjectRef &this_object, size_t argc, const JSValueRef arguments[]) { + JSValueRef exception = nullptr; + JSValueRef result = JSObjectCallAsFunction(ctx, function, this_object, argc, arguments, &exception); + if (exception) { + throw jsc::Exception(ctx, exception); + } + return result; +} + +template<> +inline JSObjectRef jsc::Function::construct(JSContextRef ctx, const JSObjectRef &function, size_t argc, const JSValueRef arguments[]) { + JSValueRef exception = nullptr; + JSObjectRef result = JSObjectCallAsConstructor(ctx, function, argc, arguments, &exception); + if (exception) { + throw jsc::Exception(ctx, exception); + } + return result; +} + +} // js +} // realm diff --git a/src/jsc/jsc_init.cpp b/src/jsc/jsc_init.cpp new file mode 100644 index 00000000..cb78f7c7 --- /dev/null +++ b/src/jsc/jsc_init.cpp @@ -0,0 +1,43 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 +#include + +#include "jsc_init.hpp" +#include "platform.hpp" + +extern "C" { + +using namespace realm; +using namespace realm::jsc; + +JSObjectRef RJSConstructorCreate(JSContextRef ctx) { + return js::Realm::create_constructor(ctx); +} + +void RJSInitializeInContext(JSContextRef ctx) { + static const String realm_string = "Realm"; + + 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)); +} + +} // extern "C" diff --git a/src/js_init.h b/src/jsc/jsc_init.h similarity index 100% rename from src/js_init.h rename to src/jsc/jsc_init.h diff --git a/src/jsc/jsc_init.hpp b/src/jsc/jsc_init.hpp new file mode 100644 index 00000000..de19d7b9 --- /dev/null +++ b/src/jsc/jsc_init.hpp @@ -0,0 +1,32 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "jsc_init.h" +#include "jsc_string.hpp" +#include "jsc_protected.hpp" +#include "jsc_context.hpp" +#include "jsc_value.hpp" +#include "jsc_object.hpp" +#include "jsc_function.hpp" +#include "jsc_exception.hpp" +#include "jsc_return_value.hpp" +#include "jsc_object_accessor.hpp" + +#include "js_realm.hpp" diff --git a/src/jsc/jsc_object.hpp b/src/jsc/jsc_object.hpp new file mode 100644 index 00000000..0013add0 --- /dev/null +++ b/src/jsc/jsc_object.hpp @@ -0,0 +1,147 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "jsc_types.hpp" + +namespace realm { +namespace js { + +template<> +inline bool jsc::Object::has_property(JSContextRef ctx, const JSObjectRef &object, const jsc::String &key) { + return JSObjectHasProperty(ctx, object, key); +} + +template<> +inline bool jsc::Object::has_property(JSContextRef ctx, const JSObjectRef &object, uint32_t index) { + return JSObjectHasProperty(ctx, object, jsc::String(util::to_string(index))); +} + +template<> +inline JSValueRef jsc::Object::get_property(JSContextRef ctx, const JSObjectRef &object, const jsc::String &key) { + JSValueRef exception = nullptr; + JSValueRef value = JSObjectGetProperty(ctx, object, key, &exception); + if (exception) { + throw jsc::Exception(ctx, exception); + } + return value; +} + +template<> +inline JSValueRef jsc::Object::get_property(JSContextRef ctx, const JSObjectRef &object, uint32_t index) { + JSValueRef exception = nullptr; + JSValueRef value = JSObjectGetPropertyAtIndex(ctx, object, index, &exception); + if (exception) { + throw jsc::Exception(ctx, exception); + } + return value; +} + +template<> +inline void jsc::Object::set_property(JSContextRef ctx, const JSObjectRef &object, const jsc::String &key, const JSValueRef &value, PropertyAttributes attributes) { + JSValueRef exception = nullptr; + JSObjectSetProperty(ctx, object, key, value, attributes << 1, &exception); + if (exception) { + throw jsc::Exception(ctx, exception); + } +} + +template<> +inline void jsc::Object::set_property(JSContextRef ctx, const JSObjectRef &object, uint32_t index, const JSValueRef &value) { + JSValueRef exception = nullptr; + JSObjectSetPropertyAtIndex(ctx, object, index, value, &exception); + if (exception) { + throw jsc::Exception(ctx, exception); + } +} + +template<> +inline std::vector jsc::Object::get_property_names(JSContextRef ctx, const JSObjectRef &object) { + JSPropertyNameArrayRef property_names = JSObjectCopyPropertyNames(ctx, object); + size_t property_count = JSPropertyNameArrayGetCount(property_names); + + std::vector names; + names.reserve(property_count); + + for (size_t i = 0; i < property_count; i++) { + names.push_back(JSPropertyNameArrayGetNameAtIndex(property_names, i)); + } + + JSPropertyNameArrayRelease(property_names); + return names; +} + +template<> +inline JSValueRef jsc::Object::get_prototype(JSContextRef ctx, const JSObjectRef &object) { + return JSObjectGetPrototype(ctx, object); +} + +template<> +inline void jsc::Object::set_prototype(JSContextRef ctx, const JSObjectRef &object, const JSValueRef &prototype) { + JSObjectSetPrototype(ctx, object, prototype); +} + +template<> +inline JSObjectRef jsc::Object::create_empty(JSContextRef ctx) { + return JSObjectMake(ctx, nullptr, nullptr); +} + +template<> +inline JSObjectRef jsc::Object::create_array(JSContextRef ctx, uint32_t length, const JSValueRef values[]) { + JSValueRef exception = nullptr; + JSObjectRef array = JSObjectMakeArray(ctx, length, values, &exception); + if (exception) { + throw jsc::Exception(ctx, exception); + } + return array; +} + +template<> +inline JSObjectRef jsc::Object::create_date(JSContextRef ctx, double time) { + JSValueRef number = jsc::Value::from_number(ctx, time); + return JSObjectMakeDate(ctx, 1, &number, nullptr); +} + +template<> +template +inline JSObjectRef jsc::Object::create_instance(JSContextRef ctx, typename ClassType::Internal* internal) { + return jsc::ObjectWrap::create_instance(ctx, internal); +} + +template<> +template +inline bool jsc::Object::is_instance(JSContextRef ctx, const JSObjectRef &object) { + return jsc::ObjectWrap::has_instance(ctx, object); +} + +template<> +template +inline typename ClassType::Internal* jsc::Object::get_internal(const JSObjectRef &object) { + return *static_cast *>(JSObjectGetPrivate(object)); +} + +template<> +template +inline void jsc::Object::set_internal(const JSObjectRef &object, typename ClassType::Internal* ptr) { + auto wrap = static_cast *>(JSObjectGetPrivate(object)); + *wrap = ptr; +} + +} // js +} // realm diff --git a/src/jsc/jsc_object_accessor.hpp b/src/jsc/jsc_object_accessor.hpp new file mode 100644 index 00000000..d210d90a --- /dev/null +++ b/src/jsc/jsc_object_accessor.hpp @@ -0,0 +1,99 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "jsc_class.hpp" +#include "js_object_accessor.hpp" + +namespace realm { + +// Specialize a native accessor class for JSC. +template<> +class NativeAccessor : public js::NativeAccessor {}; + +namespace js { + +template<> +inline std::string NativeAccessor::to_binary(JSContextRef ctx, JSValueRef &value) { + static jsc::String s_array_buffer = "ArrayBuffer"; + static jsc::String s_buffer = "buffer"; + static jsc::String s_byte_length = "byteLength"; + static jsc::String s_byte_offset = "byteOffset"; + static jsc::String s_is_view = "isView"; + static jsc::String s_uint8_array = "Uint8Array"; + + JSObjectRef global_object = JSContextGetGlobalObject(ctx); + JSObjectRef array_buffer_constructor = jsc::Object::validated_get_constructor(ctx, global_object, s_array_buffer); + JSObjectRef uint8_array_constructor = jsc::Object::validated_get_constructor(ctx, global_object, s_uint8_array); + JSValueRef uint8_array_arguments[3]; + uint32_t uint8_array_argc = 0; + + // Value should either be an ArrayBuffer or ArrayBufferView (i.e. TypedArray or DataView). + if (JSValueIsInstanceOfConstructor(ctx, value, array_buffer_constructor, nullptr)) { + uint8_array_arguments[0] = value; + uint8_array_argc = 1; + } + else if (JSObjectRef object = JSValueToObject(ctx, value, nullptr)) { + // Check if value is an ArrayBufferView by calling ArrayBuffer.isView(val). + JSValueRef is_view = jsc::Object::call_method(ctx, array_buffer_constructor, s_is_view, 1, &object); + + if (jsc::Value::to_boolean(ctx, is_view)) { + uint8_array_arguments[0] = jsc::Object::validated_get_object(ctx, object, s_buffer); + uint8_array_arguments[1] = jsc::Object::get_property(ctx, object, s_byte_offset); + uint8_array_arguments[2] = jsc::Object::get_property(ctx, object, s_byte_length); + uint8_array_argc = 3; + } + } + + if (!uint8_array_argc) { + throw std::runtime_error("Can only convert ArrayBuffer and TypedArray objects to binary"); + } + + JSObjectRef uint8_array = jsc::Function::construct(ctx, uint8_array_constructor, uint8_array_argc, uint8_array_arguments); + uint32_t byte_count = jsc::Object::validated_get_length(ctx, uint8_array); + std::string bytes(byte_count, 0); + + for (uint32_t i = 0; i < byte_count; i++) { + JSValueRef byteValue = jsc::Object::get_property(ctx, uint8_array, i); + bytes[i] = jsc::Value::to_number(ctx, byteValue); + } + + return bytes; +} + +template<> +inline JSValueRef NativeAccessor::from_binary(JSContextRef ctx, BinaryData data) { + static jsc::String s_buffer = "buffer"; + static jsc::String s_uint8_array = "Uint8Array"; + + size_t byte_count = data.size(); + JSValueRef byte_count_value = jsc::Value::from_number(ctx, byte_count); + JSObjectRef uint8_array_constructor = jsc::Object::validated_get_constructor(ctx, JSContextGetGlobalObject(ctx), s_uint8_array); + JSObjectRef uint8_array = jsc::Function::construct(ctx, uint8_array_constructor, 1, &byte_count_value); + + for (uint32_t i = 0; i < byte_count; i++) { + JSValueRef num = jsc::Value::from_number(ctx, data[i]); + jsc::Object::set_property(ctx, uint8_array, i, num); + } + + return jsc::Object::validated_get_object(ctx, uint8_array, s_buffer); +} + +} // js +} // realm diff --git a/src/jsc/jsc_protected.hpp b/src/jsc/jsc_protected.hpp new file mode 100644 index 00000000..39a8e198 --- /dev/null +++ b/src/jsc/jsc_protected.hpp @@ -0,0 +1,86 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "jsc_types.hpp" + +namespace realm { +namespace js { + +template<> +class Protected { + JSGlobalContextRef m_context; + + public: + Protected(const Protected &other) : Protected(other.m_context) {} + Protected(Protected &&other) : m_context(other.m_context) { + other.m_context = nullptr; + } + explicit Protected(JSGlobalContextRef ctx) : m_context(ctx) { + JSGlobalContextRetain(m_context); + } + ~Protected() { + if (m_context) { + JSGlobalContextRelease(m_context); + } + } + operator JSGlobalContextRef() const { + return m_context; + } +}; + +template<> +class Protected { + JSGlobalContextRef m_context; + JSValueRef m_value; + + public: + Protected(const Protected &other) : Protected(other.m_context, other.m_value) {} + Protected(Protected &&other) : m_context(other.m_context), m_value(other.m_value) { + other.m_context = nullptr; + other.m_value = nullptr; + } + Protected(JSContextRef ctx, JSValueRef value) : m_context(JSContextGetGlobalContext(ctx)), m_value(value) { + JSValueProtect(m_context, m_value); + } + ~Protected() { + if (m_value) { + JSValueUnprotect(m_context, m_value); + } + } + operator JSValueRef() const { + return m_value; + } +}; + +template<> +class Protected : public Protected { + public: + Protected(const Protected &other) : Protected(other) {} + Protected(Protected &&other) : Protected(std::move(other)) {} + Protected(JSContextRef ctx, JSObjectRef value) : Protected(ctx, value) {} + + operator JSObjectRef() const { + JSValueRef value = static_cast(*this); + return (JSObjectRef)value; + } +}; + +} // js +} // realm diff --git a/src/jsc/jsc_return_value.hpp b/src/jsc/jsc_return_value.hpp new file mode 100644 index 00000000..1974e384 --- /dev/null +++ b/src/jsc/jsc_return_value.hpp @@ -0,0 +1,64 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "jsc_types.hpp" + +namespace realm { +namespace js { + +template<> +class ReturnValue { + const JSContextRef m_context; + JSValueRef m_value = nullptr; + + public: + ReturnValue(JSContextRef ctx) : m_context(ctx) {} + + void set(const JSValueRef &value) { + m_value = value; + } + void set(const std::string &string) { + m_value = JSValueMakeString(m_context, jsc::String(string)); + } + void set(bool boolean) { + m_value = JSValueMakeBoolean(m_context, boolean); + } + void set(double number) { + m_value = JSValueMakeNumber(m_context, number); + } + void set(int32_t number) { + m_value = JSValueMakeNumber(m_context, number); + } + void set(uint32_t number) { + m_value = JSValueMakeNumber(m_context, number); + } + void set_null() { + m_value = JSValueMakeNull(m_context); + } + void set_undefined() { + m_value = JSValueMakeUndefined(m_context); + } + operator JSValueRef() const { + return m_value; + } +}; + +} // js +} // realm diff --git a/src/jsc/jsc_string.hpp b/src/jsc/jsc_string.hpp new file mode 100644 index 00000000..521b5c70 --- /dev/null +++ b/src/jsc/jsc_string.hpp @@ -0,0 +1,59 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "jsc_types.hpp" + +namespace realm { +namespace js { + +template<> +class String { + using StringType = String; + + JSStringRef m_str; + + public: + String(const char *s) : m_str(JSStringCreateWithUTF8CString(s)) {} + String(const JSStringRef &s) : m_str(JSStringRetain(s)) {} + String(const std::string &str) : String(str.c_str()) {} + String(const StringType &o) : String(o.m_str) {} + String(StringType &&o) : m_str(o.m_str) { + o.m_str = nullptr; + } + ~String() { + if (m_str) { + JSStringRelease(m_str); + } + } + + operator JSStringRef() const { + return m_str; + } + operator std::string() const { + size_t max_size = JSStringGetMaximumUTF8CStringSize(m_str); + std::string string; + string.resize(max_size); + string.resize(JSStringGetUTF8CString(m_str, &string[0], max_size) - 1); + return string; + } +}; + +} // js +} // realm diff --git a/src/jsc/jsc_types.hpp b/src/jsc/jsc_types.hpp new file mode 100644 index 00000000..07c36e48 --- /dev/null +++ b/src/jsc/jsc_types.hpp @@ -0,0 +1,62 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 + +#include "js_types.hpp" + +namespace realm { +namespace jsc { + +struct Types { + using Context = JSContextRef; + using GlobalContext = JSGlobalContextRef; + using ClassDefinition = JSClassRef; + using Value = JSValueRef; + using Object = JSObjectRef; + using String = JSStringRef; + using Function = JSObjectRef; + + using ConstructorCallback = JSObjectCallAsConstructorCallback; + using FunctionCallback = JSObjectCallAsFunctionCallback; + using PropertyGetterCallback = JSObjectGetPropertyCallback; + using PropertySetterCallback = JSObjectSetPropertyCallback; + using IndexPropertyGetterCallback = JSValueRef (*)(JSContextRef, JSObjectRef, uint32_t, JSValueRef*); + using IndexPropertySetterCallback = bool (*)(JSContextRef, JSObjectRef, uint32_t, JSValueRef, JSValueRef*); + using StringPropertyGetterCallback = JSObjectGetPropertyCallback; + using StringPropertySetterCallback = JSObjectSetPropertyCallback; + using StringPropertyEnumeratorCallback = JSObjectGetPropertyNamesCallback; +}; + +template +class ObjectWrap; + +using String = js::String; +using Context = js::Context; +using Value = js::Value; +using Function = js::Function; +using Object = js::Object; +using Exception = js::Exception; +using ReturnValue = js::ReturnValue; + +} // jsc +} // realm diff --git a/src/jsc/jsc_value.hpp b/src/jsc/jsc_value.hpp new file mode 100644 index 00000000..d2df79c8 --- /dev/null +++ b/src/jsc/jsc_value.hpp @@ -0,0 +1,194 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "jsc_types.hpp" + +namespace realm { +namespace js { + +static inline bool is_object_of_type(JSContextRef ctx, JSValueRef value, jsc::String type) { + JSObjectRef global_object = JSContextGetGlobalObject(ctx); + JSValueRef exception = nullptr; + JSValueRef constructor = JSObjectGetProperty(ctx, global_object, type, &exception); + if (exception) { + throw jsc::Exception(ctx, exception); + } + + bool result = JSValueIsInstanceOfConstructor(ctx, value, jsc::Value::validated_to_constructor(ctx, constructor), &exception); + if (exception) { + throw jsc::Exception(ctx, exception); + } + + return result; +} + +template<> +inline bool jsc::Value::is_array(JSContextRef ctx, const JSValueRef &value) { + // JSValueIsArray() is not available until iOS 9. + static const jsc::String type = "Array"; + return is_object_of_type(ctx, value, type); +} + +template<> +inline bool jsc::Value::is_array_buffer(JSContextRef ctx, const JSValueRef &value) { + static const jsc::String type = "ArrayBuffer"; + return is_object_of_type(ctx, value, type); +} + +template<> +inline bool jsc::Value::is_date(JSContextRef ctx, const JSValueRef &value) { + static const jsc::String type = "Date"; + return is_object_of_type(ctx, value, type); +} + +template<> +inline bool jsc::Value::is_boolean(JSContextRef ctx, const JSValueRef &value) { + return JSValueIsBoolean(ctx, value); +} + +template<> +inline bool jsc::Value::is_constructor(JSContextRef ctx, const JSValueRef &value) { + return JSValueIsObject(ctx, value) && JSObjectIsConstructor(ctx, (JSObjectRef)value); +} + +template<> +inline bool jsc::Value::is_function(JSContextRef ctx, const JSValueRef &value) { + return JSValueIsObject(ctx, value) && JSObjectIsFunction(ctx, (JSObjectRef)value); +} + +template<> +inline bool jsc::Value::is_null(JSContextRef ctx, const JSValueRef &value) { + return JSValueIsNull(ctx, value); +} + +template<> +inline bool jsc::Value::is_number(JSContextRef ctx, const JSValueRef &value) { + return JSValueIsNumber(ctx, value); +} + +template<> +inline bool jsc::Value::is_object(JSContextRef ctx, const JSValueRef &value) { + return JSValueIsObject(ctx, value); +} + +template<> +inline bool jsc::Value::is_string(JSContextRef ctx, const JSValueRef &value) { + return JSValueIsString(ctx, value); +} + +template<> +inline bool jsc::Value::is_undefined(JSContextRef ctx, const JSValueRef &value) { + return JSValueIsUndefined(ctx, value); +} + +template<> +inline bool jsc::Value::is_valid(const JSValueRef &value) { + return value != nullptr; +} + +template<> +inline JSValueRef jsc::Value::from_boolean(JSContextRef ctx, bool boolean) { + return JSValueMakeBoolean(ctx, boolean); +} + +template<> +inline JSValueRef jsc::Value::from_null(JSContextRef ctx) { + return JSValueMakeNull(ctx); +} + +template<> +inline JSValueRef jsc::Value::from_number(JSContextRef ctx, double number) { + return JSValueMakeNumber(ctx, number); +} + +template<> +inline JSValueRef jsc::Value::from_string(JSContextRef ctx, const jsc::String &string) { + return JSValueMakeString(ctx, string); +} + +template<> +inline JSValueRef jsc::Value::from_undefined(JSContextRef ctx) { + return JSValueMakeUndefined(ctx); +} + +template<> +inline bool jsc::Value::to_boolean(JSContextRef ctx, const JSValueRef &value) { + return JSValueToBoolean(ctx, value); +} + +template<> +inline double jsc::Value::to_number(JSContextRef ctx, const JSValueRef &value) { + JSValueRef exception = nullptr; + double number = JSValueToNumber(ctx, value, &exception); + if (exception) { + throw jsc::Exception(ctx, exception); + } + if (isnan(number)) { + throw std::invalid_argument("Value not convertible to a number."); + } + return number; +} + +template<> +inline jsc::String jsc::Value::to_string(JSContextRef ctx, const JSValueRef &value) { + JSValueRef exception = nullptr; + jsc::String string = JSValueToStringCopy(ctx, value, &exception); + + // Since the string's retain value is +2 here, we need to manually release it before returning. + JSStringRelease(string); + + if (exception) { + throw jsc::Exception(ctx, exception); + } + return string; +} + +template<> +inline JSObjectRef jsc::Value::to_object(JSContextRef ctx, const JSValueRef &value) { + JSValueRef exception = nullptr; + JSObjectRef object = JSValueToObject(ctx, value, &exception); + if (exception) { + throw jsc::Exception(ctx, exception); + } + return object; +} + +template<> +inline JSObjectRef jsc::Value::to_array(JSContextRef ctx, const JSValueRef &value) { + return to_object(ctx, value); +} + +template<> +inline JSObjectRef jsc::Value::to_constructor(JSContextRef ctx, const JSValueRef &value) { + return to_object(ctx, value); +} + +template<> +inline JSObjectRef jsc::Value::to_date(JSContextRef ctx, const JSValueRef &value) { + return to_object(ctx, value); +} + +template<> +inline JSObjectRef jsc::Value::to_function(JSContextRef ctx, const JSValueRef &value) { + return to_object(ctx, value); +} + +} // js +} // realm diff --git a/src/node/binding.gyp b/src/node/binding.gyp new file mode 100644 index 00000000..e7708d9f --- /dev/null +++ b/src/node/binding.gyp @@ -0,0 +1,54 @@ +{ + "targets": [ + { + "target_name": "realm", + "sources": [ + "node_init.cpp", + "../js_realm.cpp", + "../ios/platform.mm", + "../object-store/src/index_set.cpp", + "../object-store/src/list.cpp", + "../object-store/src/object_schema.cpp", + "../object-store/src/object_store.cpp", + "../object-store/src/results.cpp", + "../object-store/src/schema.cpp", + "../object-store/src/shared_realm.cpp", + "../object-store/src/impl/async_query.cpp", + "../object-store/src/impl/transact_log_handler.cpp", + "../object-store/src/impl/realm_coordinator.cpp", + "../object-store/src/impl/apple/external_commit_helper.cpp", + "../object-store/src/impl/apple/weak_realm_notifier.cpp", + "../object-store/src/parser/parser.cpp", + "../object-store/src/parser/query_builder.cpp" + ], + "include_dirs": [ + "..", + "../object-store/src", + "../object-store/src/impl", + "../object-store/src/impl/apple", + "../object-store/src/parser", + "../object-store/external/pegtl", + "../../core/include", + "../../node_modules/nan" + ], + "library_dirs": [ + "$(srcdir)/../../core" + ], + "defines": ["REALM_HAVE_CONFIG"], + "cflags_cc": ["-fexceptions", "-frtti", "-std=c++14"], + "ldflags": ["-lrealm"], + "xcode_settings": { + "CLANG_CXX_LANGUAGE_STANDARD": "c++14", + "CLANG_CXX_LIBRARY": "libc++", + "MACOSX_DEPLOYMENT_TARGET": "10.8", + "OTHER_CPLUSPLUSFLAGS": ["-fexceptions", "-frtti"], + "OTHER_LDFLAGS": ["-lrealm", "-framework", "Foundation"] + }, + "configurations": { + "Debug": { + "defines": ["DEBUG=1"] + } + } + } + ] +} diff --git a/src/node/node_class.hpp b/src/node/node_class.hpp new file mode 100644 index 00000000..757e6228 --- /dev/null +++ b/src/node/node_class.hpp @@ -0,0 +1,404 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "node_types.hpp" + +#include "js_class.hpp" +#include "js_util.hpp" + +namespace realm { +namespace node { + +template +using ClassDefinition = js::ClassDefinition; + +using ConstructorType = js::ConstructorType; +using MethodType = js::MethodType; +using PropertyType = js::PropertyType; +using IndexPropertyType = js::IndexPropertyType; +using StringPropertyType = js::StringPropertyType; + +template +class ObjectWrap : public Nan::ObjectWrap { + using Internal = typename ClassType::Internal; + using ParentClassType = typename ClassType::Parent; + + public: + static v8::Local create_constructor(v8::Isolate*); + static v8::Local create_instance(v8::Isolate*, Internal* = nullptr); + + static v8::Local get_template() { + static Nan::Persistent js_template(create_template()); + return Nan::New(js_template); + } + + static bool has_instance(v8::Isolate* isolate, const v8::Local &value) { + return get_template()->HasInstance(value); + } + + operator Internal*() const { + return m_object.get(); + } + + ObjectWrap& operator=(Internal* object) { + if (m_object.get() != object) { + m_object = std::unique_ptr(object); + } + return *this; + } + + private: + static ClassType s_class; + + std::unique_ptr m_object; + + ObjectWrap(Internal* object = nullptr) : m_object(object) {} + + static v8::Local create_template(); + + static void setup_method(v8::Local, const std::string &, Nan::FunctionCallback); + static void setup_static_method(v8::Local, const std::string &, Nan::FunctionCallback); + + template + static void setup_property(v8::Local, const std::string &, const PropertyType &); + + static void construct(Nan::NAN_METHOD_ARGS_TYPE); + static void get_indexes(Nan::NAN_INDEX_ENUMERATOR_ARGS_TYPE); + static void set_property(v8::Local, v8::Local, Nan::NAN_PROPERTY_SETTER_ARGS_TYPE); + + static 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()); + } + + static 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()); + } + + static void get_nonexistent_property(v8::Local, Nan::NAN_PROPERTY_GETTER_ARGS_TYPE) { + // Do nothing. This function exists only to prevent a crash where it is used. + } +}; + +template<> +class ObjectWrap { + public: + using Internal = void; + + static v8::Local get_template() { + return v8::Local();; + } +}; + +// This helper function is needed outside the scope of the ObjectWrap class as well. +static inline std::vector> get_arguments(const Nan::FunctionCallbackInfo &info) { + int count = info.Length(); + std::vector> arguments; + arguments.reserve(count); + + for (int i = 0; i < count; i++) { + arguments.push_back(info[i]); + } + + return arguments; +} + +// The static class variable must be defined as well. +template +ClassType ObjectWrap::s_class; + +template +inline v8::Local ObjectWrap::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); +} + +template +inline v8::Local ObjectWrap::create_instance(v8::Isolate* isolate, Internal* internal) { + 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); +} + +template +inline v8::Local ObjectWrap::create_template() { + Nan::EscapableHandleScope scope; + + v8::Local tpl = Nan::New(construct); + v8::Local instance_tpl = tpl->InstanceTemplate(); + v8::Local name = Nan::New(s_class.name).ToLocalChecked(); + + tpl->SetClassName(name); + instance_tpl->SetInternalFieldCount(1); + + v8::Local super_tpl = ObjectWrap::get_template(); + 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); + } + + if (s_class.index_accessor.getter) { + auto &index_accessor = s_class.index_accessor; + Nan::SetIndexedPropertyHandler(instance_tpl, index_accessor.getter, index_accessor.setter ?: set_readonly_index, 0, 0, get_indexes); + } + 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 ?: get_nonexistent_property, set_property, 0, 0, string_accessor.enumerator); + } + + return scope.Escape(tpl); +} + +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_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); +} + +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_name = Nan::New(name).ToLocalChecked(); + + tpl->Set(fn_name, fn, v8::PropertyAttribute::DontEnum); + fn->SetName(fn_name); +} + +template +template +inline void ObjectWrap::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 +inline void ObjectWrap::construct(Nan::NAN_METHOD_ARGS_TYPE info) { + if (!info.IsConstructCall()) { + Nan::ThrowError("Constructor must be called with new"); + } + if (s_class.constructor) { + auto isolate = info.GetIsolate(); + auto arguments = get_arguments(info); + v8::Local this_object = info.This(); + info.GetReturnValue().Set(this_object); + + auto wrap = new ObjectWrap(); + wrap->Wrap(this_object); + + try { + s_class.constructor(isolate, this_object, arguments.size(), arguments.data()); + } + catch (std::exception &e) { + Nan::ThrowError(node::Exception::value(isolate, e)); + } + } + else { + Nan::ThrowError("Illegal constructor"); + } +} + +template +inline void ObjectWrap::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); +} + +template +inline void ObjectWrap::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); + } +} + +} // node + +namespace js { + +template +class ObjectWrap : public node::ObjectWrap {}; + +template +void wrap(Nan::NAN_METHOD_ARGS_TYPE info) { + v8::Isolate* isolate = info.GetIsolate(); + node::ReturnValue return_value(info.GetReturnValue()); + auto arguments = node::get_arguments(info); + + try { + F(isolate, info.This(), arguments.size(), arguments.data(), return_value); + } + catch (std::exception &e) { + Nan::ThrowError(node::Exception::value(isolate, e)); + } +} + +template +void wrap(v8::Local property, Nan::NAN_GETTER_ARGS_TYPE info) { + v8::Isolate* isolate = info.GetIsolate(); + node::ReturnValue return_value(info.GetReturnValue()); + try { + F(isolate, info.This(), return_value); + } + catch (std::exception &e) { + Nan::ThrowError(node::Exception::value(isolate, e)); + } +} + +template +void wrap(v8::Local property, v8::Local value, Nan::NAN_SETTER_ARGS_TYPE info) { + v8::Isolate* isolate = info.GetIsolate(); + try { + F(isolate, info.This(), value); + } + catch (std::exception &e) { + Nan::ThrowError(node::Exception::value(isolate, e)); + } +} + +template +void wrap(uint32_t index, Nan::NAN_INDEX_GETTER_ARGS_TYPE info) { + v8::Isolate* isolate = info.GetIsolate(); + node::ReturnValue return_value(info.GetReturnValue()); + 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)); + } +} + +template +void wrap(uint32_t index, v8::Local value, Nan::NAN_INDEX_SETTER_ARGS_TYPE info) { + v8::Isolate* isolate = info.GetIsolate(); + try { + 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)); + } +} + +template +void wrap(v8::Local property, Nan::NAN_PROPERTY_GETTER_ARGS_TYPE info) { + v8::Isolate* isolate = info.GetIsolate(); + node::ReturnValue return_value(info.GetReturnValue()); + try { + F(isolate, info.This(), property, return_value); + } + catch (std::exception &e) { + Nan::ThrowError(node::Exception::value(isolate, e)); + } +} + +template +void wrap(v8::Local property, v8::Local value, Nan::NAN_PROPERTY_SETTER_ARGS_TYPE info) { + v8::Isolate* isolate = info.GetIsolate(); + try { + 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)); + } +} + +template +void wrap(Nan::NAN_PROPERTY_ENUMERATOR_ARGS_TYPE info) { + auto names = F(info.GetIsolate(), info.This()); + int count = (int)names.size(); + v8::Local array = Nan::New(count); + + for (int i = 0; i < count; i++) { + Nan::Set(array, i, v8::Local(names[i])); + } + + info.GetReturnValue().Set(array); +} + +} // js +} // realm diff --git a/src/node/node_context.hpp b/src/node/node_context.hpp new file mode 100644 index 00000000..b77b1b64 --- /dev/null +++ b/src/node/node_context.hpp @@ -0,0 +1,32 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "node_types.hpp" + +namespace realm { +namespace js { + +template<> +inline v8::Local node::Context::get_global_context(v8::Isolate* isolate) { + return isolate->GetCurrentContext(); +} + +} // js +} // realm diff --git a/src/node/node_dummy.cpp b/src/node/node_dummy.cpp new file mode 100644 index 00000000..c4522d55 --- /dev/null +++ b/src/node/node_dummy.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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. +// +//////////////////////////////////////////////////////////////////////////// + +// 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_exception.hpp b/src/node/node_exception.hpp new file mode 100644 index 00000000..17eca27f --- /dev/null +++ b/src/node/node_exception.hpp @@ -0,0 +1,32 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "node_types.hpp" + +namespace realm { +namespace js { + +template<> +inline v8::Local node::Exception::value(v8::Isolate* isolate, const std::string &message) { + return Nan::Error(message.c_str()); +} + +} // js +} // realm diff --git a/src/node/node_function.hpp b/src/node/node_function.hpp new file mode 100644 index 00000000..f983ef23 --- /dev/null +++ b/src/node/node_function.hpp @@ -0,0 +1,49 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "node_types.hpp" + +namespace realm { +namespace js { + +template<> +inline v8::Local node::Function::call(v8::Isolate* isolate, const v8::Local &function, const v8::Local &this_object, size_t argc, const v8::Local arguments[]) { + Nan::TryCatch trycatch; + auto result = Nan::Call(function, this_object, (int)argc, const_cast*>(arguments)); + + if (trycatch.HasCaught()) { + throw node::Exception(isolate, trycatch.Exception()); + } + return result.ToLocalChecked(); +} + +template<> +inline v8::Local node::Function::construct(v8::Isolate* isolate, const v8::Local &function, size_t argc, const v8::Local arguments[]) { + Nan::TryCatch trycatch; + auto result = Nan::NewInstance(function, (int)argc, const_cast*>(arguments)); + + if (trycatch.HasCaught()) { + throw node::Exception(isolate, trycatch.Exception()); + } + return result.ToLocalChecked(); +} + +} // js +} // realm diff --git a/src/node/node_init.cpp b/src/node/node_init.cpp new file mode 100644 index 00000000..da21c3ff --- /dev/null +++ b/src/node/node_init.cpp @@ -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. +// +//////////////////////////////////////////////////////////////////////////// + +#include "node_init.hpp" + +namespace realm { +namespace node { + +static void init(v8::Local exports) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Local realm_constructor = js::Realm::create_constructor(isolate); + + Nan::Set(exports, realm_constructor->GetName(), realm_constructor); +} + +} // node +} // realm + +NODE_MODULE(Realm, realm::node::init); diff --git a/src/node/node_init.hpp b/src/node/node_init.hpp new file mode 100644 index 00000000..06980428 --- /dev/null +++ b/src/node/node_init.hpp @@ -0,0 +1,31 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "node_string.hpp" +#include "node_protected.hpp" +#include "node_context.hpp" +#include "node_value.hpp" +#include "node_object.hpp" +#include "node_function.hpp" +#include "node_exception.hpp" +#include "node_return_value.hpp" +#include "node_object_accessor.hpp" + +#include "js_realm.hpp" diff --git a/src/node/node_object.hpp b/src/node/node_object.hpp new file mode 100644 index 00000000..c1fa9dd0 --- /dev/null +++ b/src/node/node_object.hpp @@ -0,0 +1,159 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "node_types.hpp" + +namespace realm { +namespace js { + +template<> +inline bool node::Object::has_property(v8::Isolate* isolate, const v8::Local &object, const node::String &key) { + return Nan::Has(object, key).FromMaybe(false); +} + +template<> +inline bool node::Object::has_property(v8::Isolate* isolate, const v8::Local &object, uint32_t index) { + return Nan::Has(object, index).FromMaybe(false); +} + +template<> +inline v8::Local node::Object::get_property(v8::Isolate* isolate, const v8::Local &object, const node::String &key) { + Nan::TryCatch trycatch; + auto value = Nan::Get(object, v8::Local(key)); + + if (trycatch.HasCaught()) { + throw node::Exception(isolate, trycatch.Exception()); + } + return value.ToLocalChecked(); +} + +template<> +inline v8::Local node::Object::get_property(v8::Isolate* isolate, const v8::Local &object, uint32_t index) { + Nan::TryCatch trycatch; + auto value = Nan::Get(object, index); + + if (trycatch.HasCaught()) { + throw node::Exception(isolate, trycatch.Exception()); + } + return value.ToLocalChecked(); +} + +template<> +inline void node::Object::set_property(v8::Isolate* isolate, const v8::Local &object, const node::String &key, const v8::Local &value, PropertyAttributes attributes) { + Nan::TryCatch trycatch; + + if (attributes) { + Nan::ForceSet(object, v8::Local(key), value, v8::PropertyAttribute(attributes)); + } + else { + Nan::Set(object, v8::Local(key), value); + } + + if (trycatch.HasCaught()) { + throw node::Exception(isolate, trycatch.Exception()); + } +} + +template<> +inline void node::Object::set_property(v8::Isolate* isolate, const v8::Local &object, uint32_t index, const v8::Local &value) { + Nan::TryCatch trycatch; + Nan::Set(object, index, value); + + if (trycatch.HasCaught()) { + throw node::Exception(isolate, trycatch.Exception()); + } +} + +template<> +inline std::vector node::Object::get_property_names(v8::Isolate* isolate, const v8::Local &object) { + auto maybe_array = Nan::GetPropertyNames(object); + if (maybe_array.IsEmpty()) { + return std::vector(); + } + + auto array = maybe_array.ToLocalChecked(); + uint32_t count = array->Length(); + + std::vector names; + names.reserve(count); + + for (uint32_t i = 0; i < count; i++) { + names.push_back(array->Get(i)->ToString()); + } + + return names; +} + +template<> +inline v8::Local node::Object::get_prototype(v8::Isolate* isolate, const v8::Local &object) { + return object->GetPrototype(); +} + +template<> +inline void node::Object::set_prototype(v8::Isolate* isolate, const v8::Local &object, const v8::Local &prototype) { + Nan::SetPrototype(object, prototype); +} + +template<> +inline v8::Local node::Object::create_empty(v8::Isolate* isolate) { + return Nan::New(); +} + +template<> +inline v8::Local node::Object::create_array(v8::Isolate* isolate, uint32_t length, const v8::Local values[]) { + v8::Local array = Nan::New(length); + for (uint32_t i = 0; i < length; i++) { + set_property(isolate, array, i, values[i]); + } + return array; +} + +template<> +inline v8::Local node::Object::create_date(v8::Isolate* isolate, double time) { + return Nan::New(time).ToLocalChecked(); +} + +template<> +template +inline v8::Local node::Object::create_instance(v8::Isolate* isolate, typename ClassType::Internal* internal) { + return node::ObjectWrap::create_instance(isolate, internal); +} + +template<> +template +inline bool node::Object::is_instance(v8::Isolate* isolate, const v8::Local &object) { + return node::ObjectWrap::has_instance(isolate, object); +} + +template<> +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, typename ClassType::Internal* ptr) { + auto wrap = Nan::ObjectWrap::Unwrap>(object); + *wrap = ptr; +} + +} // js +} // realm diff --git a/src/node/node_object_accessor.hpp b/src/node/node_object_accessor.hpp new file mode 100644 index 00000000..029ffdd0 --- /dev/null +++ b/src/node/node_object_accessor.hpp @@ -0,0 +1,75 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "node_class.hpp" +#include "js_object_accessor.hpp" + +namespace realm { + +// Specialize a native accessor class for Node. +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_protected.hpp b/src/node/node_protected.hpp new file mode 100644 index 00000000..7ae354f1 --- /dev/null +++ b/src/node/node_protected.hpp @@ -0,0 +1,84 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "node_types.hpp" + +namespace realm { +namespace node { + +template +class Protected { + // TODO: Figure out why Nan::CopyablePersistentTraits causes a build failure. + Nan::Persistent> m_value; + + public: + Protected(v8::Local value) : m_value(value) {} + + operator v8::Local() const { + return Nan::New(m_value); + } + bool operator==(const v8::Local &other) const { + return m_value == other; + } + bool operator!=(const v8::Local &other) const { + return m_value != other; + } + bool operator==(const Protected &other) const { + return m_value == other.m_value; + } + bool operator!=(const Protected &other) const { + return m_value != other.m_value; + } +}; + +} // node + +namespace js { + +template<> +class Protected : public node::Protected { + public: + Protected(v8::Local ctx) : node::Protected(ctx) {} + + operator v8::Isolate*() const { + return v8::Local(*this)->GetIsolate(); + } +}; + +template<> +class Protected : public node::Protected { + public: + Protected(v8::Isolate* isolate, v8::Local value) : node::Protected(value) {} +}; + +template<> +class Protected : public node::Protected { + public: + Protected(v8::Isolate* isolate, v8::Local object) : node::Protected(object) {} +}; + +template<> +class Protected : public node::Protected { + public: + Protected(v8::Isolate* isolate, v8::Local object) : node::Protected(object) {} +}; + +} // js +} // realm diff --git a/src/node/node_return_value.hpp b/src/node/node_return_value.hpp new file mode 100644 index 00000000..310cc854 --- /dev/null +++ b/src/node/node_return_value.hpp @@ -0,0 +1,65 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "node_types.hpp" + +namespace realm { +namespace js { + +template<> +class ReturnValue { + Nan::ReturnValue m_value; + + public: + ReturnValue(Nan::ReturnValue value) : m_value(value) {} + + void set(const v8::Local &value) { + m_value.Set(value); + } + void set(const std::string &string) { + if (string.empty()) { + m_value.SetEmptyString(); + } + else { + m_value.Set(Nan::New(string).ToLocalChecked()); + } + } + void set(bool boolean) { + m_value.Set(boolean); + } + void set(double number) { + m_value.Set(number); + } + void set(int32_t number) { + m_value.Set(number); + } + void set(uint32_t number) { + m_value.Set(number); + } + void set_null() { + m_value.SetNull(); + } + void set_undefined() { + m_value.SetUndefined(); + } +}; + +} // js +} // realm diff --git a/src/node/node_string.hpp b/src/node/node_string.hpp new file mode 100644 index 00000000..dabe9d07 --- /dev/null +++ b/src/node/node_string.hpp @@ -0,0 +1,45 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "node_types.hpp" + +namespace realm { +namespace js { + +template<> +class String { + std::string m_str; + + public: + String(const char* s) : m_str(s) {} + String(const std::string &s) : m_str(s) {} + String(const v8::Local &s) : m_str(*Nan::Utf8String(s)) {} + String(v8::Local &&s) : String(s) {} + + operator std::string() const { + return m_str; + } + operator v8::Local() const { + return Nan::New(m_str).ToLocalChecked(); + } +}; + +} // js +} // realm diff --git a/src/node/node_types.hpp b/src/node/node_types.hpp new file mode 100644 index 00000000..4220c173 --- /dev/null +++ b/src/node/node_types.hpp @@ -0,0 +1,68 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 +#include + +#include + +#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 { + +struct Types { + using Context = v8::Isolate*; + using GlobalContext = v8::Local; + using Value = v8::Local; + using Object = v8::Local; + using String = v8::Local; + using Function = v8::Local; + + using ConstructorCallback = Nan::FunctionCallback; + using FunctionCallback = Nan::FunctionCallback; + using PropertyGetterCallback = Nan::GetterCallback; + using PropertySetterCallback = Nan::SetterCallback; + using IndexPropertyGetterCallback = Nan::IndexGetterCallback; + using IndexPropertySetterCallback = Nan::IndexSetterCallback; + using StringPropertyGetterCallback = Nan::PropertyGetterCallback; + using StringPropertySetterCallback = Nan::PropertySetterCallback; + using StringPropertyEnumeratorCallback = Nan::PropertyEnumeratorCallback; +}; + +template +class ObjectWrap; + +using String = js::String; +using Context = js::Context; +using Value = js::Value; +using Function = js::Function; +using Object = js::Object; +using Exception = js::Exception; +using ReturnValue = js::ReturnValue; + +} // node +} // realm diff --git a/src/node/node_value.hpp b/src/node/node_value.hpp new file mode 100644 index 00000000..0a25ad7f --- /dev/null +++ b/src/node/node_value.hpp @@ -0,0 +1,169 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "node_types.hpp" + +namespace realm { +namespace js { + +template<> +inline bool node::Value::is_array(v8::Isolate* isolate, const v8::Local &value) { + return value->IsArray(); +} + +template<> +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<> +inline bool node::Value::is_date(v8::Isolate* isolate, const v8::Local &value) { + return value->IsDate(); +} + +template<> +inline bool node::Value::is_boolean(v8::Isolate* isolate, const v8::Local &value) { + return value->IsBoolean(); +} + +template<> +inline bool node::Value::is_constructor(v8::Isolate* isolate, const v8::Local &value) { + return value->IsFunction(); +} + +template<> +inline bool node::Value::is_function(v8::Isolate* isolate, const v8::Local &value) { + return value->IsFunction(); +} + +template<> +inline bool node::Value::is_null(v8::Isolate* isolate, const v8::Local &value) { + return value->IsNull(); +} + +template<> +inline bool node::Value::is_number(v8::Isolate* isolate, const v8::Local &value) { + return value->IsNumber(); +} + +template<> +inline bool node::Value::is_object(v8::Isolate* isolate, const v8::Local &value) { + return value->IsObject(); +} + +template<> +inline bool node::Value::is_string(v8::Isolate* isolate, const v8::Local &value) { + return value->IsString(); +} + +template<> +inline bool node::Value::is_undefined(v8::Isolate* isolate, const v8::Local &value) { + return value->IsUndefined(); +} + +template<> +inline bool node::Value::is_valid(const v8::Local &value) { + return !value.IsEmpty(); +} + +template<> +inline v8::Local node::Value::from_boolean(v8::Isolate* isolate, bool boolean) { + return Nan::New(boolean); +} + +template<> +inline v8::Local node::Value::from_null(v8::Isolate* isolate) { + return Nan::Null(); +} + +template<> +inline v8::Local node::Value::from_number(v8::Isolate* isolate, double number) { + return Nan::New(number); +} + +template<> +inline v8::Local node::Value::from_string(v8::Isolate* isolate, const node::String &string) { + return v8::Local(string); +} + +template<> +inline v8::Local node::Value::from_undefined(v8::Isolate* isolate) { + return Nan::Undefined(); +} + +template<> +inline bool node::Value::to_boolean(v8::Isolate* isolate, const v8::Local &value) { + return Nan::To(value).FromMaybe(false); +} + +template<> +inline double node::Value::to_number(v8::Isolate* isolate, const v8::Local &value) { + double number = Nan::To(value).FromMaybe(NAN); + if (isnan(number)) { + throw std::invalid_argument("Value not convertible to a number."); + } + return number; +} + +template<> +inline node::String node::Value::to_string(v8::Isolate* isolate, const v8::Local &value) { + return value->ToString(); +} + +template<> +inline v8::Local node::Value::to_object(v8::Isolate* isolate, const v8::Local &value) { + return Nan::To(value).FromMaybe(v8::Local()); +} + +template<> +inline v8::Local node::Value::to_array(v8::Isolate* isolate, const v8::Local &value) { + return to_object(isolate, value); +} + +template<> +inline v8::Local node::Value::to_date(v8::Isolate* isolate, const v8::Local &value) { + return to_object(isolate, value); +} + +template<> +inline v8::Local node::Value::to_function(v8::Isolate* isolate, const v8::Local &value) { + return value->IsFunction() ? v8::Local::Cast(value) : v8::Local(); +} + +template<> +inline v8::Local node::Value::to_constructor(v8::Isolate* isolate, const v8::Local &value) { + return to_function(isolate, value); +} + +} // js +} // realm diff --git a/src/rpc.cpp b/src/rpc.cpp index d6d70fb6..e30a8efd 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -16,26 +16,25 @@ // //////////////////////////////////////////////////////////////////////////// -#include "rpc.hpp" - +#include #include #include #include -#include "js_init.h" -#include "js_object.hpp" -#include "js_results.hpp" -#include "js_list.hpp" -#include "js_realm.hpp" -#include "js_util.hpp" + +#include "rpc.hpp" + +#include "jsc_init.hpp" +#include "jsc_types.hpp" #include "base64.hpp" #include "object_accessor.hpp" #include "shared_realm.hpp" #include "results.hpp" -#include - -using RJSAccessor = realm::NativeAccessor; -using namespace realm_js; + +using namespace realm; +using namespace realm::rpc; + +using Accessor = NativeAccessor; static const char * const RealmObjectTypesData = "data"; static const char * const RealmObjectTypesDate = "date"; @@ -59,9 +58,8 @@ RPCServer::RPCServer() { m_requests["/create_session"] = [this](const json dict) { RJSInitializeInContext(m_context); - JSStringRef realm_string = RJSStringForString("Realm"); - JSObjectRef realm_constructor = RJSValidatedObjectProperty(m_context, JSContextGetGlobalObject(m_context), realm_string); - JSStringRelease(realm_string); + jsc::String realm_string = "Realm"; + JSObjectRef realm_constructor = jsc::Object::validated_get_constructor(m_context, JSContextGetGlobalObject(m_context), realm_string); m_session_id = store_object(realm_constructor); return (json){{"result", m_session_id}}; @@ -80,88 +78,72 @@ RPCServer::RPCServer() { arg_values[i] = deserialize_json_value(args[i]); } - JSValueRef exception = NULL; - JSObjectRef realm_object = JSObjectCallAsConstructor(m_context, realm_constructor, arg_count, arg_values, &exception); - if (exception) { - return (json){{"error", RJSStringForValue(m_context, exception)}}; - } - + JSObjectRef realm_object = jsc::Function::construct(m_context, realm_constructor, arg_count, arg_values); RPCObjectID realm_id = store_object(realm_object); return (json){{"result", realm_id}}; }; m_requests["/begin_transaction"] = [this](const json dict) { RPCObjectID realm_id = dict["realmId"].get(); - RJSGetInternal(m_objects[realm_id])->get()->begin_transaction(); + SharedRealm realm = *jsc::Object::get_internal>(m_objects[realm_id]); + + realm->begin_transaction(); return json::object(); }; m_requests["/cancel_transaction"] = [this](const json dict) { RPCObjectID realm_id = dict["realmId"].get(); - RJSGetInternal(m_objects[realm_id])->get()->cancel_transaction(); + SharedRealm realm = *jsc::Object::get_internal>(m_objects[realm_id]); + + realm->cancel_transaction(); return json::object(); }; m_requests["/commit_transaction"] = [this](const json dict) { RPCObjectID realm_id = dict["realmId"].get(); - RJSGetInternal(m_objects[realm_id])->get()->commit_transaction(); + SharedRealm realm = *jsc::Object::get_internal>(m_objects[realm_id]); + + realm->commit_transaction(); return json::object(); }; m_requests["/call_method"] = [this](const json dict) { JSObjectRef object = m_objects[dict["id"].get()]; - JSStringRef method_string = RJSStringForString(dict["name"].get()); - JSObjectRef function = RJSValidatedObjectProperty(m_context, object, method_string); - JSStringRelease(method_string); + std::string method_string = dict["name"].get(); + JSObjectRef function = jsc::Object::validated_get_function(m_context, object, method_string); json args = dict["arguments"]; - size_t count = args.size(); - JSValueRef arg_values[count]; - for (size_t i = 0; i < count; i++) { + size_t arg_count = args.size(); + JSValueRef arg_values[arg_count]; + for (size_t i = 0; i < arg_count; i++) { arg_values[i] = deserialize_json_value(args[i]); } - JSValueRef exception = NULL; - JSValueRef result = JSObjectCallAsFunction(m_context, function, object, count, arg_values, &exception); - if (exception) { - return (json){{"error", RJSStringForValue(m_context, exception)}}; - } + JSValueRef result = jsc::Function::call(m_context, function, object, arg_count, arg_values); return (json){{"result", serialize_json_value(result)}}; }; m_requests["/get_property"] = [this](const json dict) { RPCObjectID oid = dict["id"].get(); json name = dict["name"]; - JSValueRef value = NULL; - JSValueRef exception = NULL; + JSValueRef value; if (name.is_number()) { - value = JSObjectGetPropertyAtIndex(m_context, m_objects[oid], name.get(), &exception); + value = jsc::Object::get_property(m_context, m_objects[oid], name.get()); } else { - JSStringRef prop_string = RJSStringForString(name.get()); - value = JSObjectGetProperty(m_context, m_objects[oid], prop_string, &exception); - JSStringRelease(prop_string); + value = jsc::Object::get_property(m_context, m_objects[oid], name.get()); } - if (exception) { - return (json){{"error", RJSStringForValue(m_context, exception)}}; - } return (json){{"result", serialize_json_value(value)}}; }; m_requests["/set_property"] = [this](const json dict) { RPCObjectID oid = dict["id"].get(); json name = dict["name"]; JSValueRef value = deserialize_json_value(dict["value"]); - JSValueRef exception = NULL; if (name.is_number()) { - JSObjectSetPropertyAtIndex(m_context, m_objects[oid], name.get(), value, &exception); + jsc::Object::set_property(m_context, m_objects[oid], name.get(), value); } else { - JSStringRef prop_string = RJSStringForString(name.get()); - JSObjectSetProperty(m_context, m_objects[oid], prop_string, value, 0, &exception); - JSStringRelease(prop_string); + jsc::Object::set_property(m_context, m_objects[oid], name.get(), value); } - if (exception) { - return (json){{"error", RJSStringForValue(m_context, exception)}}; - } return json::object(); }; m_requests["/dispose_object"] = [this](const json dict) { @@ -181,7 +163,7 @@ RPCServer::RPCServer() { m_objects.erase(object.first); } JSGarbageCollect(m_context); - RJSClearTestState(); + js::delete_all_realms(); return json::object(); }; } @@ -206,7 +188,7 @@ json RPCServer::perform_request(std::string name, json &args) { return {{"error", "Invalid session ID"}}; } } catch (std::exception &exception) { - return {{"error", (std::string)"exception thrown: " + exception.what()}}; + return {{"error", exception.what()}}; } } @@ -218,34 +200,34 @@ RPCObjectID RPCServer::store_object(JSObjectRef object) { return next_id; } -json RPCServer::serialize_json_value(JSValueRef value) { - switch (JSValueGetType(m_context, value)) { +json RPCServer::serialize_json_value(JSValueRef js_value) { + switch (JSValueGetType(m_context, js_value)) { case kJSTypeUndefined: return json::object(); case kJSTypeNull: return {{"value", json(nullptr)}}; case kJSTypeBoolean: - return {{"value", JSValueToBoolean(m_context, value)}}; + return {{"value", jsc::Value::to_boolean(m_context, js_value)}}; case kJSTypeNumber: - return {{"value", JSValueToNumber(m_context, value, NULL)}}; + return {{"value", jsc::Value::to_number(m_context, js_value)}}; case kJSTypeString: - return {{"value", RJSStringForValue(m_context, value)}}; + return {{"value", jsc::Value::to_string(m_context, js_value)}}; case kJSTypeObject: break; } - JSObjectRef js_object = JSValueToObject(m_context, value, NULL); + JSObjectRef js_object = jsc::Value::validated_to_object(m_context, js_value); - if (JSValueIsObjectOfClass(m_context, value, RJSObjectClass())) { - realm::Object *object = RJSGetInternal(js_object); + if (jsc::Object::is_instance>(m_context, js_object)) { + auto object = jsc::Object::get_internal>(js_object); return { {"type", RealmObjectTypesObject}, {"id", store_object(js_object)}, {"schema", serialize_object_schema(object->get_object_schema())} }; } - else if (JSValueIsObjectOfClass(m_context, value, RJSListClass())) { - realm::List *list = RJSGetInternal(js_object); + else if (jsc::Object::is_instance>(m_context, js_object)) { + auto list = jsc::Object::get_internal>(js_object); return { {"type", RealmObjectTypesList}, {"id", store_object(js_object)}, @@ -253,8 +235,8 @@ json RPCServer::serialize_json_value(JSValueRef value) { {"schema", serialize_object_schema(list->get_object_schema())} }; } - else if (JSValueIsObjectOfClass(m_context, value, RJSResultsClass())) { - realm::Results *results = RJSGetInternal(js_object); + else if (jsc::Object::is_instance>(m_context, js_object)) { + auto results = jsc::Object::get_internal>(js_object); return { {"type", RealmObjectTypesResults}, {"id", store_object(js_object)}, @@ -262,44 +244,40 @@ json RPCServer::serialize_json_value(JSValueRef value) { {"schema", serialize_object_schema(results->get_object_schema())} }; } - else if (RJSIsValueArray(m_context, value)) { - size_t length = RJSValidatedListLength(m_context, js_object); + else if (jsc::Value::is_array(m_context, js_object)) { + uint32_t length = jsc::Object::validated_get_length(m_context, js_object); std::vector array; - for (unsigned int i = 0; i < length; i++) { - array.push_back(serialize_json_value(JSObjectGetPropertyAtIndex(m_context, js_object, i, NULL))); + for (uint32_t i = 0; i < length; i++) { + array.push_back(serialize_json_value(jsc::Object::get_property(m_context, js_object, i))); } return {{"value", array}}; } - else if (RJSIsValueArrayBuffer(m_context, value)) { - std::string data = RJSAccessor::to_binary(m_context, value); + else if (jsc::Value::is_array_buffer(m_context, js_object)) { + std::string data = Accessor::to_binary(m_context, js_value); return { {"type", RealmObjectTypesData}, {"value", base64_encode((unsigned char *)data.data(), data.size())}, }; } - else if (RJSIsValueDate(m_context, value)) { + else if (jsc::Value::is_date(m_context, js_object)) { return { {"type", RealmObjectTypesDate}, - {"value", RJSValidatedValueToNumber(m_context, value)}, + {"value", jsc::Value::to_number(m_context, js_object)}, }; } else { // Serialize this JS object as a plain object since it doesn't match any known types above. - JSPropertyNameArrayRef js_keys = JSObjectCopyPropertyNames(m_context, js_object); - size_t count = JSPropertyNameArrayGetCount(js_keys); + std::vector js_keys = jsc::Object::get_property_names(m_context, js_object); std::vector keys; std::vector values; - for (size_t i = 0; i < count; i++) { - JSStringRef js_key = JSPropertyNameArrayGetNameAtIndex(js_keys, i); - JSValueRef js_value = RJSValidatedPropertyValue(m_context, js_object, js_key); + for (auto &js_key : js_keys) { + JSValueRef js_value = jsc::Object::get_property(m_context, js_object, js_key); - keys.push_back(RJSStringForJSString(js_key)); - values.push_back(serialize_json_value(js_value)); + keys.push_back(js_key); + values.push_back(js_value); } - JSPropertyNameArrayRelease(js_keys); - return { {"type", RealmObjectTypesDictionary}, {"keys", keys}, @@ -312,7 +290,7 @@ json RPCServer::serialize_json_value(JSValueRef value) { json RPCServer::serialize_object_schema(const realm::ObjectSchema &object_schema) { std::vector properties; - for (realm::Property prop : object_schema.properties) { + for (auto &prop : object_schema.properties) { properties.push_back(prop.name); } @@ -322,8 +300,7 @@ json RPCServer::serialize_object_schema(const realm::ObjectSchema &object_schema }; } -JSValueRef RPCServer::deserialize_json_value(const json dict) -{ +JSValueRef RPCServer::deserialize_json_value(const json dict) { json oid = dict["id"]; if (oid.is_number()) { return m_objects[oid.get()]; @@ -331,28 +308,24 @@ JSValueRef RPCServer::deserialize_json_value(const json dict) json value = dict["value"]; json type = dict["type"]; + if (type.is_string()) { std::string type_string = type.get(); + if (type_string == RealmObjectTypesFunction) { // FIXME: Make this actually call the function by its id once we need it to. - JSStringRef js_body = JSStringCreateWithUTF8CString(""); - JSObjectRef js_function = JSObjectMakeFunction(m_context, NULL, 0, NULL, js_body, NULL, 1, NULL); - JSStringRelease(js_body); - - return js_function; + return JSObjectMakeFunction(m_context, NULL, 0, NULL, jsc::String(""), NULL, 1, NULL); } else if (type_string == RealmObjectTypesDictionary) { - JSObjectRef js_object = JSObjectMake(m_context, NULL, NULL); + JSObjectRef js_object = jsc::Object::create_empty(m_context); json keys = dict["keys"]; json values = dict["values"]; size_t count = keys.size(); for (size_t i = 0; i < count; i++) { - JSStringRef js_key = RJSStringForString(keys.at(i)); + std::string js_key = keys.at(i); JSValueRef js_value = deserialize_json_value(values.at(i)); - - JSObjectSetProperty(m_context, js_object, js_key, js_value, 0, NULL); - JSStringRelease(js_key); + jsc::Object::set_property(m_context, js_object, js_key, js_value); } return js_object; @@ -362,35 +335,28 @@ JSValueRef RPCServer::deserialize_json_value(const json dict) if (!base64_decode(value.get(), &bytes)) { throw std::runtime_error("Failed to decode base64 encoded data"); } - return RJSAccessor::from_binary(m_context, realm::BinaryData(bytes)); + return Accessor::from_binary(m_context, realm::BinaryData(bytes)); } else if (type_string == RealmObjectTypesDate) { - JSValueRef exception = NULL; - JSValueRef time = JSValueMakeNumber(m_context, value.get()); - JSObjectRef date = JSObjectMakeDate(m_context, 1, &time, &exception); - - if (exception) { - throw RJSException(m_context, exception); - } - return date; + return jsc::Object::create_date(m_context, value.get()); } else if (type_string == RealmObjectTypesUndefined) { - return JSValueMakeUndefined(m_context); + return jsc::Value::from_undefined(m_context); } assert(0); } if (value.is_null()) { - return JSValueMakeNull(m_context); + return jsc::Value::from_null(m_context); } else if (value.is_boolean()) { - return JSValueMakeBoolean(m_context, value.get()); + return jsc::Value::from_boolean(m_context, value.get()); } else if (value.is_number()) { - return JSValueMakeNumber(m_context, value.get()); + return jsc::Value::from_number(m_context, value.get()); } else if (value.is_string()) { - return RJSValueForString(m_context, value.get()); + return jsc::Value::from_string(m_context, value.get()); } else if (value.is_array()) { size_t count = value.size(); @@ -400,7 +366,7 @@ JSValueRef RPCServer::deserialize_json_value(const json dict) js_values[i] = deserialize_json_value(value.at(i)); } - return JSObjectMakeArray(m_context, count, js_values, NULL); + return jsc::Object::create_array(m_context, (uint32_t)count, js_values); } assert(0); } diff --git a/src/rpc.hpp b/src/rpc.hpp index f0580f4b..ae015c5c 100644 --- a/src/rpc.hpp +++ b/src/rpc.hpp @@ -19,13 +19,13 @@ #pragma once #include "json.hpp" -#include +#include "jsc_types.hpp" namespace realm { - class ObjectSchema; -} -namespace realm_js { +class ObjectSchema; + +namespace rpc { using json = nlohmann::json; using RPCObjectID = u_int64_t; @@ -48,8 +48,8 @@ class RPCServer { json serialize_json_value(JSValueRef value); JSValueRef deserialize_json_value(const json dict); - json serialize_object_schema(const realm::ObjectSchema &objectSchema); + json serialize_object_schema(const ObjectSchema &objectSchema); }; -} - +} // rpc +} // realm diff --git a/tests/index.js b/tests/index.js new file mode 100644 index 00000000..67d9abc8 --- /dev/null +++ b/tests/index.js @@ -0,0 +1,63 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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. +// +//////////////////////////////////////////////////////////////////////////// + +/* eslint-env es6, node */ +/* eslint-disable no-console */ + +'use strict'; + +const mockery = require('mockery'); + +function runTests() { + const RealmTests = require('./js'); + const testNames = RealmTests.getTestNames(); + let passed = true; + + for (let suiteName in testNames) { + 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); + passed = false; + } + finally { + RealmTests.runTest(suiteName, 'afterEach'); + } + } + } + + return passed; +} + +if (require.main == module) { + mockery.enable(); + mockery.warnOnUnregistered(false); + mockery.registerMock('realm', require('..')); + + if (!runTests()) { + process.exit(1); + } +} 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 diff --git a/tests/react-test-app/tests/index.js b/tests/react-test-app/tests/index.js index f40b104a..4e4e8aaa 100644 --- a/tests/react-test-app/tests/index.js +++ b/tests/react-test-app/tests/index.js @@ -19,7 +19,6 @@ 'use strict'; const React = require('react-native'); -const Realm = require('realm'); const RealmTests = require('realm-tests'); RealmTests.registerTests({ @@ -56,8 +55,6 @@ function runTests() { let testNames = RealmTests.getTestNames(); for (let suiteName in testNames) { - let testSuite = RealmTests[suiteName]; - console.log('Starting ' + suiteName); for (let testName of testNames[suiteName]) {