Sync and fine grained notifications

This commit is contained in:
Ari Lazier 2016-10-04 15:02:51 -07:00
parent c8e4dc39c0
commit 9d0df0de3d
61 changed files with 2327 additions and 360 deletions

7
.dir-locals.el Normal file
View File

@ -0,0 +1,7 @@
;; Project specific Emacs settings
((nil . ((c-basic-offset . 4)
(indent-tabs-mode . nil)
(fill-column . 80)
(c-file-style . "ellemtel")
(c-file-offsets . ((innamespace . 0)))
(show-trailing-whitespace . t))))

1
.gitignore vendored
View File

@ -98,6 +98,7 @@ build/
# node.js
node_modules/
npm-debug.log
/compiled/
# Android/IJ
/android/

4
.gitmodules vendored
View File

@ -6,5 +6,5 @@
url = https://github.com/realm/realm-jsdoc.git
[submodule "src/object-store"]
path = src/object-store
url = https://github.com/realm/realm-object-store.git
branch = js
url = git@github.com:realm/realm-object-store-private.git
branch = js-sync

View File

@ -41,7 +41,7 @@ Prerequisites:
First clone this repository:
```
git clone https://github.com/realm/realm-js.git
git clone https://github.com/realm/realm-js-private.git
```
Then in the cloned directory:
@ -60,6 +60,15 @@ To build for Android:
- `./gradlew publishAndroid`
- The compiled version of the Android module is here: `<project-root>/android`
To build for Node:
- `REALM_CORE_PREFIX=/path/to/realm-core REALM_SYNC_PREFIX=/path/to/realm-sync npm install`
The `REALM_*_PREFIX` paths need to be absolute. Make sure to run `sh build.sh build-node` in both core and sync. Optionally export `REALMJS_USE_DEBUG_CORE=true` to link against the debug version of the realm binaries.
To build the Developer Edition of the module, pass `--developer_edition` to `npm install` or change the default value in `binding.gyp`.
`node-pre-gyp` is used for packaging. Run `scripts/build-node-pre-gyp.sh` with the same arguments and environment variables as `npm install` on all platforms (e.g. Linux and Darwin). Then, edit `package.json` to remove the `--build-from-source` option from the install script and run `npm pack`. Lastly, either create a fat package by merging all the tarballs created so far into one, or upload the `node-pre-gyp` tarballs to the CDN the `binary` section of `package.json` points to.
## Code of Conduct
This project adheres to the Contributor Covenant [code of conduct](https://realm.io/conduct/).

View File

@ -5,12 +5,14 @@
},
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
"40F53A12E4AE40C654358321B91166ABD3E910A6" : 0,
"F6F96CA34C5878B0A9123C7C37855491A5E599DA" : 0
"F6F96CA34C5878B0A9123C7C37855491A5E599DA" : 0,
"8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" : 0
},
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "5EE721F9-041C-4877-9E73-A925C9DB080A",
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
"40F53A12E4AE40C654358321B91166ABD3E910A6" : "realm-js\/",
"F6F96CA34C5878B0A9123C7C37855491A5E599DA" : "realm-js\/vendor\/GCDWebServer\/"
"F6F96CA34C5878B0A9123C7C37855491A5E599DA" : "realm-js\/vendor\/GCDWebServer\/",
"8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" : "realm-js\/src\/object-store\/"
},
"DVTSourceControlWorkspaceBlueprintNameKey" : "Realm",
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
@ -21,6 +23,11 @@
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "40F53A12E4AE40C654358321B91166ABD3E910A6"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/realm\/realm-object-store.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/swisspol\/GCDWebServer.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",

105
binding.gyp Normal file
View File

@ -0,0 +1,105 @@
{
"includes": [
"src/node/gyp/target_defaults.gypi",
"src/node/gyp/realm.gyp"
],
"targets": [
{
"variables": {
"developer_edition%": "0"
},
"target_name": "realm",
"dependencies": [
"object-store"
],
"sources": [
"src/node/node_sync_logger.cpp",
"src/node/node_init.cpp",
"src/node/platform.cpp",
"src/js_realm.cpp"
],
"include_dirs": [
"src"
],
"defines": [ "REALM_DEVELOPER_EDITION=<(developer_edition)" ],
"link_settings": {
"ldflags": [
"-Wl,--exclude-libs=ALL"
]
},
"xcode_settings": {
"OTHER_LDFLAGS": [ "-Xlinker -unexported_symbol -Xlinker '*'" ]
}
},
{
"variables": {
"object-store-include-dirs": [
"src/object-store/src",
"src/object-store/src/impl",
"src/object-store/src/impl/apple",
"src/object-store/src/parser",
"src/object-store/external/pegtl"
]
},
"target_name": "object-store",
"dependencies": [ "realm-sync" ], # sync also includes core
"type": "static_library",
"include_dirs": [ "<@(object-store-include-dirs)" ],
"sources": [
"src/object-store/src/collection_notifications.cpp",
"src/object-store/src/index_set.cpp",
"src/object-store/src/list.cpp",
"src/object-store/src/object_schema.cpp",
"src/object-store/src/object_store.cpp",
"src/object-store/src/results.cpp",
"src/object-store/src/schema.cpp",
"src/object-store/src/shared_realm.cpp",
"src/object-store/src/sync_manager.cpp",
"src/object-store/src/sync_session.cpp",
"src/object-store/src/thread_confined.cpp",
"src/object-store/src/global_notifier.cpp",
"src/object-store/src/impl/collection_change_builder.cpp",
"src/object-store/src/impl/collection_notifier.cpp",
"src/object-store/src/impl/handover.cpp",
"src/object-store/src/impl/list_notifier.cpp",
"src/object-store/src/impl/realm_coordinator.cpp",
"src/object-store/src/impl/results_notifier.cpp",
"src/object-store/src/impl/transact_log_handler.cpp",
"src/object-store/src/impl/weak_realm_notifier.cpp",
"src/object-store/src/parser/parser.cpp",
"src/object-store/src/parser/query_builder.cpp",
"src/object-store/src/util/format.cpp",
"src/object-store/src/util/thread_id.cpp"
],
"conditions": [
["OS=='linux'", {
"sources": [
"src/object-store/src/impl/android/external_commit_helper.cpp",
]
}],
["OS=='mac'", {
"sources": [
"src/object-store/src/impl/apple/external_commit_helper.cpp"
]
}]
],
"all_dependent_settings": {
"include_dirs": [ "<@(object-store-include-dirs)" ]
},
"export_dependent_settings": [
"<@(_dependencies)" # re-export settings related to linking the realm binaries
]
},
{
"target_name": "action_after_build",
"type": "none",
"dependencies": [ "<(module_name)" ],
"copies": [
{
"files": [ "<(PRODUCT_DIR)/<(module_name).node" ],
"destination": "<(module_path)"
}
]
}
]
}

View File

@ -189,6 +189,10 @@ Realm.defaultPath;
* object types in this Realm. **Required** when first creating a Realm at this `path`.
* @property {number} [schemaVersion] - **Required** (and must be incremented) after
* changing the `schema`.
* @property {Object} [sync] - Sync configuration parameters with the following
* child properties:
* - `user` - A `User` object obtained by calling `Realm.Sync.User.login`
* - `url` - A `string` which contains a valid Realm Sync url
*/
/**

93
docs/sync.js Normal file
View File

@ -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.
//
////////////////////////////////////////////////////////////////////////////
/**
* @memberof Realm
*/
class Sync {
}
/**
* Set a global listener function.
* @param {string} local_path - The path to the directory where realm files are stored [deprecated]
* @param {string} server_url - The sync server to listen to
* @param {SyncUser} admin_user - An admin user obtained by calling `new Realm.Sync.User.Admin`
* @param {function(realm_name)} filter_callback - Return true to recieve changes for the given realm
* @param {function(realm_name, realm, change_set)} change_callback - called on any realm changes with
* the following arguments:
* - `realm_name` - path of the Realm on which changes occurred
* - `realm` - a `Realm` object for the changed Realm
* - `change_set` - a dictionary of object names to arays of indexes indicating the indexes of objects of each type
* which have been added, removed, or modified
*/
Sync.setGlobalListener = function(local_path, server_url, admin_user, filter_callback, change_callback) {};
/**
* Set the sync log level.
* @param {string} log_level
*/
Sync.setLogLevel = function(log_level) {};
/**
* @typedef Realm.Sync~LogLevel
* @type {("error"|"info"|"debug")}
*/
/**
* Class for logging in and managing Sync users.
* @memberof Realm.Sync
*/
class User {
/**
* Login a sync user with username and password.
* @param {string} server - authentication server
* @param {string} username
* @param {string} password
* @param {function(error, user)} callback - called with the following arguments:
* - `error` - an Error object is provided on failure
* - `user` - a valid User object on success
*/
login(server, username, password, callback) {}
/**
* Login a sync user using an external login provider.
* @param {string} server - authentication server
* @param {string} provider - The provider type
* @param {string} providerToken - The access token for the given provider
* @param {function(error, User)} callback - called with the following arguments:
* - `error` - an Error object is provided on failure
* - `user` - a valid User object on success
*/
loginWithProvider(server, provider, providerToken, callback) {}
/**
* Create a new user using the username/password provider
* @param {string} server - authentication server
* @param {string} username
* @param {string} password
* @param {function(error, User)} callback - called with the following arguments:
* - `error` - an Error object is provided on failure
* - `user` - a valid User object on success
*/
create(server, username, password, callback) {}
/**
* Create an admin user for the given authentication server with an existing token
* @param {string} server - authentication server
* @param {string} adminToken - existing admin token
* @return {User} - admin user populated with the given token and server
*/
adminUser(server, adminToken) {}
}

View File

@ -34,6 +34,7 @@ TodoList.schema = {
name: 'TodoList',
properties: {
name: 'string',
creationDate: 'date',
items: {type: 'list', objectType: 'Todo'},
},
};

View File

@ -38,15 +38,18 @@ export default class TodoApp extends React.Component {
constructor(props) {
super(props);
let todoLists = realm.objects('TodoList');
if (todoLists.length < 1) {
// This is a Results object, which will live-update.
this.todoLists = realm.objects('TodoList').sorted('creationDate');
if (this.todoLists.length < 1) {
realm.write(() => {
realm.create('TodoList', {name: 'Todo List'});
realm.create('TodoList', {name: 'Todo List', creationDate: new Date()});
});
}
this.todoLists.addListener((name, changes) => {
console.log("changed: " + JSON.stringify(changes));
});
console.log("registered listener");
// This is a Results object, which will live-update.
this.todoLists = todoLists;
// Bind all the methods that we will be passing as props.
this.renderScene = this.renderScene.bind(this);
@ -79,7 +82,6 @@ export default class TodoApp extends React.Component {
component: TodoListView,
passProps: {
ref: 'listView',
items: this.todoLists,
extraItems: extraItems,
onPressItem: this._onPressTodoList,
},
@ -105,7 +107,8 @@ export default class TodoApp extends React.Component {
}
renderScene(route) {
return <route.component {...route.passProps} />
console.log(this.todoLists);
return <route.component items={this.todoLists} {...route.passProps} />
}
_addNewTodoItem(list) {
@ -128,7 +131,7 @@ export default class TodoApp extends React.Component {
}
realm.write(() => {
realm.create('TodoList', {name: ''});
realm.create('TodoList', {name: '', creationDate: new Date()});
});
this._setEditingRow(items.length - 1);

46
examples/sync.js Normal file
View File

@ -0,0 +1,46 @@
'use strict';
var Realm = require('..');
var filename = "sync.realm";
var syncUrl = "realm://127.0.0.1/nodejs/sync.realm";
var syncUserToken = "eyJpZGVudGl0eSI6Im5vZGVqcy1kZW1vIn0K";
function Foo() {}
Foo.schema = {
name: 'Foo',
properties: {
name: Realm.Types.STRING,
number: Realm.Types.INT,
}
};
var realm = new Realm({
path: filename,
syncUrl: syncUrl,
syncUserToken: syncUserToken,
schema: [Foo]
});
console.log('Starting...');
var prompt = require('prompt');
prompt.start();
var run = true;
var my_prompt = function() {
prompt.get(['command'], function (err, result) {
if (err) { return onErr(err); }
console.log('Command: ' + result.command);
if (result.command == 'exit') {
run = false;
}
if (run) {
my_prompt();
}
});
};
my_prompt();

View File

@ -18,7 +18,10 @@
'use strict';
var arrayMethods = require('./collection-methods');
function node_require(module) {
return require(module);
}
var realmConstructor;
if (typeof Realm != 'undefined') {
@ -31,14 +34,19 @@ if (typeof Realm != 'undefined') {
// eslint-disable-next-line
} else if (typeof process == 'object' && (('' + process) == '[object process]' || typeof jest == 'object')) {
// Prevent React Native packager from seeing this module.
var bindings = 'bindings';
realmConstructor = require(bindings)('realm').Realm;
var binary = node_require('node-pre-gyp');
var path = node_require('path');
var binding_path = binary.find(path.resolve(path.join(__dirname,'../package.json')));
realmConstructor = require(binding_path).Realm;
} else {
throw new Error('Missing Realm constructor - please ensure RealmReact framework is included!');
}
// Add the specified Array methods to the Collection prototype.
Object.defineProperties(realmConstructor.Collection.prototype, arrayMethods);
Object.defineProperties(realmConstructor.Collection.prototype, require('./collection-methods'));
// Add sync methods
realmConstructor.Sync.User = require('./sync').User;
// TODO: Remove this now useless object.
var types = Object.freeze({

143
lib/sync.js Normal file
View File

@ -0,0 +1,143 @@
'use strict';
function node_require(module) {
return require(module);
}
var post;
if (typeof fetch != 'undefined') {
post = function(options, callback) {
options.method = 'POST';
fetch(options.url, options)
.then((response) => {
if (response.status != 200) {
callback(undefined, {statusCode: response.status});
}
else {
return response.text();
}
})
.then((body) => {
callback(undefined, {statusCode: 200}, body)
})
.catch((error) => {
callback(error);
});
}
}
else {
post = node_require('request').post;
}
const url = require("url");
const postHeaders = {
'content-type': 'application/json;charset=utf-8',
'accept': 'application/json'
};
function _authenticate(server, json, callback) {
json.app_id = '';
var options = {
url: server + 'auth',
body: JSON.stringify(json),
headers: postHeaders
};
post(options, function(error, response, body) {
if (error) {
console.log(error);
callback(error);
}
else if (response.statusCode != 200) {
console.log('Bad response: ' + response.statusCode);
callback(new Error('Bad response: ' + response.statusCode));
}
else {
var rjson = JSON.parse(body);
// TODO: validate JSON
const token = rjson.refresh_token.token;
const identity = rjson.refresh_token.token_data.identity;
callback(undefined, new User(server, identity, token));
}
});
}
function User(server, identity, token) {
this.server = server;
this.identity = identity;
this.token = token;
this.isAdmin = false;
User.activeUsers[identity] = this;
}
User.adminUser = function(server, token) {
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
var user = new User(server, uuid, token);
user.isAdmin = true;
return user;
}
User.activeUsers = {};
User.login = function(server, username, password, callback) {
_authenticate(server, {
provider: 'password',
user_info: { password: password },
data: username
}, callback);
}
User.loginWithProvider = function(server, provider, providerToken, callback) {
_authenticate(server, {
provider: provider,
data: providerToken
}, callback);
}
User.create = function(server, username, password, callback) {
_authenticate(server, {
provider: 'password',
user_info: { password: password, register: true },
data: username
}, callback);
}
User.authenticateRealm = function(fileUrl, realmUrl, callback) {
var options = {
url: this.server + 'auth',
body: JSON.stringify({
data: this.token,
path: url.parse(realmUrl).path,
provider: 'realm',
app_id: ''
}),
headers: postHeaders
};
post(options, function(error, response, body) {
if (error) {
console.log(error);
callback(error);
}
else if (response.statusCode != 200) {
console.log('Bad response: ' + response.statusCode + body);
callback(new Error('Bad response: ' + response.statusCode));
}
else {
var json = JSON.parse(body);
// TODO: validate JSON
callback(undefined, {
token: json.access_token.token,
file_url: url.parse(fileUrl).path,
resolved_realm_url: 'realm://' + url.parse(realmUrl).host + json.access_token.token_data.path
});
}
});
}
exports['User'] = User;

View File

@ -1,7 +1,7 @@
{
"name": "realm",
"description": "Realm is a mobile database: an alternative to SQLite and key-value stores",
"version": "0.14.3",
"version": "0.14.3-6",
"license": "Apache-2.0",
"homepage": "https://realm.io",
"keywords": [
@ -35,7 +35,8 @@
"react-native",
"scripts",
"src",
"vendor"
"vendor",
"binding.gyp"
],
"scripts": {
"get-version": "echo $npm_package_version",
@ -44,20 +45,20 @@
"jsdoc": "rm -rf docs/output && jsdoc -c docs/conf.json",
"lint": "eslint",
"test": "scripts/test.sh",
"prepublish": "scripts/prepublish.sh"
"install": "node-pre-gyp install --build-from-source"
},
"dependencies": {
"bindings": "^1.2.1",
"nan": "^2.3.3",
"node-gyp": "^3.3.1",
"sync-request": "^3.0.1"
"node-pre-gyp": "^0.6.30",
"request": "^2.74.0",
"sync-request": "^3.0.1",
"url": "^0.11.0"
},
"devDependencies": {
"babel-eslint": "^6.0.4",
"eslint": "^2.10.2",
"eslint-plugin-react": "^5.1.1",
"jsdoc": "^3.4.0",
"mockery": "^1.7.0",
"semver": "^5.1.0"
},
"rnpm": {
@ -71,5 +72,11 @@
},
"engines": {
"node": ">=4"
},
"binary": {
"module_name": "realm",
"module_path": "./compiled/{node_abi}_{platform}_{arch}/",
"host": "https://static.realm.io",
"remote_path": "/node-pre-gyp"
}
}

46
scripts/build-node-pre-gyp.sh Executable file
View File

@ -0,0 +1,46 @@
#!/bin/bash
node_versions=${@:-4.4.7 5.12.0 6.5.0}
topdir=$(cd $(dirname "$0")/..; pwd)
die() {
echo $1
exit 1
}
. ${topdir}/dependencies.list
mkdir -p ${topdir}/out
: ${NVM_DIR=$topdir/.nvm}
if [ ! -d "$NVM_DIR" ]; then
(
git clone https://github.com/creationix/nvm.git "$NVM_DIR"
cd "$NVM_DIR"
git checkout `git describe --abbrev=0 --tags --match "v[0-9]*" origin`
)
fi
if [ -f "$NVM_DIR/nvm.sh" ]; then
. "$NVM_DIR/nvm.sh"
else
# we must be on mac and nvm was installed with brew
# TODO: change the mac slaves to use manual nvm installation
. "$(brew --prefix nvm)/nvm.sh"
fi
for node_version in ${node_versions}; do
(
rm -rf node_modules build
nvm install ${node_version} || die "Could not install nodejs v${node_version}"
nvm use ${node_version} || die "Could not load nodejs v${node_version}"
npm install "$EXTRA_NPM_ARGUMENTS" || die "Could not build module"
#./scripts/test.sh node || die "Unit tests for nodejs v${node_version} failed"
./node_modules/.bin/node-pre-gyp package || die "Could not package module"
cp build/stage/node-pre-gyp/*.tar.gz ${topdir}/out/
)
done

View File

@ -4,7 +4,7 @@ set -e
set -o pipefail
# Set to "latest" for the latest build.
: ${REALM_CORE_VERSION:=1.5.0}
: ${REALM_CORE_VERSION:=2.0.0-rc4}
if [ "$1" = '--version' ]; then
echo "$REALM_CORE_VERSION"

View File

@ -154,21 +154,16 @@ case "$TARGET" in
cat tests.xml
;;
"node")
npm install
scripts/download-core.sh node
src/node/build-node.sh $CONFIGURATION
# Change to a temp directory.
cd "$(mktemp -q -d -t realm.node.XXXXXX)"
trap "rm -rf '$PWD'" EXIT
node "$SRCROOT/tests"
pushd "$SRCROOT/tests"
npm install
npm test
popd
;;
"test-runners")
npm install
scripts/download-core.sh node
src/node/build-node.sh $CONFIGURATION
for runner in ava mocha jest; do
pushd "$SRCROOT/tests/test-runners/$runner"
npm install
@ -181,6 +176,27 @@ case "$TARGET" in
cmake -DCMAKE_BUILD_TYPE=$CONFIGURATION .
make run-tests
;;
"download-object-server")
. dependencies.list
object_server_bundle="realm-object-server-bundled_node_darwin-$REALM_OBJECT_SERVER_VERSION.tar.gz"
curl -f -L "https://static.realm.io/downloads/object-server/$object_server_bundle" -o "$object_server_bundle"
rm -rf tests/sync-bundle
mkdir -p tests/sync-bundle
tar -C tests/sync-bundle -xf "$object_server_bundle"
rm "$object_server_bundle"
echo -e "enterprise:\n skip_setup: true\n" >> "tests/sync-bundle/object-server/configuration.yml"
touch "tests/sync-bundle/object-server/do_not_open_browser"
;;
"object-server-integration")
echo -e "yes\n" | ./tests/sync-bundle/reset-server-realms.command
pushd "$SRCROOT/tests"
npm install
npm run test-sync
popd
;;
*)
echo "Invalid target '${TARGET}'"
exit 1

View File

@ -7,6 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
020229861D9EEB39000F0C4F /* global_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 020229661D9DBF02000F0C4F /* global_notifier.cpp */; };
020229871D9EEB39000F0C4F /* sync_metadata.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 020229801D9EEAF2000F0C4F /* sync_metadata.cpp */; };
020229881D9EEB39000F0C4F /* sync_session.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 020229821D9EEAF2000F0C4F /* sync_session.cpp */; };
02409DC21BCF11D6005F3B3E /* RealmJSCoreTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 02409DC11BCF11D6005F3B3E /* RealmJSCoreTests.m */; };
02414B881CE68CA200A8669F /* dates-v5.realm in Resources */ = {isa = PBXBuildFile; fileRef = 02414B871CE68CA200A8669F /* dates-v5.realm */; };
02414BA51CE6ABCF00A8669F /* collection_change_builder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02414B991CE6AAEF00A8669F /* collection_change_builder.cpp */; };
@ -14,6 +17,8 @@
02414BA71CE6ABCF00A8669F /* list_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02414B9D1CE6AAEF00A8669F /* list_notifier.cpp */; };
02414BA81CE6ABCF00A8669F /* results_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02414B9F1CE6AAEF00A8669F /* results_notifier.cpp */; };
02414BA91CE6ABCF00A8669F /* collection_notifications.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02414B961CE6AADD00A8669F /* collection_notifications.cpp */; };
02443E391D8AF936007B0DF4 /* sync_manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02443E361D8AF8FB007B0DF4 /* sync_manager.cpp */; };
02443E421D8AFB74007B0DF4 /* weak_realm_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02443E401D8AFB5F007B0DF4 /* weak_realm_notifier.cpp */; };
0270BC821B7D020100010E03 /* RealmJSTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0270BC7B1B7D020100010E03 /* RealmJSTests.mm */; };
027A23131CD3E379000543AE /* libRealmJS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F63FF2B11C1241E500B3B8E0 /* libRealmJS.a */; };
02D041F71CE11159000E4250 /* dates-v3.realm in Resources */ = {isa = PBXBuildFile; fileRef = 02D041F61CE11159000E4250 /* dates-v3.realm */; };
@ -29,9 +34,9 @@
02F59ECA1C88F190007F774C /* parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EC61C88F190007F774C /* parser.cpp */; };
02F59ECB1C88F190007F774C /* query_builder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EC81C88F190007F774C /* query_builder.cpp */; };
02F59ED41C88F1B6007F774C /* external_commit_helper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59ECF1C88F1B6007F774C /* external_commit_helper.cpp */; };
02F59ED51C88F1B6007F774C /* weak_realm_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59ED11C88F1B6007F774C /* weak_realm_notifier.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 */; };
5D25F5A11D6284FD00EBBB30 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = F63FF3301C16434400B3B8E0 /* libz.tbd */; };
5DC74A781D623C9800D77A4F /* handover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5DC74A751D623C8700D77A4F /* handover.cpp */; };
5DC74A791D623CA200D77A4F /* handover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5DC74A751D623C8700D77A4F /* handover.cpp */; };
5DC74A7A1D623CA800D77A4F /* thread_confined.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5DC74A721D623C7A00D77A4F /* thread_confined.cpp */; };
@ -53,7 +58,6 @@
F61378791C18EAC5008BFC51 /* js in Resources */ = {isa = PBXBuildFile; fileRef = F61378781C18EAAC008BFC51 /* js */; };
F620F0581CB766DA0082977B /* node_init.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F620F0571CB766DA0082977B /* node_init.cpp */; };
F620F0751CB9F60C0082977B /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F620F0741CB9F60C0082977B /* CoreFoundation.framework */; };
F63117F01CEB0D5F00ECB2DE /* weak_realm_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F63117EE1CEB0D5900ECB2DE /* weak_realm_notifier.cpp */; };
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 */; };
F63FF2CD1C12469E00B3B8E0 /* rpc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0290480F1C0428DF00ABDED4 /* rpc.cpp */; };
@ -98,6 +102,12 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
020229661D9DBF02000F0C4F /* global_notifier.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = global_notifier.cpp; path = src/global_notifier.cpp; sourceTree = "<group>"; };
020229671D9DBF02000F0C4F /* global_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = global_notifier.hpp; path = src/global_notifier.hpp; sourceTree = "<group>"; };
020229801D9EEAF2000F0C4F /* sync_metadata.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sync_metadata.cpp; path = src/sync_metadata.cpp; sourceTree = "<group>"; };
020229811D9EEAF2000F0C4F /* sync_metadata.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = sync_metadata.hpp; path = src/sync_metadata.hpp; sourceTree = "<group>"; };
020229821D9EEAF2000F0C4F /* sync_session.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sync_session.cpp; path = src/sync_session.cpp; sourceTree = "<group>"; };
020229831D9EEAF2000F0C4F /* sync_session.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = sync_session.hpp; path = src/sync_session.hpp; sourceTree = "<group>"; };
02409DC11BCF11D6005F3B3E /* RealmJSCoreTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RealmJSCoreTests.m; path = ios/RealmJSCoreTests.m; sourceTree = "<group>"; };
02414B871CE68CA200A8669F /* dates-v5.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "dates-v5.realm"; sourceTree = "<group>"; };
02414B961CE6AADD00A8669F /* collection_notifications.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = collection_notifications.cpp; path = src/collection_notifications.cpp; sourceTree = "<group>"; };
@ -110,6 +120,12 @@
02414B9E1CE6AAEF00A8669F /* list_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = list_notifier.hpp; sourceTree = "<group>"; };
02414B9F1CE6AAEF00A8669F /* results_notifier.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = results_notifier.cpp; sourceTree = "<group>"; };
02414BA01CE6AAEF00A8669F /* results_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = results_notifier.hpp; sourceTree = "<group>"; };
02443E351D8AF8FB007B0DF4 /* sync_config.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = sync_config.hpp; path = src/sync_config.hpp; sourceTree = "<group>"; };
02443E361D8AF8FB007B0DF4 /* sync_manager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sync_manager.cpp; path = src/sync_manager.cpp; sourceTree = "<group>"; };
02443E371D8AF8FB007B0DF4 /* sync_manager.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = sync_manager.hpp; path = src/sync_manager.hpp; sourceTree = "<group>"; };
02443E3A1D8AFAD1007B0DF4 /* sync_client.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = sync_client.hpp; sourceTree = "<group>"; };
02443E401D8AFB5F007B0DF4 /* weak_realm_notifier.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = weak_realm_notifier.cpp; sourceTree = "<group>"; };
0250D9C01D7647E00012C20C /* js_sync.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = js_sync.hpp; sourceTree = "<group>"; };
025678951CAB392000FB8501 /* jsc_types.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = jsc_types.hpp; sourceTree = "<group>"; };
0270BC5A1B7CFC1300010E03 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0270BC781B7D020100010E03 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ios/Info.plist; sourceTree = "<group>"; };
@ -127,6 +143,7 @@
029048101C0428DF00ABDED4 /* rpc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = rpc.hpp; sourceTree = "<group>"; };
029048351C042A3C00ABDED4 /* platform.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = platform.hpp; sourceTree = "<group>"; };
029048381C042A8F00ABDED4 /* platform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platform.mm; sourceTree = "<group>"; };
0290934A1CEFA9170009769E /* js_observable.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = js_observable.hpp; sourceTree = "<group>"; };
02A3C7A41BC4341500B1A7BE /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
02B58CBC1AE99CEC009B348C /* RealmJSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RealmJSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
@ -157,14 +174,12 @@
02F59EC91C88F190007F774C /* query_builder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = query_builder.hpp; sourceTree = "<group>"; };
02F59ECF1C88F1B6007F774C /* external_commit_helper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = external_commit_helper.cpp; sourceTree = "<group>"; };
02F59ED01C88F1B6007F774C /* external_commit_helper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = external_commit_helper.hpp; sourceTree = "<group>"; };
02F59ED11C88F1B6007F774C /* weak_realm_notifier.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = weak_realm_notifier.cpp; sourceTree = "<group>"; };
02F59ED21C88F1B6007F774C /* weak_realm_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = weak_realm_notifier.hpp; sourceTree = "<group>"; };
02F59EDA1C88F2BA007F774C /* external_commit_helper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = external_commit_helper.hpp; sourceTree = "<group>"; };
02F59EDB1C88F2BA007F774C /* realm_coordinator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = realm_coordinator.cpp; sourceTree = "<group>"; };
02F59EDC1C88F2BB007F774C /* realm_coordinator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = realm_coordinator.hpp; sourceTree = "<group>"; };
02F59EDD1C88F2BB007F774C /* transact_log_handler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = transact_log_handler.cpp; sourceTree = "<group>"; };
02F59EDE1C88F2BB007F774C /* transact_log_handler.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = transact_log_handler.hpp; sourceTree = "<group>"; };
02F59EDF1C88F2BB007F774C /* weak_realm_notifier_base.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = weak_realm_notifier_base.hpp; sourceTree = "<group>"; };
02F59EDF1C88F2BB007F774C /* weak_realm_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = weak_realm_notifier.hpp; sourceTree = "<group>"; };
02F59EE01C88F2BB007F774C /* weak_realm_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = weak_realm_notifier.hpp; sourceTree = "<group>"; };
5DC74A721D623C7A00D77A4F /* thread_confined.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = thread_confined.cpp; path = src/thread_confined.cpp; sourceTree = "<group>"; };
5DC74A731D623C7A00D77A4F /* thread_confined.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = thread_confined.hpp; path = src/thread_confined.hpp; sourceTree = "<group>"; };
@ -204,15 +219,10 @@
F6267BC91CADC30000AC36B1 /* js_util.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = js_util.hpp; sourceTree = "<group>"; };
F6267BCA1CADC49200AC36B1 /* node_dummy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = node_dummy.cpp; sourceTree = "<group>"; };
F62BF8FB1CAC71780022BCDC /* libRealmNode.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libRealmNode.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
F63117EE1CEB0D5900ECB2DE /* weak_realm_notifier.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = weak_realm_notifier.cpp; sourceTree = "<group>"; };
F63117EF1CEB0D5900ECB2DE /* weak_realm_notifier.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = weak_realm_notifier.hpp; sourceTree = "<group>"; };
F63118431CEBA7B700ECB2DE /* external_commit_helper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = external_commit_helper.cpp; sourceTree = "<group>"; };
F63118441CEBA7B700ECB2DE /* external_commit_helper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = external_commit_helper.hpp; sourceTree = "<group>"; };
F63118451CEBA7B700ECB2DE /* weak_realm_notifier.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = weak_realm_notifier.cpp; sourceTree = "<group>"; };
F63118461CEBA7B700ECB2DE /* weak_realm_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = weak_realm_notifier.hpp; sourceTree = "<group>"; };
F631184A1CEBA7D800ECB2DE /* external_commit_helper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = external_commit_helper.cpp; sourceTree = "<group>"; };
F631184B1CEBA7D800ECB2DE /* external_commit_helper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = external_commit_helper.hpp; sourceTree = "<group>"; };
F631184C1CEBA7D800ECB2DE /* weak_realm_notifier.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = weak_realm_notifier.hpp; sourceTree = "<group>"; };
F63FF2B11C1241E500B3B8E0 /* libRealmJS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRealmJS.a; sourceTree = BUILT_PRODUCTS_DIR; };
F63FF2F01C16405C00B3B8E0 /* libGCDWebServers.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libGCDWebServers.a; sourceTree = BUILT_PRODUCTS_DIR; };
F63FF2FD1C1642BB00B3B8E0 /* GCDWebServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServer.h; sourceTree = "<group>"; };
@ -266,6 +276,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5D25F5A11D6284FD00EBBB30 /* libz.tbd in Frameworks */,
027A23131CD3E379000543AE /* libRealmJS.a in Frameworks */,
02D8D1F71B601984006DB49D /* JavaScriptCore.framework in Frameworks */,
);
@ -297,11 +308,13 @@
F6874A441CAD2ACD00EEEE36 /* JSC */,
F62BF9001CAC72C40022BCDC /* Node */,
F62A35141C18E783004A917D /* Object Store */,
0290934A1CEFA9170009769E /* js_observable.hpp */,
F60102F71CBDA6D400EC01BA /* js_collection.hpp */,
029048041C0428DF00ABDED4 /* js_list.hpp */,
029048061C0428DF00ABDED4 /* js_realm_object.hpp */,
029048071C0428DF00ABDED4 /* js_realm.cpp */,
029048081C0428DF00ABDED4 /* js_realm.hpp */,
0250D9C01D7647E00012C20C /* js_sync.hpp */,
0290480A1C0428DF00ABDED4 /* js_results.hpp */,
0290480C1C0428DF00ABDED4 /* js_schema.hpp */,
F620F0521CAF0B600082977B /* js_class.hpp */,
@ -414,6 +427,8 @@
02F59EAE1C88F17D007F774C /* binding_context.hpp */,
02F59EAF1C88F17D007F774C /* index_set.cpp */,
02F59EB01C88F17D007F774C /* index_set.hpp */,
020229661D9DBF02000F0C4F /* global_notifier.cpp */,
020229671D9DBF02000F0C4F /* global_notifier.hpp */,
02F59EB11C88F17D007F774C /* list.cpp */,
02F59EB21C88F17D007F774C /* list.hpp */,
02F59EB31C88F17D007F774C /* object_accessor.hpp */,
@ -430,6 +445,13 @@
02F59EBE1C88F17D007F774C /* shared_realm.hpp */,
5DC74A721D623C7A00D77A4F /* thread_confined.cpp */,
5DC74A731D623C7A00D77A4F /* thread_confined.hpp */,
02443E351D8AF8FB007B0DF4 /* sync_config.hpp */,
02443E361D8AF8FB007B0DF4 /* sync_manager.cpp */,
02443E371D8AF8FB007B0DF4 /* sync_manager.hpp */,
020229801D9EEAF2000F0C4F /* sync_metadata.cpp */,
020229811D9EEAF2000F0C4F /* sync_metadata.hpp */,
020229821D9EEAF2000F0C4F /* sync_session.cpp */,
020229831D9EEAF2000F0C4F /* sync_session.hpp */,
);
name = "Object Store";
path = "object-store";
@ -464,7 +486,6 @@
F63118421CEBA7A100ECB2DE /* android */,
F63117EB1CEB0C1B00ECB2DE /* apple */,
F63118491CEBA7BD00ECB2DE /* generic */,
F63117ED1CEB0CC600ECB2DE /* node */,
02414B991CE6AAEF00A8669F /* collection_change_builder.cpp */,
02414B9A1CE6AAEF00A8669F /* collection_change_builder.hpp */,
02414B9B1CE6AAEF00A8669F /* collection_notifier.cpp */,
@ -478,9 +499,11 @@
02F59EDA1C88F2BA007F774C /* external_commit_helper.hpp */,
02F59EDB1C88F2BA007F774C /* realm_coordinator.cpp */,
02F59EDC1C88F2BB007F774C /* realm_coordinator.hpp */,
02443E3A1D8AFAD1007B0DF4 /* sync_client.hpp */,
02F59EDD1C88F2BB007F774C /* transact_log_handler.cpp */,
02F59EDE1C88F2BB007F774C /* transact_log_handler.hpp */,
02F59EDF1C88F2BB007F774C /* weak_realm_notifier_base.hpp */,
02F59EDF1C88F2BB007F774C /* weak_realm_notifier.hpp */,
02443E401D8AFB5F007B0DF4 /* weak_realm_notifier.cpp */,
02F59EE01C88F2BB007F774C /* weak_realm_notifier.hpp */,
);
name = impl;
@ -492,8 +515,6 @@
children = (
02F59ECF1C88F1B6007F774C /* external_commit_helper.cpp */,
02F59ED01C88F1B6007F774C /* external_commit_helper.hpp */,
02F59ED11C88F1B6007F774C /* weak_realm_notifier.cpp */,
02F59ED21C88F1B6007F774C /* weak_realm_notifier.hpp */,
);
path = apple;
sourceTree = "<group>";
@ -510,22 +531,11 @@
path = src/parser;
sourceTree = "<group>";
};
F63117ED1CEB0CC600ECB2DE /* node */ = {
isa = PBXGroup;
children = (
F63117EE1CEB0D5900ECB2DE /* weak_realm_notifier.cpp */,
F63117EF1CEB0D5900ECB2DE /* weak_realm_notifier.hpp */,
);
path = node;
sourceTree = "<group>";
};
F63118421CEBA7A100ECB2DE /* android */ = {
isa = PBXGroup;
children = (
F63118431CEBA7B700ECB2DE /* external_commit_helper.cpp */,
F63118441CEBA7B700ECB2DE /* external_commit_helper.hpp */,
F63118451CEBA7B700ECB2DE /* weak_realm_notifier.cpp */,
F63118461CEBA7B700ECB2DE /* weak_realm_notifier.hpp */,
);
path = android;
sourceTree = "<group>";
@ -535,7 +545,6 @@
children = (
F631184A1CEBA7D800ECB2DE /* external_commit_helper.cpp */,
F631184B1CEBA7D800ECB2DE /* external_commit_helper.hpp */,
F631184C1CEBA7D800ECB2DE /* weak_realm_notifier.hpp */,
);
path = generic;
sourceTree = "<group>";
@ -849,7 +858,6 @@
F6E931BC1CFEAE340016AF14 /* collection_notifier.cpp in Sources */,
F60102DC1CBB96C900EC01BA /* query_builder.cpp in Sources */,
F60102DD1CBB96CC00EC01BA /* external_commit_helper.cpp in Sources */,
F63117F01CEB0D5F00ECB2DE /* weak_realm_notifier.cpp in Sources */,
F60102E11CBB96DD00EC01BA /* transact_log_handler.cpp in Sources */,
F6E931BE1CFEAE3A0016AF14 /* results_notifier.cpp in Sources */,
F60102D71CBB96B800EC01BA /* object_store.cpp in Sources */,
@ -870,6 +878,11 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
020229861D9EEB39000F0C4F /* global_notifier.cpp in Sources */,
020229871D9EEB39000F0C4F /* sync_metadata.cpp in Sources */,
020229881D9EEB39000F0C4F /* sync_session.cpp in Sources */,
02443E421D8AFB74007B0DF4 /* weak_realm_notifier.cpp in Sources */,
02443E391D8AF936007B0DF4 /* sync_manager.cpp in Sources */,
02E008D51D10ABB600F3AA37 /* format.cpp in Sources */,
5DC74A7A1D623CA800D77A4F /* thread_confined.cpp in Sources */,
02414BA51CE6ABCF00A8669F /* collection_change_builder.cpp in Sources */,
@ -887,7 +900,6 @@
02F59ECA1C88F190007F774C /* parser.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 */,
02F59EC51C88F17D007F774C /* shared_realm.cpp in Sources */,
02F59ECB1C88F190007F774C /* query_builder.cpp in Sources */,
@ -1145,12 +1157,21 @@
isa = XCBuildConfiguration;
buildSettings = {
DEBUG_INFORMATION_FORMAT = dwarf;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
REALM_DEBUG,
REALM_HAVE_CONFIG,
__ASSERTMACROS__,
REALM_ENABLE_SYNC,
);
OTHER_CPLUSPLUSFLAGS = (
"$(inherited)",
"-isystem",
../core/include,
"../../realm-core/ios-lib/include",
"-isystem",
"../../realm-sync/src",
);
OTHER_LIBTOOLFLAGS = "$(SRCROOT)/../core/librealm-ios-dbg.a";
OTHER_LIBTOOLFLAGS = "$(SRCROOT)/../../realm-core/ios-lib/librealm-ios-dbg.a $(SRCROOT)/../../realm-sync/src/realm/librealm-sync-iphonesimulator-dbg.a";
PRODUCT_NAME = RealmJS;
SKIP_INSTALL = YES;
};
@ -1159,12 +1180,19 @@
F63FF2B91C1241E500B3B8E0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_PREPROCESSOR_DEFINITIONS = (
REALM_HAVE_CONFIG,
__ASSERTMACROS__,
REALM_ENABLE_SYNC,
);
OTHER_CPLUSPLUSFLAGS = (
"$(inherited)",
"-isystem",
../core/include,
"../../realm-core/ios-lib/include",
"-isystem",
"../../realm-sync/src",
);
OTHER_LIBTOOLFLAGS = "$(SRCROOT)/../core/librealm-ios.a";
OTHER_LIBTOOLFLAGS = "$(SRCROOT)/../../realm-core/ios-lib/librealm-ios.a $(SRCROOT)/../../realm-sync/src/realm/librealm-sync-iphonesimulator.a";
PRODUCT_NAME = RealmJS;
SKIP_INSTALL = YES;
};

View File

@ -5,22 +5,6 @@
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F62BF8FA1CAC71780022BCDC"
BuildableName = "libRealmNode.dylib"
BlueprintName = "RealmNode"
ReferencedContainer = "container:RealmJS.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
@ -29,24 +13,6 @@
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "YES"
customWorkingDirectory = "$(TEMP_DIR)"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<PathRunnable
runnableDebuggingMode = "0"
FilePath = "/usr/local/bin/node">
</PathRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@ -56,9 +22,27 @@
ReferencedContainer = "container:RealmJS.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "YES"
customWorkingDirectory = "~/src/realm/sync/realm-js"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<PathRunnable
runnableDebuggingMode = "0"
FilePath = "/usr/local/bin/node">
</PathRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "$(SRCROOT)/../tests"
argument = "test.js"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
@ -71,15 +55,6 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F62BF8FA1CAC71780022BCDC"
BuildableName = "libRealmNode.dylib"
BlueprintName = "RealmNode"
ReferencedContainer = "container:RealmJS.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@ -19,6 +19,10 @@
#pragma once
#include "js_class.hpp"
#include "js_types.hpp"
#include "js_observable.hpp"
#include "collection_notifications.hpp"
namespace realm {
namespace js {
@ -27,9 +31,40 @@ namespace js {
class Collection {};
template<typename T>
struct CollectionClass : ClassDefinition<T, Collection> {
struct CollectionClass : ClassDefinition<T, Collection, ObservableClass<T>> {
using ContextType = typename T::Context;
using ValueType = typename T::Value;
using ObjectType = typename T::Object;
using Object = js::Object<T>;
using Value = js::Value<T>;
std::string const name = "Collection";
static inline ValueType create_collection_change_set(ContextType ctx, const CollectionChangeSet &change_set);
};
template<typename T>
typename T::Value CollectionClass<T>::create_collection_change_set(ContextType ctx, const CollectionChangeSet &change_set)
{
ObjectType object = Object::create_empty(ctx);
std::vector<ValueType> deletions, insertions, modifications;
for (auto index : change_set.deletions.as_indexes()) {
deletions.push_back(Value::from_number(ctx, index));
}
Object::set_property(ctx, object, "deletions", Object::create_array(ctx, deletions));
for (auto index : change_set.insertions.as_indexes()) {
insertions.push_back(Value::from_number(ctx, index));
}
Object::set_property(ctx, object, "insertions", Object::create_array(ctx, insertions));
for (auto index : change_set.modifications.as_indexes()) {
modifications.push_back(Value::from_number(ctx, index));
}
Object::set_property(ctx, object, "modifications", Object::create_array(ctx, modifications));
return object;
}
} // js
} // realm

View File

@ -33,10 +33,20 @@ namespace realm {
namespace js {
template<typename T>
struct ListClass : ClassDefinition<T, realm::List, CollectionClass<T>> {
class List : public realm::List {
public:
List(std::shared_ptr<Realm> r, const ObjectSchema& s, LinkViewRef l) noexcept : realm::List(r, l) {}
List(const realm::List &l) : realm::List(l) {}
std::vector<std::pair<Protected<typename T::Function>, NotificationToken>> m_notification_tokens;
};
template<typename T>
struct ListClass : ClassDefinition<T, realm::js::List<T>, CollectionClass<T>> {
using ContextType = typename T::Context;
using ObjectType = typename T::Object;
using ValueType = typename T::Value;
using FunctionType = typename T::Function;
using Object = js::Object<T>;
using Value = js::Value<T>;
using ReturnValue = js::ReturnValue<T>;
@ -58,7 +68,12 @@ struct ListClass : ClassDefinition<T, realm::List, CollectionClass<T>> {
static void filtered(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void sorted(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &);
// observable
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 &);
std::string const name = "List";
MethodMap<T> const methods = {
@ -71,6 +86,9 @@ struct ListClass : ClassDefinition<T, realm::List, CollectionClass<T>> {
{"filtered", wrap<filtered>},
{"sorted", wrap<sorted>},
{"isValid", wrap<is_valid>},
{"addListener", wrap<add_listener>},
{"removeListener", wrap<remove_listener>},
{"removeAllListeners", wrap<remove_all_listeners>},
};
PropertyMap<T> const properties = {
@ -82,7 +100,7 @@ struct ListClass : ClassDefinition<T, realm::List, CollectionClass<T>> {
template<typename T>
typename T::Object ListClass<T>::create_instance(ContextType ctx, realm::List list) {
return create_object<T, ListClass<T>>(ctx, new realm::List(std::move(list)));
return create_object<T, ListClass<T>>(ctx, new realm::js::List<T>(std::move(list)));
}
template<typename T>
@ -230,6 +248,55 @@ template<typename T>
void ListClass<T>::is_valid(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
return_value.set(get_internal<T, ListClass<T>>(this_object)->is_valid());
}
template<typename T>
void ListClass<T>::add_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
auto list = get_internal<T, ListClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, arguments[0]);
Protected<FunctionType> protected_callback(ctx, callback);
Protected<ObjectType> protected_this(ctx, this_object);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
auto token = list->add_notification_callback([=](CollectionChangeSet change_set, std::exception_ptr exception) {
typename T::HandleScope scope;
ValueType arguments[2];
arguments[0] = static_cast<ObjectType>(protected_this);
arguments[1] = CollectionClass<T>::create_collection_change_set(protected_ctx, change_set);
Function<T>::call(protected_ctx, protected_callback, protected_this, 2, arguments);
});
list->m_notification_tokens.emplace_back(protected_callback, std::move(token));
}
template<typename T>
void ListClass<T>::remove_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
auto list = get_internal<T, ListClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, arguments[0]);
auto protected_function = Protected<FunctionType>(ctx, callback);
auto iter = list->m_notification_tokens.begin();
typename Protected<FunctionType>::Comparator compare;
while (iter != list->m_notification_tokens.end()) {
if(compare(iter->first, protected_function)) {
iter = list->m_notification_tokens.erase(iter);
}
else {
iter++;
}
}
}
template<typename T>
void ListClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
auto list = get_internal<T, ListClass<T>>(this_object);
list->m_notification_tokens.clear();
}
} // js
} // realm

35
src/js_observable.hpp Normal file
View File

@ -0,0 +1,35 @@
////////////////////////////////////////////////////////////////////////////
//
// 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"
namespace realm {
namespace js {
// Empty class that merely serves as useful type for now.
class Observable {};
template<typename T>
struct ObservableClass : ClassDefinition<T, Observable> {
std::string const name = "Observable";
};
} // js
} // realm

View File

@ -28,11 +28,14 @@
#include "js_list.hpp"
#include "js_results.hpp"
#include "js_schema.hpp"
#include "js_observable.hpp"
#include "js_sync.hpp"
#include "shared_realm.hpp"
#include "binding_context.hpp"
#include "object_accessor.hpp"
#include "platform.hpp"
#include "sync_config.hpp"
namespace realm {
namespace js {
@ -99,8 +102,10 @@ class RealmDelegate : public BindingContext {
Protected<GlobalContextType> m_context;
std::list<Protected<FunctionType>> m_notifications;
std::weak_ptr<realm::Realm> m_realm;
void notify(const char *notification_name) {
typename T::HandleScope scope;
SharedRealm realm = m_realm.lock();
if (!realm) {
throw std::runtime_error("Realm no longer exists");
@ -124,7 +129,7 @@ void set_default_path(std::string path);
void delete_all_realms();
template<typename T>
class RealmClass : public ClassDefinition<T, SharedRealm> {
class RealmClass : public ClassDefinition<T, SharedRealm, ObservableClass<T>> {
using GlobalContextType = typename T::GlobalContext;
using ContextType = typename T::Context;
using FunctionType = typename T::Function;
@ -162,7 +167,7 @@ public:
static void schema_version(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void clear_test_state(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void copy_bundled_realm_files(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);
@ -254,12 +259,14 @@ inline typename T::Function RealmClass<T>::create_constructor(ContextType ctx) {
FunctionType list_constructor = ObjectWrap<T, ListClass<T>>::create_constructor(ctx);
FunctionType results_constructor = ObjectWrap<T, ResultsClass<T>>::create_constructor(ctx);
FunctionType realm_object_constructor = ObjectWrap<T, RealmObjectClass<T>>::create_constructor(ctx);
FunctionType sync_constructor = SyncClass<T>::create_constructor(ctx);
PropertyAttributes attributes = ReadOnly | DontEnum | DontDelete;
Object::set_property(ctx, realm_constructor, "Collection", collection_constructor, attributes);
Object::set_property(ctx, realm_constructor, "List", list_constructor, attributes);
Object::set_property(ctx, realm_constructor, "Results", results_constructor, attributes);
Object::set_property(ctx, realm_constructor, "Object", realm_object_constructor, attributes);
Object::set_property(ctx, realm_constructor, "Sync", sync_constructor, attributes);
return realm_constructor;
}
@ -376,6 +383,8 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
std::string encryption_key = NativeAccessor::to_binary(ctx, encryption_key_value);
config.encryption_key = std::vector<char>(encryption_key.begin(), encryption_key.end());
}
SyncClass<T>::populate_sync_config(ctx, object, config);
}
}
else {

View File

@ -31,10 +31,25 @@ namespace realm {
namespace js {
template<typename T>
struct ResultsClass : ClassDefinition<T, realm::Results, CollectionClass<T>> {
class Results : public realm::Results {
public:
Results(Results const& r) : realm::Results(r) {};
Results(realm::Results const& r) : realm::Results(r) {};
Results(Results&&) = default;
Results& operator=(Results&&) = default;
Results& operator=(Results const&) = default;
using realm::Results::Results;
std::vector<std::pair<Protected<typename T::Function>, NotificationToken>> m_notification_tokens;
};
template<typename T>
struct ResultsClass : ClassDefinition<T, realm::js::Results<T>, CollectionClass<T>> {
using ContextType = typename T::Context;
using ObjectType = typename T::Object;
using ValueType = typename T::Value;
using FunctionType = typename T::Function;
using Object = js::Object<T>;
using Value = js::Value<T>;
using ReturnValue = js::ReturnValue<T>;
@ -56,6 +71,11 @@ struct ResultsClass : ClassDefinition<T, realm::Results, CollectionClass<T>> {
static void sorted(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &);
// observable
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 &);
std::string const name = "Results";
MethodMap<T> const methods = {
@ -63,6 +83,9 @@ struct ResultsClass : ClassDefinition<T, realm::Results, CollectionClass<T>> {
{"filtered", wrap<filtered>},
{"sorted", wrap<sorted>},
{"isValid", wrap<is_valid>},
{"addListener", wrap<add_listener>},
{"removeListener", wrap<remove_listener>},
{"removeAllListeners", wrap<remove_all_listeners>},
};
PropertyMap<T> const properties = {
@ -74,13 +97,13 @@ struct ResultsClass : ClassDefinition<T, realm::Results, CollectionClass<T>> {
template<typename T>
typename T::Object ResultsClass<T>::create_instance(ContextType ctx, realm::Results results) {
return create_object<T, ResultsClass<T>>(ctx, new realm::Results(std::move(results)));
return create_object<T, ResultsClass<T>>(ctx, new realm::js::Results<T>(std::move(results)));
}
template<typename T>
typename T::Object ResultsClass<T>::create_instance(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema) {
auto table = ObjectStore::table_for_object_type(realm->read_group(), object_schema.name);
return create_object<T, ResultsClass<T>>(ctx, new realm::Results(realm, *table));
return create_object<T, ResultsClass<T>>(ctx, new realm::js::Results<T>(realm, *table));
}
template<typename T>
@ -155,8 +178,8 @@ typename T::Object ResultsClass<T>::create_sorted(ContextType ctx, const U &coll
}
auto table = realm::ObjectStore::table_for_object_type(realm->read_group(), object_schema.name);
auto results = new realm::Results(realm, collection.get_query(),
{*table, std::move(columns), std::move(ascending)});
auto results = new realm::js::Results<T>(realm, collection.get_query(),
{*table, std::move(columns), std::move(ascending)});
return create_object<T, ResultsClass<T>>(ctx, results);
}
@ -209,6 +232,55 @@ template<typename T>
void ResultsClass<T>::is_valid(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
return_value.set(get_internal<T, ResultsClass<T>>(this_object)->is_valid());
}
template<typename T>
void ResultsClass<T>::add_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
auto results = get_internal<T, ResultsClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, arguments[0]);
Protected<FunctionType> protected_callback(ctx, callback);
Protected<ObjectType> protected_this(ctx, this_object);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
auto token = results->add_notification_callback([=](CollectionChangeSet change_set, std::exception_ptr exception) {
typename T::HandleScope scope;
ValueType arguments[2];
arguments[0] = static_cast<ObjectType>(protected_this);
arguments[1] = CollectionClass<T>::create_collection_change_set(protected_ctx, change_set);
Function<T>::call(protected_ctx, protected_callback, protected_this, 2, arguments);
});
results->m_notification_tokens.emplace_back(protected_callback, std::move(token));
}
template<typename T>
void ResultsClass<T>::remove_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
auto results = get_internal<T, ResultsClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, arguments[0]);
auto protected_function = Protected<FunctionType>(ctx, callback);
auto iter = results->m_notification_tokens.begin();
typename Protected<FunctionType>::Comparator compare;
while (iter != results->m_notification_tokens.end()) {
if(compare(iter->first, protected_function)) {
iter = results->m_notification_tokens.erase(iter);
}
else {
iter++;
}
}
}
template<typename T>
void ResultsClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
auto results = get_internal<T, ResultsClass<T>>(this_object);
results->m_notification_tokens.clear();
}
} // js
} // realm

226
src/js_sync.hpp Normal file
View File

@ -0,0 +1,226 @@
////////////////////////////////////////////////////////////////////////////
//
// 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 <list>
#include <map>
#include <set>
#include "js_class.hpp"
#include "js_collection.hpp"
#include "sync_manager.hpp"
#include "sync_config.hpp"
#include "sync_session.hpp"
#include "realm/util/logger.hpp"
#include "realm/util/uri.hpp"
#if REALM_PLATFORM_NODE
#include "node/node_sync_logger.hpp"
#endif
#if !REALM_DEVELOPER_EDITION
#include "js_enterprise.hpp"
#endif
namespace realm {
namespace js {
template<typename T>
class SyncClass : public ClassDefinition<T, void *> {
using GlobalContextType = typename T::GlobalContext;
using ContextType = typename T::Context;
using FunctionType = typename T::Function;
using ObjectType = typename T::Object;
using ValueType = typename T::Value;
using String = js::String<T>;
using Object = js::Object<T>;
using Value = js::Value<T>;
using Function = js::Function<T>;
using ReturnValue = js::ReturnValue<T>;
using NativeAccessor = realm::NativeAccessor<ValueType, ContextType>;
public:
std::string const name = "Sync";
static FunctionType create_constructor(ContextType);
static void set_sync_log_level(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void set_verify_servers_ssl_certificate(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
#if REALM_PLATFORM_NODE
static void set_sync_logger(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
#endif
// private
static void refresh_access_token(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void populate_sync_config(ContextType, ObjectType config_object, Realm::Config&);
// static properties
static void get_is_developer_edition(ContextType, ObjectType, ReturnValue &);
MethodMap<T> const static_methods = {
{"refreshAccessToken", wrap<refresh_access_token>},
{"setLogLevel", wrap<set_sync_log_level>},
{"setVerifyServersSslCertificate", wrap<set_verify_servers_ssl_certificate>},
#if REALM_PLATFORM_NODE
{"setSyncLogger", wrap<set_sync_logger>},
#endif
};
PropertyMap<T> const static_properties {
{"isDeveloperEdition", {wrap<get_is_developer_edition>, nullptr}}
};
};
template<typename T>
inline typename T::Function SyncClass<T>::create_constructor(ContextType ctx) {
FunctionType sync_constructor = ObjectWrap<T, SyncClass<T>>::create_constructor(ctx);
Protected<ValueType> refresh(ctx, Object::validated_get_function(ctx, sync_constructor, std::string("refreshAccessToken")));
Protected<ObjectType> protected_sync(ctx, sync_constructor);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
realm::SyncManager::shared().set_login_function([=](const std::string& path, const realm::SyncConfig& config) {
typename T::HandleScope handle_scope;
FunctionType user_constructor = Object::validated_get_function(protected_ctx, protected_sync, std::string("User"));
FunctionType authenticate = Object::validated_get_function(protected_ctx, user_constructor, "authenticateRealm");
ObjectType users = Object::validated_get_object(protected_ctx, user_constructor, std::string("activeUsers"));
ObjectType user = Object::validated_get_object(protected_ctx, users, config.user_tag.c_str(), "Invalid user identity");
if (Object::validated_get_boolean(protected_ctx, user, "isAdmin")) {
std::string token = Object::validated_get_string(protected_ctx, user, "token");
// FIXME: This log-in callback is called while the object store still holds some sync-related locks.
// Notify the object store of the access token asynchronously to avoid the deadlock that would result
// from reentering the object store here.
auto thread = std::thread([path, config, token]{
auto session = SyncManager::shared().get_existing_active_session(path);
session->refresh_access_token(token, config.realm_url);
});
thread.detach();
}
else {
ValueType arguments[3];
arguments[0] = Value::from_string(protected_ctx, path.c_str());
arguments[1] = Value::from_string(protected_ctx, config.realm_url.c_str());
arguments[2] = refresh;
Function::call(protected_ctx, authenticate, user, 3, arguments);
}
});
realm::SyncManager::shared().set_error_handler([=](int error_code, std::string message) {
std::cout << error_code << " " << message << std::endl;
});
#if REALM_PLATFORM_NODE
// AtExit is called before the V8 VM is disposed. We use it to clean the captured JS objects in the login function.
// Should they be destructed after the VM is disposed, there will be a segmentation fault during node's shutdown.
::node::AtExit([](void*) {
realm::SyncManager::shared().set_login_function(nullptr);
});
#endif
#if !REALM_DEVELOPER_EDITION
SyncEnterpriseClass<T>::add_methods(ctx, sync_constructor);
#endif
return sync_constructor;
}
template<typename T>
void SyncClass<T>::set_sync_log_level(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
std::string log_level = Value::validated_to_string(ctx, arguments[0]);
std::istringstream in(log_level); // Throws
in.imbue(std::locale::classic()); // Throws
in.unsetf(std::ios_base::skipws);
util::Logger::Level log_level_2 = util::Logger::Level();
in >> log_level_2; // Throws
if (!in || !in.eof())
throw std::runtime_error("Bad log level");
realm::SyncManager::shared().set_log_level(log_level_2);
}
template<typename T>
void SyncClass<T>::set_verify_servers_ssl_certificate(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
bool verify_servers_ssl_certificate = Value::validated_to_boolean(ctx, arguments[0]);
realm::SyncManager::shared().set_client_should_validate_ssl(verify_servers_ssl_certificate);
}
#if REALM_HAVE_NODE_SYNC_LOGGER
template<typename T>
void SyncClass<T>::set_sync_logger(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
auto callback = Value::validated_to_function(ctx, arguments[0]);
node::SyncLoggerFactory *factory = new node::SyncLoggerFactory(ctx, this_object, callback); // Throws
realm::SyncManager::shared().set_logger_factory(*factory);
}
#endif
template<typename T>
void SyncClass<T>::refresh_access_token(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 2);
static const String token_string = "token";
static const String file_url_string = "file_url";
static const String realm_url_string = "resolved_realm_url";
ObjectType json_arguments = Value::validated_to_object(ctx, arguments[1]);
std::string token = Object::validated_get_string(ctx, json_arguments, token_string);
std::string file_url = Object::validated_get_string(ctx, json_arguments, file_url_string);
std::string realm_url = Object::validated_get_string(ctx, json_arguments, realm_url_string);
if (auto session = SyncManager::shared().get_existing_active_session(file_url)) {
session->refresh_access_token(token, realm_url);
return_value.set(true);
}
else {
return_value.set(false);
}
}
template<typename T>
void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType config_object, Realm::Config& config) {
ValueType sync_config_value = Object::get_property(ctx, config_object, "sync");
if (!Value::is_undefined(ctx, sync_config_value)) {
auto sync_config_object = Value::validated_to_object(ctx, sync_config_value);
ObjectType user = Object::validated_get_object(ctx, sync_config_object, "user");
config.sync_config = std::shared_ptr<SyncConfig>(
new SyncConfig(Object::validated_get_string(ctx, user, "identity"),
Object::validated_get_string(ctx, sync_config_object, "url"),
{}, SyncSessionStopPolicy::AfterChangesUploaded)
);
config.schema_mode = SchemaMode::Additive;
}
}
template<typename T>
void SyncClass<T>::get_is_developer_edition(ContextType ctx, ObjectType object, ReturnValue &return_value) {
#if REALM_DEVELOPER_EDITION
return_value.set(true);
#else
return_value.set(false);
#endif
}
} // js
} // realm

View File

@ -133,6 +133,11 @@ struct Function {
using ValueType = typename T::Value;
static ValueType call(ContextType, const FunctionType &, const ObjectType &, size_t, const ValueType[]);
template<size_t N> static ValueType call(ContextType ctx, const FunctionType &function,
const ObjectType &this_object, const ValueType (&arguments)[N])
{
return call(ctx, function, this_object, N, arguments);
}
static ValueType call(ContextType ctx, const FunctionType &function, size_t argument_count, const ValueType arguments[]) {
return call(ctx, function, {}, argument_count, arguments);
}
@ -247,6 +252,10 @@ class Protected {
bool operator!=(const ValueType &) const;
bool operator==(const Protected<ValueType> &) const;
bool operator!=(const Protected<ValueType> &) const;
struct Comparator {
bool operator()(const Protected<ValueType>& a, const Protected<ValueType>& b) const;
};
};
template<typename T>

View File

@ -47,6 +47,12 @@ class Protected<JSGlobalContextRef> {
operator bool() const {
return m_context != nullptr;
}
struct Comparator {
bool operator() (const Protected<JSGlobalContextRef>& a, const Protected<JSGlobalContextRef>& b) const {
return a.m_context == b.m_context;
}
};
};
template<>
@ -75,6 +81,21 @@ class Protected<JSValueRef> {
operator bool() const {
return m_value != nullptr;
}
struct Comparator {
bool operator() (const Protected<JSValueRef>& a, const Protected<JSValueRef>& b) const {
if (a.m_context != b.m_context) {
return false;
}
return JSValueIsStrictEqual(a.m_context, a.m_value, b.m_value);
}
};
Protected<JSValueRef>& operator=(Protected<JSValueRef> other) {
std::swap(m_context, other.m_context);
std::swap(m_value, other.m_value);
return *this;
}
};
template<>
@ -89,6 +110,11 @@ class Protected<JSObjectRef> : public Protected<JSValueRef> {
JSValueRef value = static_cast<JSValueRef>(*this);
return (JSObjectRef)value;
}
Protected<JSObjectRef>& operator=(Protected<JSObjectRef> other) {
std::swap(*this, other);
return *this;
}
};
} // js

View File

@ -34,6 +34,7 @@ struct Types {
using Object = JSObjectRef;
using String = JSStringRef;
using Function = JSObjectRef;
using HandleScope = void *;
using ConstructorCallback = JSObjectCallAsConstructorCallback;
using FunctionCallback = JSObjectCallAsFunctionCallback;

View File

@ -1,85 +0,0 @@
{
"targets": [
{
"target_name": "realm",
"sources": [
"node_init.cpp",
"platform.cpp",
"../js_realm.cpp",
"../object-store/src/collection_notifications.cpp",
"../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/thread_confined.cpp",
"../object-store/src/impl/collection_change_builder.cpp",
"../object-store/src/impl/collection_notifier.cpp",
"../object-store/src/impl/handover.cpp",
"../object-store/src/impl/list_notifier.cpp",
"../object-store/src/impl/realm_coordinator.cpp",
"../object-store/src/impl/results_notifier.cpp",
"../object-store/src/impl/transact_log_handler.cpp",
"../object-store/src/impl/node/weak_realm_notifier.cpp",
"../object-store/src/parser/parser.cpp",
"../object-store/src/parser/query_builder.cpp",
"../object-store/src/util/format.cpp",
"../object-store/src/util/thread_id.cpp"
],
"include_dirs": [
"..",
"../object-store/src",
"../object-store/src/impl",
"../object-store/src/impl/apple",
"../object-store/src/parser",
"../object-store/external/pegtl",
"../../core-node/include",
"<!(node -e 'require(\"nan\")')"
],
"library_dirs": [
"$(srcdir)/../../core-node"
],
"defines": [
"REALM_HAVE_CONFIG",
"REALM_PLATFORM_NODE=1"
],
"cflags_cc": [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wno-missing-field-initializers",
"-Wno-return-type"
],
"libraries": ["-lrealm-node"],
"xcode_settings": {
"CLANG_CXX_LANGUAGE_STANDARD": "c++14",
"CLANG_CXX_LIBRARY": "libc++",
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
"GCC_ENABLE_CPP_RTTI": "YES",
"MACOSX_DEPLOYMENT_TARGET": "10.8",
"OTHER_LDFLAGS": ["-framework", "Foundation"]
},
"conditions": [
[
"OS=='linux'", {
"sources": [
"../object-store/src/impl/android/external_commit_helper.cpp",
]
}
],
["OS=='mac'", {
"sources": [
"../object-store/src/impl/apple/external_commit_helper.cpp"
]
}]
],
"configurations": {
"Debug": {
"defines": ["DEBUG=1"]
}
}
}
]
}

View File

@ -1,28 +0,0 @@
#!/bin/bash
set -o pipefail
set -e
cd "$(dirname "$0")"
CONFIGURATION="${1:-"Release"}"
PATH="$(cd ../../node_modules/.bin && pwd):$PATH"
if [ -s "${HOME}/.nvm/nvm.sh" ]; then
. "${HOME}/.nvm/nvm.sh"
nvm use 5.4.0 || true
fi
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
# Link to the appropriate module in the build directory.
cd ../..
mkdir -p build
ln -fs "../src/node/build/$CONFIGURATION/realm.node" build/

89
src/node/gyp/realm.gyp Normal file
View File

@ -0,0 +1,89 @@
{
"variables": {
"use_realm_debug": "<!(echo $REALMJS_USE_DEBUG_CORE)",
},
"targets": [
{
"target_name": "realm-core",
"type": "none",
"direct_dependent_settings": {
"conditions": [
["use_realm_debug!=''", {
"libraries": [ "-lrealm-node-dbg" ],
"defines": [ "REALM_DEBUG=1" ]
}, {
"libraries": [ "-lrealm-node" ]
}]
]
},
"all_dependent_settings": {
"defines": [ "REALM_HAVE_CONFIG", "REALM_PLATFORM_NODE=1", "REALM_ENABLE_SYNC" ]
},
"variables": {
"prefix": "<!(echo $REALM_CORE_PREFIX)"
},
"conditions": [
["prefix!=''", {
"all_dependent_settings": {
"include_dirs": [ "<(prefix)/src" ],
},
"direct_dependent_settings": {
"library_dirs": [ "<(prefix)/src/realm" ]
}
}, {
"conditions": [
["OS=='mac'", {
"dependencies": [ "vendored-realm" ]
}]
]
}]
]
},
{
"target_name": "realm-sync",
"type": "none",
"dependencies": [ "realm-core" ], # sync headers include core headers
"direct_dependent_settings": {
"conditions": [
["use_realm_debug!=''", {
"libraries": [ "-lrealm-sync-node-dbg" ]
}, {
"libraries": [ "-lrealm-sync-node" ]
}]
]
},
"export_dependent_settings": [ "realm-core" ], # depending on sync is tantamount to depending on core
"variables": {
"prefix": "<!(echo $REALM_SYNC_PREFIX)"
},
"conditions": [
["prefix!=''", {
"all_dependent_settings": {
"include_dirs": [ "<(prefix)/src" ],
},
"direct_dependent_settings": {
"library_dirs": [ "<(prefix)/src/realm" ]
}
},
{
"conditions": [
["OS=='mac'", {
"dependencies": [ "vendored-realm" ]
}]
]
}]
],
},
{
"variables": {
"realm_vendor_dir%": "<(module_root_dir)/vendor",
},
"target_name": "vendored-realm",
"type": "none",
"all_dependent_settings": {
"include_dirs": [ "<(realm_vendor_dir)/realm-sync/include" ],
"library_dirs": [ "<(realm_vendor_dir)/realm-sync/osx" ]
}
}
]
}

View File

@ -0,0 +1,42 @@
{
"target_defaults": {
"variables": {
"warning-flags": [
"-Wno-missing-field-initializers",
"-Wno-return-type"
]
},
"cflags_cc!": [ # turn off default flags on older nodes on linux
"-fno-exceptions",
"-fno-rtti",
"-std=gnu++0x"
],
"cflags_cc": [
"-fexceptions",
"-frtti",
"-std=c++14",
"<@(warning-flags)"
],
"include_dirs": [
"<!(node -e \"require('nan')\")"
],
"configurations": {
"Debug": {
"defines": ["DEBUG=1"]
}
},
"conditions": [
["OS=='mac'", {
"xcode_settings": {
"CLANG_CXX_LANGUAGE_STANDARD": "c++14",
"CLANG_CXX_LIBRARY": "libc++",
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
"GCC_ENABLE_CPP_RTTI": "YES",
"MACOSX_DEPLOYMENT_TARGET": "10.8",
"OTHER_LDFLAGS": ["-framework Foundation"],
"WARNING_CFLAGS": [ "<@(warning-flags)" ]
}
}]
]
}
}

View File

@ -17,6 +17,7 @@
////////////////////////////////////////////////////////////////////////////
#include "node_init.hpp"
#include "js_realm.hpp"
namespace realm {
namespace node {

View File

@ -27,5 +27,3 @@
#include "node_exception.hpp"
#include "node_return_value.hpp"
#include "node_object_accessor.hpp"
#include "js_realm.hpp"

View File

@ -35,7 +35,7 @@ class Protected {
operator v8::Local<MemberType>() const {
return Nan::New(m_value);
}
operator bool() const {
explicit operator bool() const {
return m_value.isEmpty();
}
bool operator==(const v8::Local<MemberType> &other) const {
@ -50,6 +50,12 @@ class Protected {
bool operator!=(const Protected<MemberType> &other) const {
return m_value != other.m_value;
}
struct Comparator {
bool operator()(const Protected<MemberType>& a, const Protected<MemberType>& b) const {
return Nan::New(a.m_value)->StrictEquals(Nan::New(b.m_value));
}
};
};
} // node

View File

@ -0,0 +1,94 @@
////////////////////////////////////////////////////////////////////////////
//
// 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 <utility>
#include <stdexcept>
#include <mutex>
#include "node_init.hpp"
#include "node_class.hpp"
#include "js_realm.hpp"
#include "node_types.hpp"
#include "node_sync_logger.hpp"
namespace realm {
namespace node {
SyncLoggerQueue::~SyncLoggerQueue() noexcept
{
m_callback_this_object.Reset();
m_callback.Reset();
}
void SyncLoggerQueue::log_uv_callback()
{
// This function is always executed by the Node.js event loop
// thread.
Nan::HandleScope scope;
v8::Local<v8::Object> this_object = Nan::New(m_callback_this_object);
v8::Local<v8::Function> callback = Nan::New(m_callback);
std::queue<SyncLoggerMessage> popped;
{
std::lock_guard<std::mutex> lock(m_mutex); // Throws
popped.swap(m_log_queue);
}
for (;;) {
if (popped.empty())
break;
Nan::TryCatch trycatch;
v8::Local<v8::Value> argv[] = {
Nan::New((int)(popped.front().m_level)),
Nan::New(popped.front().m_message).ToLocalChecked()
};
Nan::MakeCallback(this_object, callback, 2, argv);
if (trycatch.HasCaught()) {
throw node::Exception(m_v8_isolate, trycatch.Exception());
}
popped.pop();
}
}
void SyncLogger::do_log(realm::util::Logger::Level level, std::string message)
{
std::lock_guard<std::mutex> lock(m_mutex); // Throws
m_log_queue.emplace(level, message);
m_log_uv_async.send();
}
SyncLoggerFactory::~SyncLoggerFactory() noexcept
{
m_callback_this_object.Reset();
m_callback.Reset();
}
std::unique_ptr<util::Logger> SyncLoggerFactory::make_logger(util::Logger::Level level)
{
v8::Local<v8::Object> this_object = Nan::New(m_callback_this_object);
v8::Local<v8::Function> callback = Nan::New(m_callback);
SyncLogger *logger = new SyncLogger(m_v8_isolate, this_object, callback); // Throws
logger->set_level_threshold(level);
return std::unique_ptr<util::Logger>(logger);
}
} // namespace node
} // namespace realm

View File

@ -0,0 +1,89 @@
////////////////////////////////////////////////////////////////////////////
//
// 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.
//
////////////////////////////////////////////////////////////////////////////
#ifndef REALM_NODE_GLOBAL_LOGGER_HPP
#define REALM_NODE_GLOBAL_LOGGER_HPP
#include <string>
#include <set>
#include <queue>
#include <nan.h>
#include "node_uv_async.hpp"
namespace realm {
namespace node {
struct SyncLoggerMessage {
std::string m_message;
realm::util::Logger::Level m_level;
SyncLoggerMessage(realm::util::Logger::Level level, std::string message):
m_message(message),
m_level(level) { }
};
class SyncLoggerQueue {
public:
SyncLoggerQueue(v8::Isolate* v8_isolate, v8::Local<v8::Object> callback_this_object, v8::Local<v8::Function> callback) :
m_log_uv_async([this] { log_uv_callback(); }), // Throws
m_v8_isolate(v8_isolate),
m_callback_this_object(callback_this_object),
m_callback(callback) { }
~SyncLoggerQueue() noexcept;
protected:
void log_uv_callback();
std::queue<SyncLoggerMessage> m_log_queue;
std::mutex m_mutex;
UvAsync m_log_uv_async;
private:
v8::Isolate* m_v8_isolate;
Nan::Persistent<v8::Object> m_callback_this_object;
Nan::Persistent<v8::Function> m_callback;
};
class SyncLogger: public realm::util::RootLogger, public SyncLoggerQueue {
public:
SyncLogger(v8::Isolate* v8_isolate, v8::Local<v8::Object> callback_this_object, v8::Local<v8::Function> callback) :
SyncLoggerQueue(v8_isolate, callback_this_object, callback) { }
protected:
void do_log(realm::util::Logger::Level, std::string) override final;
};
class SyncLoggerFactory : public realm::SyncLoggerFactory {
public:
SyncLoggerFactory(v8::Isolate* v8_isolate, v8::Local<v8::Object> callback_this_object, v8::Local<v8::Function> callback) :
m_v8_isolate(v8_isolate),
m_callback_this_object(callback_this_object),
m_callback(callback) { }
~SyncLoggerFactory() noexcept;
virtual std::unique_ptr<util::Logger> make_logger(util::Logger::Level level);
private:
v8::Isolate* m_v8_isolate;
Nan::Persistent<v8::Object> m_callback_this_object;
Nan::Persistent<v8::Function> m_callback;
};
} // namespace node
} // namespace realm
#endif // REALM_NODE_GLOBAL_LOGGER_HPP

View File

@ -41,6 +41,7 @@ struct Types {
using Object = v8::Local<v8::Object>;
using String = v8::Local<v8::String>;
using Function = v8::Local<v8::Function>;
using HandleScope = Nan::HandleScope;
using ConstructorCallback = Nan::FunctionCallback;
using FunctionCallback = Nan::FunctionCallback;

104
src/node/node_uv_async.hpp Normal file
View File

@ -0,0 +1,104 @@
////////////////////////////////////////////////////////////////////////////
//
// 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.
//
////////////////////////////////////////////////////////////////////////////
#ifndef REALM_NODE_UV_ASYNC_HPP
#define REALM_NODE_UV_ASYNC_HPP
#include <functional>
#include <memory>
#include <utility>
#include <stdexcept>
#include <uv.h>
namespace realm {
namespace node {
// Must be created and destroyed by the thread that is associated with the
// specified libuv event loop.
class UvAsync {
public:
UvAsync(std::function<void()> func, uv_loop_t* = uv_default_loop());
~UvAsync() noexcept;
/// Schedule the associated callback function to be executed by the
/// associated libuv event loop. May be called by any thread.
void send();
private:
struct Rep;
Rep* m_rep;
static void exec(uv_async_t* handle) noexcept;
static void close(uv_handle_t* handle) noexcept;
};
// implementation
struct UvAsync::Rep {
uv_async_t handle;
std::function<void()> func;
};
inline UvAsync::UvAsync(std::function<void()> func, uv_loop_t* loop)
{
std::unique_ptr<Rep> rep(new Rep);
rep->handle.data = &*rep;
rep->func = std::move(func);
int ret = uv_async_init(loop, &rep->handle, &UvAsync::exec);
if (ret < 0)
throw std::runtime_error("uv_async_init() failed");
m_rep = rep.release();
}
inline UvAsync::~UvAsync() noexcept
{
m_rep->func = std::function<void()>();
uv_close(reinterpret_cast<uv_handle_t*>(&m_rep->handle), &UvAsync::close);
}
inline void UvAsync::send()
{
int ret = uv_async_send(&m_rep->handle);
if (ret < 0)
throw std::runtime_error("uv_async_send() failed");
}
inline void UvAsync::exec(uv_async_t* handle) noexcept
{
Rep* rep = static_cast<Rep*>(handle->data);
if (rep->func)
rep->func(); // Throws
// FIXME: How to deal with C++ exceptions here?
}
inline void UvAsync::close(uv_handle_t* handle) noexcept
{
Rep* rep = static_cast<Rep*>(handle->data);
delete rep;
}
} // namespace node
} // namespace realm
#endif // REALM_NODE_UV_ASYNC_HPP

View File

@ -129,7 +129,7 @@ inline bool node::Value::to_boolean(v8::Isolate* isolate, const v8::Local<v8::Va
template<>
inline double node::Value::to_number(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
double number = Nan::To<double>(value).FromMaybe(NAN);
if (isnan(number)) {
if (std::isnan(number)) {
throw std::invalid_argument("Value not convertible to a number.");
}
return number;

85
test.js Normal file
View File

@ -0,0 +1,85 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
'use strict';
const Realm = require('.');
const prompt = require('prompt');
const mkdirp = require('mkdirp');
const wildcard = require('wildcard');
// User.loginWithProvider('http://127.0.0.1:8080/', 'debug', 'abcd', function(error, user) {
// console.log(user);
// });
Realm.Sync.User.create('http://127.0.0.1:9080/', 'ari', 'aaa', function(error, user) {
var notifier_dir = './notifier';
mkdirp.sync(notifier_dir);
var access_token = 'ewoJImlkZW50aXR5IjogImFkbWluIiwKCSJhY2Nlc3MiOiBbInVwbG9hZCIsICJkb3dubG9hZCIsICJtYW5hZ2UiXQp9Cg==:DlFksxA+cJyEOc9bu6JwBUfDi4fJCagjAcIPPsoisjqfmOzSrk5Omuw0IkxCRU534p2+CAAj5IOH47DfObPtAA8q2DHguYDOKWYxyktS/6doPCqDHYN7k9EgUHdPTkESNkuPZbaVfXZTGzocB8m7+MaEXJde7FGPbh1sBz/+sPldnlAhnOqO5QbWzIEyoGHiOSg3V7UCh2H8kalr3tef7fkE2X65OBMgcarPvM5M6sPijOx2N5zrVrjL2wvguP9zS+g2ybFPUqV3DGv3S8cnGA+wVId/jCfGc2ujNhecunJdENH+/pL+0BTYHCFEWkY1WP1NUyti60FwRaXAtcYxeA==';
var admin_user = new Realm.Sync.User.adminUser('http://127.0.0.1:9080/', access_token);
Realm.Sync.setLogLevel('error');
Realm.Sync.setGlobalListener(notifier_dir, 'realm://127.0.0.1:9080', admin_user,
(name) => {
console.log('filter: ' + name);
return true;
},
(name, realm, changes) => {
console.log('change: ' + name);
console.log(changes);
}
);
console.log('global notifier listening...');
Realm.Sync.User.login('http://127.0.0.1:9080/', 'ari', 'aaa', function(error, user) {
console.log(user);
var realm = new Realm({
sync: {
user: user,
url: 'realm://127.0.0.1:9080/~/demo/realm1'
},
schema: [{
name: 'IntObject',
properties: {
int: 'int'
}
}]
});
function create(err, result) {
if (err) {
exit();
}
if (!err) {
realm.write(() => {
realm.create('IntObject', {int: parseInt(result.int)});
});
console.log(realm.objects('IntObject'));
}
prompt.get(['int'], create);
}
prompt.start();
prompt.get(['int'], create);
});
});

2
tests/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/sync-bundle/
/junitresults-*.xml

View File

@ -23,14 +23,13 @@
const fs = require('fs');
const path = require('path');
const mockery = require('mockery');
function runTests() {
const Realm = require('realm');
const RealmTests = require('./js');
RealmTests.registerTests({
WorkerTests: require('./js/worker-tests'),
AsyncTests: require('./js/async-tests'),
});
const testNames = RealmTests.getTestNames();
@ -79,10 +78,6 @@ function runTests() {
}
if (require.main == module) {
mockery.enable();
mockery.warnOnUnregistered(false);
mockery.registerMock('realm', require('..'));
runTests().then(
(passed) => {
if (!passed) {

View File

@ -27,5 +27,6 @@
- (JSValue *)loadModuleFromURL:(NSURL *)url error:(NSError **)error;
- (JSValue *)loadJSONFromURL:(NSURL *)url error:(NSError **)error;
- (JSValue *)loadGlobalModule:(NSString *)name relativeToURL:(NSURL *)url error:(NSError **)error;
@end

View File

@ -195,6 +195,18 @@ static NSString * const RJSModuleLoaderErrorDomain = @"RJSModuleLoaderErrorDomai
BOOL isDirectory;
if ([fileManager fileExistsAtPath:moduleURL.path isDirectory:&isDirectory] && isDirectory) {
NSURL *packageURL = [moduleURL URLByAppendingPathComponent:@"package.json"];
NSDictionary *package;
if ([fileManager fileExistsAtPath:packageURL.path]) {
NSError *error;
NSData *data = [NSData dataWithContentsOfURL:packageURL options:0 error:&error];
package = data ? [NSJSONSerialization JSONObjectWithData:data options:0 error:&error] : nil;
NSAssert(package, @"%@", error);
}
moduleURL = [moduleURL URLByAppendingPathComponent:package[@"main"] ?: @"index.js"];
return [self loadModuleFromURL:moduleURL error:error];
}

View File

@ -31,12 +31,23 @@
+ (XCTestSuite *)defaultTestSuite {
XCTestSuite *suite = [super defaultTestSuite];
JSContext *context = [[JSContext alloc] init];
// We need a JS context from a UIWebView so it has setTimeout, Promise, etc.
UIWebView *webView = [[UIWebView alloc] init];
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
RJSModuleLoader *moduleLoader = [[RJSModuleLoader alloc] initWithContext:context];
NSURL *realmURL = [[NSBundle bundleForClass:self] URLForResource:@"index" withExtension:@"js" subdirectory:@"lib"];
NSURL *scriptURL = [[NSBundle bundleForClass:self] URLForResource:@"index" withExtension:@"js" subdirectory:@"js"];
NSError *error;
// The ES6 global Promise constructor was added in iOS 8.
if (![context[@"Promise"] isObject]) {
JSValue *promiseModule = [moduleLoader loadGlobalModule:@"es6-promise" relativeToURL:scriptURL error:&error];
NSAssert(promiseModule, @"%@", error);
context[@"Promise"] = promiseModule[@"Promise"];
}
// Create Realm constructor in the JS context.
RJSInitializeInContext(context.JSGlobalContextRef);
@ -73,25 +84,46 @@
JSContext *context = testObject.context;
context.exception = nil;
[testObject invokeMethod:@"runTest" withArguments:@[NSStringFromClass(self.class), method]];
JSValue *promise = [testObject invokeMethod:@"runTest" withArguments:@[NSStringFromClass(self.class), method]];
JSValue *exception = context.exception;
if (exception) {
JSValue *message = [exception hasProperty:@"message"] ? exception[@"message"] : exception;
NSString *source = [exception hasProperty:@"sourceURL"] ? [exception[@"sourceURL"] toString] : nil;
NSUInteger line = [exception hasProperty:@"line"] ? [exception[@"line"] toUInt32] - 1 : 0;
NSURL *sourceURL = nil;
if (context.exception) {
[self recordException:context.exception];
return;
}
if (source) {
NSString *path = [NSString pathWithComponents:@[[@(__FILE__) stringByDeletingLastPathComponent], @"..", @"js", source.lastPathComponent]];
sourceURL = [NSURL URLWithString:path];
}
if ([promise isObject]) {
XCTestExpectation *expectation = [self expectationWithDescription:@"Promise resolved or rejected"];
[self recordFailureWithDescription:message.description
inFile:sourceURL ? sourceURL.absoluteString : @(__FILE__)
atLine:sourceURL ? line : __LINE__
expected:YES];
JSValue *onFulfilled = [JSValue valueWithObject:^() {
[expectation fulfill];
} inContext:context];
JSValue *onRejected = [JSValue valueWithObject:^(JSValue *error) {
[self recordException:error];
[expectation fulfill];
} inContext:context];
[promise invokeMethod:@"then" withArguments:@[onFulfilled, onRejected]];
[self waitForExpectationsWithTimeout:5.0 handler:NULL];
}
}
- (void)recordException:(JSValue *)exception {
JSValue *message = [exception hasProperty:@"message"] ? exception[@"message"] : exception;
NSString *source = [exception hasProperty:@"sourceURL"] ? [exception[@"sourceURL"] toString] : nil;
NSUInteger line = [exception hasProperty:@"line"] ? [exception[@"line"] toUInt32] - 1 : 0;
NSURL *sourceURL = nil;
if (source) {
NSString *path = [NSString pathWithComponents:@[[@(__FILE__) stringByDeletingLastPathComponent], @"..", @"js", source.lastPathComponent]];
sourceURL = [NSURL URLWithString:path];
}
[self recordFailureWithDescription:message.description
inFile:sourceURL ? sourceURL.absoluteString : @(__FILE__)
atLine:sourceURL ? line : __LINE__
expected:YES];
}
@end

360
tests/js/async-tests.js Normal file
View File

@ -0,0 +1,360 @@
////////////////////////////////////////////////////////////////////////////
//
// 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 */
'use strict';
const Realm = require('realm');
const TestCase = require('./asserts');
const schemas = require('./schemas');
const Worker = require('./worker');
function createNotificationTest(config, getObservable, addListener, removeListener, messages, expectedCount) {
return new Promise((resolve, reject) => {
let realm = new Realm(config);
let observable = getObservable(realm);
let worker = new Worker(__dirname + '/worker-tests-script.js');
// Test will fail if it does not receive a change event within a second.
let timer = setTimeout(() => {
reject(new Error('Timed out waiting for change notification'));
}, 5000);
let cleanup = () => {
clearTimeout(timer);
worker.terminate();
};
var count = 0;
var listener = addListener(observable, () => count++, resolve, reject, cleanup);
messages.push(['echo', 'resolve']);
var messageIndex = 0;
worker.onmessage = (message) => {
if (message.error) {
reject(message.error);
cleanup();
}
else if (message.result == 'resolve') {
if (count != expectedCount) {
reject('Notification count ' + count + ' not equal to expected count ' + expectedCount);
}
else {
resolve();
}
cleanup();
}
else {
if (message.result == 'removeListener') {
removeListener(observable, listener);
}
worker.postMessage(messages[messageIndex++]);
}
};
worker.postMessage(messages[messageIndex++]);
});
};
function createCollectionChangeTest(config, createCollection, messages, expected, removeAll) {
return createNotificationTest(
config,
createCollection,
(collection, increment, resolve, reject, cleanup) => {
var listener = (object, changes) => {
try {
var notificationCount = increment();
TestCase.assertArraysEqual(changes.insertions, expected[notificationCount][0]);
TestCase.assertArraysEqual(changes.deletions, expected[notificationCount][1]);
TestCase.assertArraysEqual(changes.modifications, expected[notificationCount][2]);
} catch (e) {
reject(e);
cleanup();
}
};
collection.addListener(listener);
return listener;
},
removeAll ? (observable) => observable.removeAllListeners() :
(observable, listener) => observable.removeListener(listener),
messages,
expected.length
);
};
const ListObject = {
name: 'ListObject',
properties: {
list: {type: 'list', objectType: 'TestObject'},
}
};
const PrimaryListObject = {
name: 'PrimaryListObject',
properties: {
list: {type: 'list', objectType: 'IntPrimaryObject'},
}
};
module.exports = {
testChangeNotifications() {
var config = { schema: [schemas.TestObject] };
return createNotificationTest(
config,
(realm) => realm,
(realm, increment, resolve, reject, cleanup) => realm.addListener('change', () => {
try {
var objects = realm.objects('TestObject');
TestCase.assertEqual(objects.length, 1);
TestCase.assertEqual(objects[0].doubleCol, 42);
increment();
} catch (e) {
reject(e);
cleanup();
}
}),
undefined,
[[config, 'create', 'TestObject', [{doubleCol: 42}]]],
1
);
},
testResultsAddNotifications() {
var config = { schema: [schemas.TestObject] };
return createCollectionChangeTest(
config,
(realm) => realm.objects('TestObject'),
[
[config, 'create', 'TestObject', [{ doubleCol: 1 }]],
[config, 'create', 'TestObject', [{ doubleCol: 2 }, { doubleCol: 3 }]]
],
[
[[], [], []],
[[0], [], []],
[[1, 2], [], []],
]
);
},
testResultsRemoveNotifications() {
var config = { schema: [schemas.TestObject] };
return createCollectionChangeTest(
config,
(realm) => realm.objects('TestObject'),
[
[config, 'create', 'TestObject', [{ doubleCol: 1 }]],
['echo', 'removeListener'],
[config, 'create', 'TestObject', [{ doubleCol: 2 }, { doubleCol: 3 }]]
],
[
[[], [], []],
[[0], [], []],
]
);
},
testResultsRemoveAllNotifications() {
var config = { schema: [schemas.TestObject] };
return createCollectionChangeTest(
config,
(realm) => realm.objects('TestObject'),
[
[config, 'create', 'TestObject', [{ doubleCol: 1 }]],
['echo', 'removeListener'],
[config, 'create', 'TestObject', [{ doubleCol: 2 }, { doubleCol: 3 }]]
],
[
[[], [], []],
[[0], [], []],
],
true
);
},
testResultsDeleteNotifications() {
var config = { schema: [schemas.TestObject] };
return createCollectionChangeTest(
config,
function(realm) {
return realm.objects('TestObject');
},
[
[config, 'create', 'TestObject', [[0], [1], [2], [3], [4]]],
[config, 'delete', 'TestObject', [4]],
[config, 'delete', 'TestObject', [0, 2]]
],
[
[[], [], []],
[[0, 1, 2, 3, 4], [], []],
[[], [4], []],
[[0], [0, 2, 3], []]
]
);
},
testResultsUpdateNotifications() {
var config = { schema: [schemas.IntPrimary] };
return createCollectionChangeTest(
config,
(realm) => realm.objects('IntPrimaryObject'),
[
[config, 'create', 'IntPrimaryObject', [[0, '0'], [1, '1'], [2, '2']]],
[config, 'update', 'IntPrimaryObject', [[0, '00'], [2, '22']]]
],
[
[[], [], []],
[[0, 1, 2], [], []],
[[], [], [0, 2]]
]
);
},
testListAddNotifications() {
var config = { schema: [schemas.TestObject, ListObject] };
return createCollectionChangeTest(
config,
function(realm) {
let listObject;
realm.write(() => {
listObject = realm.create('ListObject', {list: []})
});
return listObject.list;
},
[
[config, 'list_method', 'ListObject', 'list', 'push', {doubleCol: 0}, {doubleCol: 1}]
],
[
[[], [], []],
[[0, 1], [], []]
]
);
},
testListRemoveNotifications() {
var config = { schema: [schemas.TestObject, ListObject] };
return createCollectionChangeTest(
config,
function(realm) {
let listObject;
realm.write(() => {
listObject = realm.create('ListObject', {list: []})
});
return listObject.list;
},
[
[config, 'list_method', 'ListObject', 'list', 'push', {doubleCol: 0}, {doubleCol: 1}],
['echo', 'removeListener'],
[config, 'list_method', 'ListObject', 'list', 'push', {doubleCol: 0}, {doubleCol: 1}],
],
[
[[], [], []],
[[0, 1], [], []]
]
);
},
testListRemoveAllNotifications() {
var config = { schema: [schemas.TestObject, ListObject] };
return createCollectionChangeTest(
config,
function(realm) {
let listObject;
realm.write(() => {
listObject = realm.create('ListObject', {list: []})
});
return listObject.list;
},
[
[config, 'list_method', 'ListObject', 'list', 'push', {doubleCol: 0}, {doubleCol: 1}],
['echo', 'removeListener'],
[config, 'list_method', 'ListObject', 'list', 'push', {doubleCol: 0}, {doubleCol: 1}],
],
[
[[], [], []],
[[0, 1], [], []]
],
true
);
},
testListDeleteNotifications() {
var config = { schema: [schemas.TestObject, ListObject] };
return createCollectionChangeTest(
config,
function(realm) {
let listObject;
realm.write(() => {
listObject = realm.create('ListObject', {list: [[0], [1], [2]]})
});
return listObject.list;
},
[
[config, 'list_method', 'ListObject', 'list', 'splice', 1, 2]
],
[
[[], [], []],
[[], [1, 2], []]
]
);
},
testListSpliceNotifications() {
var config = { schema: [schemas.TestObject, ListObject] };
return createCollectionChangeTest(
config,
function(realm) {
let listObject;
realm.write(() => {
listObject = realm.create('ListObject', {list: [[0], [1], [2]]})
});
return listObject.list;
},
[
[config, 'list_method', 'ListObject', 'list', 'splice', 1, 1, [2]]
],
[
[[], [], []],
[[1], [1], []]
]
);
},
testListUpdateNotifications() {
var config = { schema: [schemas.IntPrimary, PrimaryListObject] };
return createCollectionChangeTest(
config,
function(realm) {
let listObject;
realm.write(() => {
listObject = realm.create('PrimaryListObject', {list: [[0, '0'], [1, '1']]})
});
return listObject.list;
},
[
[config, 'update', 'IntPrimaryObject', [[1, '11']]]
],
[
[[], [], []],
[[], [], [1]]
]
);
},
};

View File

@ -1,5 +1,8 @@
{
"name": "realm-tests",
"version": "0.0.1",
"private": true
"private": true,
"dependencies": {
"es6-promise": "^3.2.1"
}
}

View File

@ -33,7 +33,7 @@ module.exports = {
testRealmConstructorPath: function() {
TestCase.assertThrows(function() {
new Realm('/invalidpath');
new Realm('');
}, 'Realm cannot be created with an invalid path');
TestCase.assertThrows(function() {
new Realm('test1.realm', 'invalidArgument');

View File

@ -23,30 +23,39 @@
const Realm = require('../..');
const handlers = {
create(options) {
let realm = new Realm(options.config);
realm.write(() => {
realm.create(options.type, options.properties);
});
}
};
process.on('message', (message) => {
process.send(handleMessage(message));
});
function handleMessage(message) {
let error, result;
if (message[0] == 'echo') {
return {result: message[1]}
}
try {
let handler = handlers[message.action];
if (handler) {
result = handler(message);
} else {
throw new Error('Unknown worker action: ' + message.action);
}
let realm = new Realm(message[0]);
realm.write(() => {
if (message[1] == 'create') {
result = message[3].map((value) => realm.create(message[2], value));
}
else if (message[1] == 'delete') {
let objects = realm.objects(message[2]);
objects = message[3].map((index) => objects[index]);
realm.delete(objects);
}
else if (message[1] == 'update') {
result = message[3].map((value) => realm.create(message[2], value, true));
}
else if (message[1] == 'list_method') {
var listObject = realm.objects(message[2])[0];
var list = listObject[message[3]];
result = list[message[4]].apply(list, message.slice(5));
}
else {
throw new Error('Unknown realm method: ' + message[1]);
}
});
} catch (e) {
console.warn(e);
error = e.message;

View File

@ -1,76 +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.
//
////////////////////////////////////////////////////////////////////////////
/* eslint-env es6, node */
'use strict';
const Realm = require('realm');
const TestCase = require('./asserts');
const schemas = require('./schemas');
const Worker = require('./worker');
module.exports = {
testChangeNotifications() {
return new Promise((resolve, reject) => {
let config = {schema: [schemas.TestObject]};
let realm = new Realm(config);
let objects = realm.objects('TestObject');
let worker = new Worker(__dirname + '/worker-tests-script.js');
// Test will fail if it does not receive a change event within a second.
let timer = setTimeout(() => {
reject(new Error('Timed out waiting for change notification'));
}, 1000);
let cleanup = () => {
clearTimeout(timer);
worker.terminate();
};
// Test will pass if it receives a change event and the Realm changed.
realm.addListener('change', () => {
try {
TestCase.assertEqual(objects.length, 1);
TestCase.assertEqual(objects[0].doubleCol, 42);
resolve();
} catch (e) {
reject(e);
} finally {
cleanup();
}
});
worker.onmessage = (message) => {
if (message.error) {
cleanup();
reject(message.error);
}
};
worker.postMessage({
action: 'create',
config: config,
type: 'TestObject',
properties: {
doubleCol: 42,
}
});
});
}
};

View File

@ -31,7 +31,9 @@ class Worker {
});
}
postMessage(message) {
this._process.send(message);
if (this._process) {
this._process.send(message);
}
}
terminate() {
if (this._process) {

16
tests/package.json Normal file
View File

@ -0,0 +1,16 @@
{
"name": "realm-tests-jasmine",
"version": "0.0.1",
"private": true,
"dependencies": {
"es6-promise": "^3.2.1",
"jasmine": "^2.5.1",
"jasmine-console-reporter": "^1.2.7",
"jasmine-reporters": "^2.2.0"
},
"scripts": {
"test": "jasmine spec/unit_tests.js",
"test-sync": "jasmine spec/sync_integration_tests.js",
"postinstall": "rm -f node_modules/realm && ln -s ../.. node_modules/realm"
}
}

View File

@ -28,6 +28,36 @@ import {
import React from 'react';
import { runTests } from './tests';
const Realm = require('realm');
Realm.Sync.setLogLevel('error');
var realm;
Realm.Sync.User.login('http://127.0.0.1:9080/', 'ari', 'aaa', function(error, user) {
console.log(user);
realm = new Realm({
sync: {
user: user,
url: 'realm://127.0.0.1:9080/~/demo/realm1'
},
schema: [{
name: 'IntObject',
properties: {
int: 'int'
}
}]
});
realm.addListener('change', () => {
console.log(realm.objects('IntObject'));
});
realm.write(() => {
realm.create('IntObject', {int: realm.objects('IntObject').length});
});
});
class ReactTests extends React.Component {
render() {
return (

View File

@ -91,6 +91,13 @@
ReferencedContainer = "container:ReactTests.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "OS_ACTIVITY_MODE"
value = "disable"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>

View File

@ -6,11 +6,11 @@
"start": "react-native start"
},
"dependencies": {
"react": "~15.2.0",
"react-native": "^0.31.0",
"react": "~15.3.1",
"react-native": "^0.34.0",
"react-native-fs": "^1.1.0",
"xmlbuilder": "^4.2.1",
"realm": "file:../..",
"realm-tests": "file:../js"
"realm-tests": "file:../js",
"xmlbuilder": "^4.2.1"
}
}

View File

@ -0,0 +1,9 @@
var jasmineReporters = require('jasmine-reporters');
var junitReporter = new jasmineReporters.JUnitXmlReporter({
savePath: '.',
consolidateAll: false
});
jasmine.getEnv().addReporter(junitReporter);
var JasmineConsoleReporter = require('jasmine-console-reporter');
jasmine.getEnv().addReporter(new JasmineConsoleReporter());

View File

@ -0,0 +1,11 @@
{
"spec_dir": "spec",
"spec_files": [
"**/*[sS]pec.js"
],
"helpers": [
"helpers/**/*.js"
],
"stopSpecOnExpectationFailure": false,
"random": false
}

View File

@ -0,0 +1,99 @@
"use strict";
const spawn = require("child_process").spawn;
const readline = require("readline");
const fs = require("fs");
const Realm = require("realm");
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
beforeEach(function(done) {
this.objectServer = spawn("sync-bundle/start-object-server.command");
this.objectServer.once("close", (code) => {
if (typeof code === "number" && code != 0) {
console.error(`Object Server exited with code ${code}`);
process.exit(-1);
}
});
this.rl = readline.createInterface({ input: this.objectServer.stdout });
this.rl.on("line", (line) => {
var match;
if ((match = line.match(/Connection\[1\]: Session\[1\]: Received: BIND\(server_path='\/(.+)',/))) {
var adminUser = Realm.Sync.User.adminUser('http://127.0.0.1:9080/',
fs.readFileSync("sync-bundle/admin_token.base64", "utf-8"));
this.adminRealmPath = match[1];
this.adminRealm = new Realm({
path: "__admin.realm",
sync: {
user: adminUser,
url: `realm://127.0.0.1:9080/${this.adminRealmPath}`
},
schema: [
{
name: "RealmFile",
properties: {
id: 'string',
path: 'string'
}
}
]
});
done();
}
});
});
afterEach(function(done) {
this.rl.close();
this.objectServer.kill('SIGKILL');
this.adminRealm.close();
let reset = spawn("sync-bundle/reset-server-realms.command");
reset.once("close", done);
reset.stdin.write("yes\n");
Realm.clearTestState();
});
describe("Sync", function() {
it("should work", function(done) {
Realm.Sync.User.create('http://127.0.0.1:9080/', 'foo', 'bar', function(error) {
if (error) {
fail(error);
return;
}
Realm.Sync.User.login('http://127.0.0.1:9080/', 'foo', 'bar', function(error, user) {
if (error) {
fail(error);
return;
}
var realm = new Realm({
syncConfig: {
identity: user.identity,
url: 'realm://127.0.0.1:9080/~/demo/realm1'
},
schema: [
{
name: 'IntObject',
properties: {
int: 'int'
}
}
]
});
});
});
var realms = this.adminRealm.objects("RealmFile");
realms.addListener((sender, changeset) => {
if (changeset.insertions.length === 1) {
expect(realms[changeset.insertions[0]].path).toMatch(/demo\/realm1$/);
done();
}
});
});
});

79
tests/spec/unit_tests.js Normal file
View File

@ -0,0 +1,79 @@
////////////////////////////////////////////////////////////////////////////
//
// 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 fs = require('fs');
const path = require('path');
const Realm = require('../..');
const RealmTests = require('../js');
// Create this method with appropriate implementation for Node testing.
Realm.copyBundledRealmFiles = function() {
let sourceDir = path.join(__dirname, '../data');
let destinationDir = path.dirname(Realm.defaultPath);
for (let filename of fs.readdirSync(sourceDir)) {
let src = path.join(sourceDir, filename);
let dest = path.join(destinationDir, filename);
// If the destination file already exists, then don't overwrite it.
try {
fs.accessSync(dest);
continue;
} catch (e) {}
fs.writeFileSync(dest, fs.readFileSync(src));
}
};
const tests = RealmTests.getTestNames();
for (const suiteName in tests) {
describe(suiteName, () => {
beforeEach(() => RealmTests.runTest(suiteName, 'beforeEach'));
for (const testName of tests[suiteName]) {
it(testName, () => {
try {
RealmTests.runTest(suiteName, testName)
}
catch (e) {
fail(e);
}
});
}
afterEach(() => RealmTests.runTest(suiteName, 'afterEach'));
});
}
const asyncTests = require('../js/async-tests');
describe('AsyncTests', () => {
beforeEach(() => Realm.clearTestState());
for (const testName in asyncTests) {
it(testName, (done) => asyncTests[testName]().catch((e) => fail(e)).then(done));
}
afterEach(() => Realm.clearTestState());
});

1
vendor/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/realm/