From ad99a988e20ac7458b9e4be13d1c512ad8bdd89a Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Wed, 31 Jan 2018 10:17:32 +0100 Subject: [PATCH 01/61] Updating to sync 3.0.0-alpha.1 --- CHANGELOG.md | 17 +++++++++++++++++ dependencies.list | 2 +- src/object-store | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d741d836..df0f0fc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +2.3.0-alpha.1 Release notes +============================================================= +### Breaking changes +* Sync protocol changed to version 24. +* History schema format for server-side Realm files bumped to version 4. This means that after the server has been upgraded, it cannot be downgraded again without restoring state from backup. +* Backup protocol version bumped to 2. No compatibility with earlier versions of the backup protocol is provided. + +### Enhancements +* Reduced initial download times in Realms with long transaction histories. + +### Bug fixes +* None. + +### Internal +* Updated to Realm Sync 3.0.0-alpha.1. + + 2.2.6 Release notes (2018-1-26) ============================================================= ### Breaking changes diff --git a/dependencies.list b/dependencies.list index 32a36b46..95275f7e 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js VERSION=2.2.6 REALM_CORE_VERSION=5.1.2 -REALM_SYNC_VERSION=2.2.9 +REALM_SYNC_VERSION=3.0.0-alpha.1 REALM_OBJECT_SERVER_VERSION=2.5.1 diff --git a/src/object-store b/src/object-store index 9aab0ffd..436c1112 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit 9aab0ffd5bc7bfc438ec28375ba581cf732f57ee +Subproject commit 436c1112337ee68fc65655c2a253997c5dcf458f From 396cb7ab74918e5567ee97688db97cc3f9022f75 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Wed, 31 Jan 2018 14:59:40 +0100 Subject: [PATCH 02/61] [2.3.0-alpha.1] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 2 +- package.json | 2 +- src/RealmJS.xcodeproj/project.pbxproj | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df0f0fc5..8ac8f476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0-alpha.1 Release notes +2.3.0-alpha.1 Release notes (2018-1-31) ============================================================= ### Breaking changes * Sync protocol changed to version 24. diff --git a/dependencies.list b/dependencies.list index 95275f7e..e8619854 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.2.6 +VERSION=2.3.0-alpha.1 REALM_CORE_VERSION=5.1.2 REALM_SYNC_VERSION=3.0.0-alpha.1 REALM_OBJECT_SERVER_VERSION=2.5.1 diff --git a/package.json b/package.json index ebd70bfe..3f502594 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.2.6", + "version": "2.3.0-alpha.1", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ diff --git a/src/RealmJS.xcodeproj/project.pbxproj b/src/RealmJS.xcodeproj/project.pbxproj index 6ebb6f85..9ec1b86c 100644 --- a/src/RealmJS.xcodeproj/project.pbxproj +++ b/src/RealmJS.xcodeproj/project.pbxproj @@ -961,7 +961,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2.2.6; + CURRENT_PROJECT_VERSION = 2.3.0; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1025,7 +1025,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2.2.6; + CURRENT_PROJECT_VERSION = 2.3.0; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; From 435b2e5fbe802255591fbcc7fba83217c605b4f7 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Wed, 31 Jan 2018 18:53:00 +0100 Subject: [PATCH 03/61] [2.3.0-alpha.2] Bump version --- CHANGELOG.md | 4 ++-- dependencies.list | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ac8f476..2b12485f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0-alpha.1 Release notes (2018-1-31) +2.3.0 Release notes (2018-1-31) ============================================================= ### Breaking changes * Sync protocol changed to version 24. @@ -12,7 +12,7 @@ * None. ### Internal -* Updated to Realm Sync 3.0.0-alpha.1. +* Updated to Realm Sync 3.0.0-alpha.2. 2.2.6 Release notes (2018-1-26) diff --git a/dependencies.list b/dependencies.list index e8619854..c9eab895 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js VERSION=2.3.0-alpha.1 REALM_CORE_VERSION=5.1.2 -REALM_SYNC_VERSION=3.0.0-alpha.1 +REALM_SYNC_VERSION=3.0.0-alpha.2 REALM_OBJECT_SERVER_VERSION=2.5.1 From e075269770a35ff6c7aecf109ee2edc6979606f1 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Wed, 31 Jan 2018 18:54:40 +0100 Subject: [PATCH 04/61] [2.3.0-alpha.2] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b12485f..9d4cfce8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-1-31) +2.3.0-alpha.2 Release notes (2018-1-31) ============================================================= ### Breaking changes * Sync protocol changed to version 24. diff --git a/dependencies.list b/dependencies.list index c9eab895..4e5c4ded 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.1 +VERSION=2.3.0-alpha.2 REALM_CORE_VERSION=5.1.2 REALM_SYNC_VERSION=3.0.0-alpha.2 REALM_OBJECT_SERVER_VERSION=2.5.1 diff --git a/package.json b/package.json index 3f502594..ae0ec5b0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.1", + "version": "2.3.0-alpha.2", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From dad7fa403df435422b90556b934fb8e8c9ccc2e6 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 1 Feb 2018 15:37:10 +0100 Subject: [PATCH 05/61] [2.3.0-alpha.3] Bump version --- CHANGELOG.md | 3 ++- dependencies.list | 2 +- package.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d4cfce8..0a2a9607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0-alpha.2 Release notes (2018-1-31) +2.3.0-alpha.3 Release notes (2018-2-1) ============================================================= ### Breaking changes * Sync protocol changed to version 24. @@ -7,6 +7,7 @@ ### Enhancements * Reduced initial download times in Realms with long transaction histories. +* Wait for pending notifications to complete when removing a sync listener (1648). ### Bug fixes * None. diff --git a/dependencies.list b/dependencies.list index 4e5c4ded..c1ba740c 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.2 +VERSION=2.3.0-alpha.3 REALM_CORE_VERSION=5.1.2 REALM_SYNC_VERSION=3.0.0-alpha.2 REALM_OBJECT_SERVER_VERSION=2.5.1 diff --git a/package.json b/package.json index ae0ec5b0..a273f52b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.2", + "version": "2.3.0-alpha.3", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From 6f12589fcea169af4db93798e9ee904bb3246332 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 2 Feb 2018 15:44:31 +0100 Subject: [PATCH 06/61] Testing against ROS 3.0.0-alpha.1 --- CHANGELOG.md | 2 +- dependencies.list | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a2a9607..6e1a7a98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ ### Internal * Updated to Realm Sync 3.0.0-alpha.2. - +* Tested against Realm Object Server 3.0.0-alpha.1. 2.2.6 Release notes (2018-1-26) ============================================================= diff --git a/dependencies.list b/dependencies.list index c1ba740c..5471412d 100644 --- a/dependencies.list +++ b/dependencies.list @@ -2,4 +2,4 @@ PACKAGE_NAME=realm-js VERSION=2.3.0-alpha.3 REALM_CORE_VERSION=5.1.2 REALM_SYNC_VERSION=3.0.0-alpha.2 -REALM_OBJECT_SERVER_VERSION=2.5.1 +REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.1 From 1529717577d1b73c3b0a39eaf5d5543e693696a3 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 5 Feb 2018 10:21:30 +0100 Subject: [PATCH 07/61] Disable partial sync test --- tests/js/session-tests.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index a127937c..90fc45ed 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -707,6 +707,7 @@ module.exports = { }); }, + /* Disabled: waiting for new implementation testPartialSync() { // FIXME: try to enable for React Native if (!isNodeProccess) { @@ -739,7 +740,7 @@ module.exports = { }) }) }, - + */ testClientReset() { // FIXME: try to enable for React Native if (!isNodeProccess) { From ab02e260927abb2f418cf6bd3b227f91fe24d461 Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Mon, 5 Feb 2018 18:52:40 +0100 Subject: [PATCH 08/61] bump dependency versions --- dependencies.list | 4 ++-- src/js_results.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dependencies.list b/dependencies.list index 5471412d..9090cbfd 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js VERSION=2.3.0-alpha.3 -REALM_CORE_VERSION=5.1.2 -REALM_SYNC_VERSION=3.0.0-alpha.2 +REALM_CORE_VERSION=5.2.0 +REALM_SYNC_VERSION=3.0.0-alpha.3 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.1 diff --git a/src/js_results.hpp b/src/js_results.hpp index f179be3f..5f6a30d3 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -153,10 +153,10 @@ typename T::Object ResultsClass::create_filtered(ContextType ctx, const U &co auto const &realm = collection.get_realm(); auto const &object_schema = collection.get_object_schema(); - parser::Predicate predicate = parser::parse(query_string); + parser::ParserResult result = parser::parse(query_string); NativeAccessor accessor(ctx, realm, object_schema); query_builder::ArgumentConverter> converter(accessor, &args.value[1], args.count - 1); - query_builder::apply_predicate(query, predicate, converter); + query_builder::apply_predicate(query, result.predicate, converter); return create_instance(ctx, collection.filter(std::move(query))); } From 74fc625eca91b79d95a618182261e539318df6b0 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 5 Feb 2018 19:51:39 +0100 Subject: [PATCH 09/61] Fix test --- tests/js/query-tests.json | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/tests/js/query-tests.json b/tests/js/query-tests.json index b55bb297..db9636cc 100644 --- a/tests/js/query-tests.json +++ b/tests/js/query-tests.json @@ -1,7 +1,7 @@ { "dateTests" : { - "schema" : [{ + "schema" : [{ "name": "DateObject", "properties": [{ "name": "date", "type": "date" }] }], @@ -25,7 +25,7 @@ }, "boolTests" : { - "schema" : [{ + "schema" : [{ "name": "BoolObject", "properties": [{ "name": "boolCol", "type": "bool" }] }], @@ -57,11 +57,11 @@ ["QueryThrows", "BoolObject", "boolCol BEGINSWITH true"], ["QueryThrows", "BoolObject", "boolCol CONTAINS true"], ["QueryThrows", "BoolObject", "boolCol ENDSWITH true"] - ] + ] }, "intTests" : { - "schema" : [{ + "schema" : [{ "name": "IntObject", "properties": [{ "name": "intCol", "type": "int" }] }], @@ -92,7 +92,7 @@ }, "floatTests" : { - "schema" : [{ + "schema" : [{ "name": "FloatObject", "properties": [{ "name": "floatCol", "type": "float" }] }], @@ -126,7 +126,7 @@ }, "doubleTests" : { - "schema" : [{ + "schema" : [{ "name": "DoubleObject", "properties": [{ "name": "doubleCol", "type": "double" }] }], @@ -157,7 +157,7 @@ }, "stringTests" : { - "schema" : [{ + "schema" : [{ "name": "StringObject", "properties": [{ "name": "stringCol", "type": "string" }] }], @@ -205,7 +205,7 @@ }, "binaryTests" : { - "schema" : [{ + "schema" : [{ "name": "BinaryObject", "properties": [{ "name": "binaryCol", "type": "data" }] }], @@ -256,14 +256,14 @@ "compoundTests" : { "schema" : [ - { "name": "IntObject", + { "name": "IntObject", "properties": [{ "name": "intCol", "type": "int" }], "primaryKey" : "intCol" } ], "objects": [ { "type": "IntObject", "value": [0] }, { "type": "IntObject", "value": [1] }, - { "type": "IntObject", "value": [2] }, + { "type": "IntObject", "value": [2] }, { "type": "IntObject", "value": [3] } ], "tests": [ @@ -281,14 +281,14 @@ ["ObjectSet", [0, 1, 2],"IntObject", "intCol == 0 || intCol == 1 || intCol <= 2"], ["ObjectSet", [0, 1], "IntObject", "intCol == 1 && intCol >= 1 || intCol == 0"], ["ObjectSet", [0, 1], "IntObject", "intCol == 1 || intCol == 0 && intCol <= 0 && intCol >= 0"], - ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || NOT (intCol == 3 && intCol >= 0) && intCol == 1"] + ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || NOT (intCol == 3 && intCol >= 0) && intCol == 1"] ] }, "keyPathTests" : { "schema" : [ - { - "name": "BasicTypesObject", + { + "name": "BasicTypesObject", "properties": [ { "name": "intCol", "type": "int" }, { "name": "floatCol", "type": "float" }, @@ -296,7 +296,7 @@ { "name": "stringCol", "type": "string" }, { "name": "dateCol", "type": "date?" } ] - }, + }, { "name": "LinkTypesObject", "primaryKey": "primaryKey", @@ -326,8 +326,8 @@ "optionalTests" : { "schema" : [ - { - "name": "OptionalTypesObject", + { + "name": "OptionalTypesObject", "primaryKey": "primaryKey", "properties": [ { "name": "primaryKey", "type": "int" }, @@ -338,7 +338,7 @@ { "name": "dateCol", "type": "date", "optional": true }, { "name": "dataCol", "type": "data", "optional": true } ] - }, + }, { "name": "LinkTypesObject", "primaryKey": "primaryKey", @@ -375,8 +375,7 @@ ["ObjectSet", [1], "LinkTypesObject", "basicLink.stringCol == null"], ["ObjectSet", [0], "LinkTypesObject", "basicLink.stringCol != null"], ["ObjectSet", [1], "LinkTypesObject", "basicLink.dateCol == null"], - ["ObjectSet", [0], "LinkTypesObject", "basicLink.dateCol != null"], - ["QueryThrows", "LinkTypesObject", "basicLink.dataCol == null"] + ["ObjectSet", [0], "LinkTypesObject", "basicLink.dateCol != null"] ] } From 7cf1298709388966cf9ff5e5f6e7b9f1dd876887 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 5 Feb 2018 19:56:48 +0100 Subject: [PATCH 10/61] [2.3.0-alpha.4] Bump version --- CHANGELOG.md | 6 ++++-- dependencies.list | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e1a7a98..fb605a70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0-alpha.3 Release notes (2018-2-1) +2.3.0 Release notes (2018-2-5) ============================================================= ### Breaking changes * Sync protocol changed to version 24. @@ -8,12 +8,14 @@ ### Enhancements * Reduced initial download times in Realms with long transaction histories. * Wait for pending notifications to complete when removing a sync listener (1648). +* Further enhancements of the query parser. ### Bug fixes * None. ### Internal -* Updated to Realm Sync 3.0.0-alpha.2. +* Updated to Realm Core 5.2.0. +* Updated to Realm Sync 3.0.0-alpha.3. * Tested against Realm Object Server 3.0.0-alpha.1. 2.2.6 Release notes (2018-1-26) diff --git a/dependencies.list b/dependencies.list index 9090cbfd..40de548e 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.3 +VERSION=2.3.0-alpha.4 REALM_CORE_VERSION=5.2.0 REALM_SYNC_VERSION=3.0.0-alpha.3 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.1 diff --git a/package.json b/package.json index a273f52b..da8c6988 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.3", + "version": "2.3.0-alpha.4", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From 33eec8b0ef3daa9dc7222f9811fbac7cb5e2be14 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 6 Feb 2018 12:25:20 +0100 Subject: [PATCH 11/61] [2.3.0-alpha.5] Bump version --- CHANGELOG.md | 4 ++-- dependencies.list | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb605a70..02ebad3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-2-5) +2.3.0 Release notes (2018-2-6) ============================================================= ### Breaking changes * Sync protocol changed to version 24. @@ -15,7 +15,7 @@ ### Internal * Updated to Realm Core 5.2.0. -* Updated to Realm Sync 3.0.0-alpha.3. +* Updated to Realm Sync 3.0.0-alpha.4. * Tested against Realm Object Server 3.0.0-alpha.1. 2.2.6 Release notes (2018-1-26) diff --git a/dependencies.list b/dependencies.list index 40de548e..ec1fbcfd 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.4 +VERSION=2.3.0-alpha.5 REALM_CORE_VERSION=5.2.0 -REALM_SYNC_VERSION=3.0.0-alpha.3 +REALM_SYNC_VERSION=3.0.0-alpha.4 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.1 diff --git a/package.json b/package.json index da8c6988..2fb8813f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.4", + "version": "2.3.0-alpha.5", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From 5dd8fdc08d27dcb589a1221bd0561e5b0aefc755 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Wed, 7 Feb 2018 14:09:34 +0100 Subject: [PATCH 12/61] [2.3.0-alpha.6] Bump version --- CHANGELOG.md | 6 +++--- dependencies.list | 4 ++-- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02ebad3d..35d4261e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-2-6) +2.3.0 Release notes (2018-2-7) ============================================================= ### Breaking changes * Sync protocol changed to version 24. @@ -15,8 +15,8 @@ ### Internal * Updated to Realm Core 5.2.0. -* Updated to Realm Sync 3.0.0-alpha.4. -* Tested against Realm Object Server 3.0.0-alpha.1. +* Updated to Realm Sync 3.0.0-beta.1. +* Tested against Realm Object Server 3.0.0-alpha.2. 2.2.6 Release notes (2018-1-26) ============================================================= diff --git a/dependencies.list b/dependencies.list index ec1fbcfd..921c2cb6 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js VERSION=2.3.0-alpha.5 REALM_CORE_VERSION=5.2.0 -REALM_SYNC_VERSION=3.0.0-alpha.4 -REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.1 +REALM_SYNC_VERSION=3.0.0-beta.1 +REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.2 diff --git a/package.json b/package.json index 2fb8813f..4164af56 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.5", + "version": "2.3.0-alpha.6", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From b78a8a60d0579a0af1c350504e02f3a66e59f025 Mon Sep 17 00:00:00 2001 From: James Stone Date: Wed, 7 Feb 2018 07:11:43 -0800 Subject: [PATCH 13/61] Hook up the sort/distinct orderings from parsed queries (#1653) * Hook up the sort/distinct orderings from parsed queries * Update changelog, better tests, fix syntax --- CHANGELOG.md | 7 ++++++- src/js_results.hpp | 4 +++- src/object-store | 2 +- tests/js/query-tests.js | 3 +++ tests/js/query-tests.json | 29 ++++++++++++++++++++++++++++- 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35d4261e..0a9d425b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,12 @@ ### Enhancements * Reduced initial download times in Realms with long transaction histories. * Wait for pending notifications to complete when removing a sync listener (1648). -* Further enhancements of the query parser. +* Enabled sort and distinct in the query string. If sort or distinct are also applied outside of the query string, the conditions are stacked. + - Example syntax: `age > 20 SORT(name ASC, age DESC) DISTINCT(name)` + - The ordering for sorting can be one of the following case insensitive literals: `ASC`, `ASCENDING`, `DESC`, `DESCENDING`. + - Any number of properties can appear inside the brackets in a comma separated list. + - Any number of sort/distinct conditions can be indicated, they will be applied in the specified order. + - Sort or distinct cannot operate independently, these conditions must be attached to at least one query filter. ### Bug fixes * None. diff --git a/src/js_results.hpp b/src/js_results.hpp index 5f6a30d3..f3694d08 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -152,13 +152,15 @@ typename T::Object ResultsClass::create_filtered(ContextType ctx, const U &co auto query = collection.get_query(); auto const &realm = collection.get_realm(); auto const &object_schema = collection.get_object_schema(); + DescriptorOrdering ordering; parser::ParserResult result = parser::parse(query_string); NativeAccessor accessor(ctx, realm, object_schema); query_builder::ArgumentConverter> converter(accessor, &args.value[1], args.count - 1); query_builder::apply_predicate(query, result.predicate, converter); + query_builder::apply_ordering(ordering, query.get_table(), result.ordering); - return create_instance(ctx, collection.filter(std::move(query))); + return create_instance(ctx, collection.filter(std::move(query)).apply_ordering(std::move(ordering))); } template diff --git a/src/object-store b/src/object-store index 436c1112..dbbd3d92 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit 436c1112337ee68fc65655c2a253997c5dcf458f +Subproject commit dbbd3d9212e608359fc8297fb9c29433e9a13724 diff --git a/tests/js/query-tests.js b/tests/js/query-tests.js index dcc1a1ef..9ccd43fc 100644 --- a/tests/js/query-tests.js +++ b/tests/js/query-tests.js @@ -154,5 +154,8 @@ module.exports = { }, testOptionalQueries: function() { runQuerySuite(testCases.optionalTests); + }, + testOrderingQueries: function() { + runQuerySuite(testCases.orderingTests); } }; diff --git a/tests/js/query-tests.json b/tests/js/query-tests.json index db9636cc..678e0f5b 100644 --- a/tests/js/query-tests.json +++ b/tests/js/query-tests.json @@ -377,6 +377,33 @@ ["ObjectSet", [1], "LinkTypesObject", "basicLink.dateCol == null"], ["ObjectSet", [0], "LinkTypesObject", "basicLink.dateCol != null"] ] -} +}, +"orderingTests" : { + "schema" : [ + { "name": "Person", + "properties": [ + { "name": "id", "type": "int"}, + { "name": "name", "type": "string" }, + { "name": "age", "type": "int"} + ], + "primaryKey" : "id" } + ], + "objects": [ + { "type": "Person", "value": [0, "John", 28] }, + { "type": "Person", "value": [1, "John", 37] }, + { "type": "Person", "value": [2, "Jake", 27] }, + { "type": "Person", "value": [3, "Jake", 32] }, + { "type": "Person", "value": [4, "Jake", 32] }, + { "type": "Person", "value": [5, "Johnny", 19] } + ], + "tests": [ + ["ObjectSet", [1, 3], "Person", "age > 20 SORT(age DESC) DISTINCT(name)"], + ["ObjectSet", [2, 0], "Person", "age > 20 SORT(age ASC) DISTINCT(name)"], + ["ObjectSet", [2, 0], "Person", "age > 20 SORT(age ASC, name DESC) DISTINCT(name)"], + ["ObjectSet", [2, 0], "Person", "age > 20 SORT(name DESC) SORT(age ASC) DISTINCT(name)"], + ["ObjectSet", [2, 0, 3, 1], "Person", "age > 20 SORT(age ASC, name DESC) DISTINCT(name, age)"], + ["ObjectSet", [0, 2], "Person", "age > 20 SORT(age ASC) DISTINCT(age) SORT(name DESC) DISTINCT(name)"] + ] +} } From 3b8ce20c18ce27b2857ca020891851560cd057f0 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 9 Feb 2018 11:01:40 +0100 Subject: [PATCH 14/61] [2.3.0-alpha.7] Bump version --- CHANGELOG.md | 4 ++-- dependencies.list | 4 ++-- package.json | 2 +- src/object-store | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a9d425b..64ca6902 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-2-7) +2.3.0 Release notes (2018-2-9) ============================================================= ### Breaking changes * Sync protocol changed to version 24. @@ -20,7 +20,7 @@ ### Internal * Updated to Realm Core 5.2.0. -* Updated to Realm Sync 3.0.0-beta.1. +* Updated to Realm Sync 3.0.0-beta.2. * Tested against Realm Object Server 3.0.0-alpha.2. 2.2.6 Release notes (2018-1-26) diff --git a/dependencies.list b/dependencies.list index 921c2cb6..c3585d62 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.5 +VERSION=2.3.0-alpha.7 REALM_CORE_VERSION=5.2.0 -REALM_SYNC_VERSION=3.0.0-beta.1 +REALM_SYNC_VERSION=3.0.0-beta.2 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.2 diff --git a/package.json b/package.json index 4164af56..ba3f853c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.6", + "version": "2.3.0-alpha.7", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ diff --git a/src/object-store b/src/object-store index dbbd3d92..56f24683 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit dbbd3d9212e608359fc8297fb9c29433e9a13724 +Subproject commit 56f246831c6bd1bd33cf0f852d049b11e94e50e9 From 95fc7f2234ebd9a257dce872cde6a8dc3a7a4713 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 13 Feb 2018 08:56:03 +0100 Subject: [PATCH 15/61] [2.3.0-alpha.8] Bump version --- CHANGELOG.md | 4 ++-- dependencies.list | 2 +- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64ca6902..e2338fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-2-9) +2.3.0 Release notes (2018-2-13) ============================================================= ### Breaking changes * Sync protocol changed to version 24. @@ -20,7 +20,7 @@ ### Internal * Updated to Realm Core 5.2.0. -* Updated to Realm Sync 3.0.0-beta.2. +* Updated to Realm Sync 3.0.0-beta.3. * Tested against Realm Object Server 3.0.0-alpha.2. 2.2.6 Release notes (2018-1-26) diff --git a/dependencies.list b/dependencies.list index c3585d62..65a3062c 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js VERSION=2.3.0-alpha.7 REALM_CORE_VERSION=5.2.0 -REALM_SYNC_VERSION=3.0.0-beta.2 +REALM_SYNC_VERSION=3.0.0-beta.3 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.2 diff --git a/package.json b/package.json index ba3f853c..d6c8bc25 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.7", + "version": "2.3.0-alpha.8", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From 6f433246360066e53b30f4cbc9875de32a28a4f1 Mon Sep 17 00:00:00 2001 From: James Stone Date: Tue, 13 Feb 2018 16:17:27 -0800 Subject: [PATCH 16/61] Preliminary work for queries over named backlinks --- src/js_results.hpp | 21 ++++++++++++++++++++- tests/js/linkingobjects-tests.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/js_results.hpp b/src/js_results.hpp index f3694d08..9a7b380b 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -141,6 +141,23 @@ typename T::Object ResultsClass::create_instance(ContextType ctx, SharedRealm return create_object>(ctx, new realm::js::Results(realm, *table)); } +void alias_backlinks(parser::KeypathMapping &mapping, const realm::SharedRealm &realm) +{ + const realm::Schema &schema = realm->schema(); + for (auto it = schema.begin(); it != schema.end(); ++it) { + for (const Property &property : it->computed_properties) { + if (property.type == realm::PropertyType::LinkingObjects) { + auto target_object_schema = schema.find(property.object_type); + auto link_property = target_object_schema->property_for_name(property.link_origin_property_name); + const TableRef table = ObjectStore::table_for_object_type(realm->read_group(), target_object_schema->name); + std::string native_name = "@links." + std::string(table->get_name()) + "." + property.link_origin_property_name; + //std::cout << "mapping named backlink: " << property.name << " to: " << native_name << std::endl; + mapping.add_mapping(table, property.name, native_backlink_name); + } + } + } +} + template template typename T::Object ResultsClass::create_filtered(ContextType ctx, const U &collection, Arguments args) { @@ -153,11 +170,13 @@ typename T::Object ResultsClass::create_filtered(ContextType ctx, const U &co auto const &realm = collection.get_realm(); auto const &object_schema = collection.get_object_schema(); DescriptorOrdering ordering; + parser::KeypathMapping mapping; + alias_backlinks(mapping, realm); parser::ParserResult result = parser::parse(query_string); NativeAccessor accessor(ctx, realm, object_schema); query_builder::ArgumentConverter> converter(accessor, &args.value[1], args.count - 1); - query_builder::apply_predicate(query, result.predicate, converter); + query_builder::apply_predicate(query, result.predicate, converter, mapping); query_builder::apply_ordering(ordering, query.get_table(), result.ordering); return create_instance(ctx, collection.filter(std::move(query)).apply_ordering(std::move(ordering))); diff --git a/tests/js/linkingobjects-tests.js b/tests/js/linkingobjects-tests.js index fb07a0ff..943a6aac 100644 --- a/tests/js/linkingobjects-tests.js +++ b/tests/js/linkingobjects-tests.js @@ -124,4 +124,36 @@ module.exports = { TestCase.assertEqual(oliviersParents.length, 0); }); }, + + testFilterByLinkingObject: function() { + const Realm = require('realm') + + const ProductSchema = { + name: 'Product', + primaryKey:'productId', + properties: { + productId:'int', + product:'string', + combinations: {type: 'linkingObjects', objectType: 'Combination', property: 'products'} + } + } + + const CombinationSchema = { + name: 'Combination', + properties: { + onSale: {type: 'string'}, + products: {type: 'list', objectType:'Product'} + } + } + + Realm.open({ + schema: [ProductSchema, CombinationSchema] + }).then(function (realm) { + let queryFilter1 = 'onSale = "yes" AND products.productId = 1' + let data1 = realm.objects('Combination').filtered(queryFilter1) + + let queryFilter2 = 'combinations.onSale = "yes" AND productId = 1' + let data2 = realm.objects('Product').filtered(queryFilter2) + }) + }, }; From 8faac5561f8f29d6d2e26c435fa8f04d1c7e677c Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Wed, 14 Feb 2018 08:58:24 +0100 Subject: [PATCH 17/61] [2.3.0-alpha.9] Bump version --- CHANGELOG.md | 4 ++-- dependencies.list | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eda5ae5b..6b72c66e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-2-13) +2.3.0 Release notes (2018-2-14) ============================================================= ### Breaking changes * Sync protocol changed to version 24. @@ -17,7 +17,7 @@ ### Internal * Updated to Realm Core 5.2.0. -* Updated to Realm Sync 3.0.0-beta.3. +* Updated to Realm Sync 3.0.0-beta.4. * Tested against Realm Object Server 3.0.0-alpha.2. 2.2.8 Release notes (2018-2-13) diff --git a/dependencies.list b/dependencies.list index 65a3062c..108d027b 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.7 +VERSION=2.3.0-alpha.9 REALM_CORE_VERSION=5.2.0 -REALM_SYNC_VERSION=3.0.0-beta.3 +REALM_SYNC_VERSION=3.0.0-beta.4 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.2 diff --git a/package.json b/package.json index d6c8bc25..387ec843 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.8", + "version": "2.3.0-alpha.9", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From e42eb6469aaf71a48fe1b0d9b0d614d00c4fa5c4 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 15 Feb 2018 16:42:16 +0100 Subject: [PATCH 18/61] [2.3.0-alpha.10] Bump version --- CHANGELOG.md | 6 ++---- dependencies.list | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b72c66e..04de504d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-2-14) +2.3.0 Release notes (2018-2-15) ============================================================= ### Breaking changes * Sync protocol changed to version 24. @@ -17,7 +17,7 @@ ### Internal * Updated to Realm Core 5.2.0. -* Updated to Realm Sync 3.0.0-beta.4. +* Updated to Realm Sync 3.0.0-beta.5. * Tested against Realm Object Server 3.0.0-alpha.2. 2.2.8 Release notes (2018-2-13) @@ -50,8 +50,6 @@ ### Internal * None. ->>>>>>> 1ad557b47fa03b4085b8fb5c682598a36d79b966 - 2.2.6 Release notes (2018-1-26) ============================================================= ### Breaking changes diff --git a/dependencies.list b/dependencies.list index 108d027b..60877b13 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.9 +VERSION=2.3.0-alpha.10 REALM_CORE_VERSION=5.2.0 -REALM_SYNC_VERSION=3.0.0-beta.4 +REALM_SYNC_VERSION=3.0.0-beta.5 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.2 diff --git a/package.json b/package.json index 387ec843..47a116b3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.9", + "version": "2.3.0-alpha.10", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From 9c07407d50a07b2b8976c2f8824848f08a8f22a7 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 19 Feb 2018 17:45:34 +0000 Subject: [PATCH 19/61] [2.3.0-alpha.11] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04de504d..08f84e74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-2-15) +2.3.0 Release notes (2018-2-19) ============================================================= ### Breaking changes * Sync protocol changed to version 24. diff --git a/dependencies.list b/dependencies.list index 60877b13..f68a8de1 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.10 +VERSION=2.3.0-alpha.11 REALM_CORE_VERSION=5.2.0 -REALM_SYNC_VERSION=3.0.0-beta.5 +REALM_SYNC_VERSION=3.0.0-beta.6 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.2 diff --git a/package.json b/package.json index 47a116b3..714e7346 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.10", + "version": "2.3.0-alpha.11", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From bd5c237b0f5bbaab1496171919bce1d6c17bf46b Mon Sep 17 00:00:00 2001 From: James Stone Date: Mon, 19 Feb 2018 16:14:10 -0800 Subject: [PATCH 20/61] Named backlink queries working with core 5.3.0 --- src/js_results.hpp | 8 ++--- tests/js/linkingobjects-tests.js | 52 ++++++++++++-------------------- 2 files changed, 23 insertions(+), 37 deletions(-) diff --git a/src/js_results.hpp b/src/js_results.hpp index 9a7b380b..f825692f 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -141,18 +141,16 @@ typename T::Object ResultsClass::create_instance(ContextType ctx, SharedRealm return create_object>(ctx, new realm::js::Results(realm, *table)); } -void alias_backlinks(parser::KeypathMapping &mapping, const realm::SharedRealm &realm) +void alias_backlinks(parser::KeyPathMapping &mapping, const realm::SharedRealm &realm) { const realm::Schema &schema = realm->schema(); for (auto it = schema.begin(); it != schema.end(); ++it) { for (const Property &property : it->computed_properties) { if (property.type == realm::PropertyType::LinkingObjects) { auto target_object_schema = schema.find(property.object_type); - auto link_property = target_object_schema->property_for_name(property.link_origin_property_name); const TableRef table = ObjectStore::table_for_object_type(realm->read_group(), target_object_schema->name); std::string native_name = "@links." + std::string(table->get_name()) + "." + property.link_origin_property_name; - //std::cout << "mapping named backlink: " << property.name << " to: " << native_name << std::endl; - mapping.add_mapping(table, property.name, native_backlink_name); + mapping.add_mapping(table, property.name, native_name); } } } @@ -170,7 +168,7 @@ typename T::Object ResultsClass::create_filtered(ContextType ctx, const U &co auto const &realm = collection.get_realm(); auto const &object_schema = collection.get_object_schema(); DescriptorOrdering ordering; - parser::KeypathMapping mapping; + parser::KeyPathMapping mapping; alias_backlinks(mapping, realm); parser::ParserResult result = parser::parse(query_string); diff --git a/tests/js/linkingobjects-tests.js b/tests/js/linkingobjects-tests.js index 943a6aac..d5aff0e9 100644 --- a/tests/js/linkingobjects-tests.js +++ b/tests/js/linkingobjects-tests.js @@ -83,6 +83,26 @@ module.exports = { TestCase.assertArraysEqual(names(resultsC), ['JP']); }, + testFilteredLinkingObjectsByName: function() { + var realm = new Realm({schema: [schemas.PersonObject]}); + + var christine, olivier; + realm.write(function() { + olivier = realm.create('PersonObject', {name: 'Olivier', age: 0}); + christine = realm.create('PersonObject', {name: 'Christine', age: 25, children: [olivier]}); + realm.create('PersonObject', {name: 'JP', age: 28, children: [olivier]}); + }); + + let people = realm.objects('PersonObject') + + TestCase.assertEqual(people.filtered('parents.age > 25').length, 1); + TestCase.assertEqual(people.filtered('parents.age > 25')[0].name, 'Olivier'); + TestCase.assertEqual(people.filtered('parents.@count == 2').length, 1); + TestCase.assertEqual(people.filtered('parents.name CONTAINS[c] "chris"').length, 1); + TestCase.assertEqual(people.filtered('parents.name.@size == 2').length, 1); + TestCase.assertEqual(people.filtered('25 IN parents.age').length, 1); + }, + testMethod: function() { var realm = new Realm({schema: [schemas.PersonObject]}); @@ -124,36 +144,4 @@ module.exports = { TestCase.assertEqual(oliviersParents.length, 0); }); }, - - testFilterByLinkingObject: function() { - const Realm = require('realm') - - const ProductSchema = { - name: 'Product', - primaryKey:'productId', - properties: { - productId:'int', - product:'string', - combinations: {type: 'linkingObjects', objectType: 'Combination', property: 'products'} - } - } - - const CombinationSchema = { - name: 'Combination', - properties: { - onSale: {type: 'string'}, - products: {type: 'list', objectType:'Product'} - } - } - - Realm.open({ - schema: [ProductSchema, CombinationSchema] - }).then(function (realm) { - let queryFilter1 = 'onSale = "yes" AND products.productId = 1' - let data1 = realm.objects('Combination').filtered(queryFilter1) - - let queryFilter2 = 'combinations.onSale = "yes" AND productId = 1' - let data2 = realm.objects('Product').filtered(queryFilter2) - }) - }, }; From 462856a24dafafa1c9a0762c5bf28151e9762132 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 20 Feb 2018 13:40:54 +0100 Subject: [PATCH 21/61] Partial sync (#1583) The new Partial Sync API --- CHANGELOG.md | 13 +- docs/collection.js | 36 ++- docs/realm.js | 11 - docs/sync.js | 58 +++++ .../realm/react/example/MainApplication.java | 2 +- lib/browser/constants.js | 1 + lib/browser/index.js | 4 +- lib/browser/results.js | 1 + lib/browser/subscription.js | 38 ++++ lib/extensions.js | 19 +- lib/index.d.ts | 25 ++- react-native/android/src/main/jni/Android.mk | 1 + realm.gypi | 4 +- src/RealmJS.xcodeproj/project.pbxproj | 18 +- src/js_collection.hpp | 11 +- src/js_realm.hpp | 50 ----- src/js_results.hpp | 47 +++- src/js_sync.hpp | 122 +++++++++++ src/object-store | 2 +- tests/js/index.js | 2 +- tests/js/session-tests.js | 206 +++++++++++++++++- 21 files changed, 550 insertions(+), 121 deletions(-) create mode 100644 lib/browser/subscription.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 08f84e74..fe19f677 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,22 @@ 2.3.0 Release notes (2018-2-19) ============================================================= ### Breaking changes -* Sync protocol changed to version 24. -* History schema format for server-side Realm files bumped to version 4. This means that after the server has been upgraded, it cannot be downgraded again without restoring state from backup. -* Backup protocol version bumped to 2. No compatibility with earlier versions of the backup protocol is provided. +* [Object Server] Sync protocol changed to version 24. +* [Object Server] History schema format for server-side Realm files bumped to version 4. This means that after the server has been upgraded, it cannot be downgraded again without restoring state from backup. +* [Object Server] Backup protocol version bumped to 2. No compatibility with earlier versions of the backup protocol is provided. +* [Object Server] `Realm.subscribeToObjects()` has been removed. Use `Realm.Results.subscribe()` instead. ### Enhancements -* Reduced initial download times in Realms with long transaction histories. -* Wait for pending notifications to complete when removing a sync listener (1648). +* [Object Server] Reduced initial download times in Realms with long transaction histories. +* [Object Server] Wait for pending notifications to complete when removing a sync listener (1648). * Enabled sort and distinct in the query string. If sort or distinct are also applied outside of the query string, the conditions are stacked. - Example syntax: `age > 20 SORT(name ASC, age DESC) DISTINCT(name)` - The ordering for sorting can be one of the following case insensitive literals: `ASC`, `ASCENDING`, `DESC`, `DESCENDING`. - Any number of properties can appear inside the brackets in a comma separated list. - Any number of sort/distinct conditions can be indicated, they will be applied in the specified order. - Sort or distinct cannot operate independently, these conditions must be attached to at least one query filter. +* [Object Server] Added `Realm.Results.subscribe()` to subscribe to partial synced Realms. +* [Object Server] Added class `Realm.Sync.Subscription` to support partial synced Realms. ### Internal * Updated to Realm Core 5.2.0. diff --git a/docs/collection.js b/docs/collection.js index a3f524b3..648738e7 100644 --- a/docs/collection.js +++ b/docs/collection.js @@ -134,6 +134,26 @@ class Collection { */ snapshot() {} + /** + * Subscribe to a subset of objects matching the query of the collection. The Realm will only be + * partially synced. Not all queries are currently supported. Once subscribed, it is highly recommended + * to add a listener. + * + * @example + * let wines = realm.objects('Wine').filtered('vintage <= $0', maxYear).subscribe(); + * wines.addListener((collection, changes) => { + * if (changes.partial_sync.new_state == Realm.Sync.SubscriptionState.Initialized) { + * // update UI + * } + * }); + * + * @param {string} subscriptionName - an optional name for the subscription. + * @returns {Realm.Sync.Subscription} - the Realm.Sync.Subscription instance. + * @throws {Error} if the partial sync is not enabled in the configuration or the query is not supported by Realm Object Server. + * @since 2.3.0 + */ + subscribe(subscriptionName) {} + /** * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries Array.prototype.entries} * @returns {Realm.Collection~Iterator} of each `[index, object]` pair in the collection @@ -400,15 +420,21 @@ class Collection { * The callback function is called with two arguments: * - `collection`: the collection instance that changed, * - `changes`: a dictionary with keys `insertions`, `modifications` and `deletions`, - * each containing a list of indices that were inserted, updated or deleted respectively. + * each containing a list of indices that were inserted, updated or deleted respectively. If + * partial sync is enabled, an additional key `partial_sync` is added. + * - `changes.partial_sync`: `error` indicates if an error has occurred, `old_state` is the previous + * state, and `new_state` is the current state. * @throws {Error} If `callback` is not a function. * @example * wines.addListener((collection, changes) => { * // collection === wines - * console.log(`${changes.insertions.length} insertions`); - * console.log(`${changes.modifications.length} modifications`); - * console.log(`${changes.deletions.length} deletions`); - * console.log(`new size of collection: ${collection.length}`); + * if (changes.partial_sync.new_state == Realm.Sync.SubscriptionState.Initialized) { + * console.log('Our subset is ready'); + * console.log(`${changes.insertions.length} insertions`); + * console.log(`${changes.modifications.length} modifications`); + * console.log(`${changes.deletions.length} deletions`); + * console.log(`new size of collection: ${collection.length}`); + * } * }); */ addListener(callback) {} diff --git a/docs/realm.js b/docs/realm.js index d4785253..a04c259d 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -241,17 +241,6 @@ class Realm { * @returns {true} if compaction succeeds. */ compact() {} - - /** - * If the Realm is a partially synchronized Realm, fetch and synchronize the objects - * of a given object type that match the given query (in string format). - * - * **Partial synchronization is a tech preview. Its APIs are subject to change.** - * @param {Realm~ObjectType} type - The type of Realm objects to retrieve. - * @param {string} query - Query used to filter objects. - * @return {Promise} - a promise that will be resolved with the Realm.Results instance when it's available. - */ - subscribeToObjects(className, query, callback) {} } /** diff --git a/docs/sync.js b/docs/sync.js index 65d6520f..77dfeedc 100644 --- a/docs/sync.js +++ b/docs/sync.js @@ -468,6 +468,64 @@ class Session { removeProgressNotification(progressCallback) {} } +/** + * An object encapsulating partial sync subscriptions. + * @memberof Realm.Sync + */ +class Subscription { + /** + * Gets the current state of the subscription. + * Can be either: + * - Realm.Sync.SubscriptionState.Error: An error occurred while creating or processing the partial sync subscription. + * - Realm.Sync.SubscriptionState.Creating: The subscription is being created. + * - Realm.Sync.SubscriptionState.Pending: The subscription was created, but has not yet been processed by the sync server. + * - Realm.Sync.SubscriptionState.Complete: The subscription has been processed by the sync server and data is being synced to the device. + * - Realm.Sync.SubscriptionState.Invalidated: The subscription has been removed. + * @type {number} + */ + get state() {} + + /** + * Gets the error message. `undefined` if no error. + * @type {string} + */ + get error() {} + + /** + * Unsubscribe a partial synced `Realm.Results`. The state will change to `Realm.Sync.SubscriptionState.Invalidated`. + * The `Realm.Results` will not produce any meaningful values. Moreover, any objects matching the query will be + * removed if they are not matched by any other query. The object removal is done asynchronously. + */ + unsubscribe() {} + + /** + * Adds a listener `callback` which will be called when the state of the subscription changes. + * @param {function(state)} callback - A function to be called when changes occur. + * @throws {Error} If `callback` is not a function. + * @example + * let subscription = results.subscribe(); + * subscription.addListener((subscription, state) => { + * switch (state) { + * case Realm.Sync.SubscriptionState.Complete: + * // results is ready to be consumed + * break; + * case Realm.Sync.SubscriptionState.Error: + * console.log('An error occurred: ', subscription.error); + * break; + * } + * } + */ + addListener(callback) {} + + /** + * Remove the listener `callback` from the subscription instance. + * @param {function(collection, changes)} callback - Callback function that was previously + * added as a listener through the {@link Subscription#addListener addListener} method. + * @throws {Error} If `callback` is not a function. + */ + removeListener(callback) {} +} + /** * A Realm Worker can be used to process Sync events in multiple automatically-managed child processes. * diff --git a/examples/ReactExample/android/app/src/main/java/io/realm/react/example/MainApplication.java b/examples/ReactExample/android/app/src/main/java/io/realm/react/example/MainApplication.java index b220a0d8..2a428589 100644 --- a/examples/ReactExample/android/app/src/main/java/io/realm/react/example/MainApplication.java +++ b/examples/ReactExample/android/app/src/main/java/io/realm/react/example/MainApplication.java @@ -20,7 +20,7 @@ public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { @Override - protected boolean getUseDeveloperSupport() { + public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } diff --git a/lib/browser/constants.js b/lib/browser/constants.js index fe7360fe..3cb55ab6 100644 --- a/lib/browser/constants.js +++ b/lib/browser/constants.js @@ -41,6 +41,7 @@ export const propTypes = {}; 'RESULTS', 'USER', 'SESSION', + 'SUBSCRIPTION', 'UNDEFINED', ].forEach(function(type) { Object.defineProperty(objectTypes, type, { diff --git a/lib/browser/index.js b/lib/browser/index.js index a2e449d2..dbd6d42f 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -130,7 +130,6 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [ 'close', '_waitForDownload', '_objectForObjectId', - '_subscribeToObjects', ]); // Mutating methods: @@ -147,7 +146,8 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [ const Sync = { User, - Session + Session, + Subscription, }; Object.defineProperties(Realm, { diff --git a/lib/browser/results.js b/lib/browser/results.js index a35f9fda..d309d7e8 100644 --- a/lib/browser/results.js +++ b/lib/browser/results.js @@ -30,6 +30,7 @@ createMethods(Results.prototype, objectTypes.RESULTS, [ 'filtered', 'sorted', 'snapshot', + 'subscribe', 'isValid', 'indexOf', 'min', diff --git a/lib/browser/subscription.js b/lib/browser/subscription.js new file mode 100644 index 00000000..3dddeac2 --- /dev/null +++ b/lib/browser/subscription.js @@ -0,0 +1,38 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2018 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +'use strict'; + +import { objectTypes } from './constants'; +import { getterForProperty, createMethods } from './util'; + +export default class Subscription { + +} + +Object.defineProperties(Subscription.prototype, { + error: { get: getterForProperty('error') }, + state: { get: getterForProperty('state') } +}); + +// // Non-mutating methods: +createMethods(Subscription.prototype, objectTypes.SUBSCRIPTION, [ + 'unsubscribe', + 'addListener', + 'removeListener' +]); diff --git a/lib/extensions.js b/lib/extensions.js index f1c04695..5c793e34 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -159,18 +159,13 @@ module.exports = function(realmConstructor) { } } - realmConstructor.prototype.subscribeToObjects = function(objectType, query) { - const realm = this; - let promise = new Promise((resolve, reject) => { - realm._subscribeToObjects(objectType, query, function(err, results) { - if (err) { - reject(err); - } else { - resolve(results); - } - }); - }); - return promise; + // Keep these value in sync with subscription_state.hpp + realmConstructor.Sync.SubscriptionState = { + Error: -1, // An error occurred while creating or processing the partial sync subscription. + Creating: 2, // The subscription is being created. + Pending: 0, // The subscription was created, but has not yet been processed by the sync server. + Complete: 1, // The subscription has been processed by the sync server and data is being synced to the device. + Invalidated: 3, // The subscription has been removed. }; } diff --git a/lib/index.d.ts b/lib/index.d.ts index 878d8e82..b333adc1 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -161,6 +161,11 @@ declare namespace Realm { sorted(descriptor: SortDescriptor[]): Results; sorted(descriptor: string, reverse?: boolean): Results; + /** + * @returns Results + */ + subscribe(subscriptionName?: string): Realm.Sync.Subscription; + /** * @returns Results */ @@ -394,6 +399,21 @@ declare namespace Realm.Sync { removeProgressNotification(progressCallback: ProgressNotificationCallback): void; } + type SubscriptionNotificationCallback = (state: number) => void; + + /** + * Subscription + * @see { @link https://realm.io/docs/javascript/latest/api/Realm.Sync.Subscription.html } + */ + class Subscription { + readonly state: number; + readonly error: string; + + unsubscribe(): void; + addListener(subscruptionCallback: SubscriptionNotificationCallback): void; + removeListener(subscruptionCallback: SubscriptionNotificationCallback): void; + } + /** * AuthError * @see { @link https://realm.io/docs/javascript/latest/api/Realm.Sync.AuthError.html } @@ -600,11 +620,6 @@ declare class Realm { * @returns boolean */ compact(): boolean; - - /** - * @returns Promise> - */ - subscribeToObjects(objectType: string, query: string): Promise>; } declare module 'realm' { diff --git a/react-native/android/src/main/jni/Android.mk b/react-native/android/src/main/jni/Android.mk index 87ced46c..ce0554ca 100644 --- a/react-native/android/src/main/jni/Android.mk +++ b/react-native/android/src/main/jni/Android.mk @@ -78,6 +78,7 @@ LOCAL_SRC_FILES += src/object-store/src/sync/sync_user.cpp LOCAL_SRC_FILES += src/object-store/src/sync/sync_permission.cpp LOCAL_SRC_FILES += src/object-store/src/sync/impl/sync_file.cpp LOCAL_SRC_FILES += src/object-store/src/sync/impl/sync_metadata.cpp +LOCAL_SRC_FILES += src/object-store/src/sync/impl/work_queue.cpp endif LOCAL_C_INCLUDES := src diff --git a/realm.gypi b/realm.gypi index 94b8c297..45aa4dfb 100644 --- a/realm.gypi +++ b/realm.gypi @@ -99,6 +99,7 @@ "src/object-store/src/sync/impl/sync_client.hpp", "src/object-store/src/sync/impl/sync_file.hpp", "src/object-store/src/sync/impl/sync_metadata.hpp", + "src/object-store/src/sync/impl/work_queue.hpp", "src/object-store/src/sync/partial_sync.hpp", "src/object-store/src/sync/sync_config.hpp", "src/object-store/src/sync/sync_manager.hpp", @@ -147,7 +148,8 @@ "src/object-store/src/sync/sync_session.cpp", "src/object-store/src/sync/sync_config.cpp", "src/object-store/src/sync/impl/sync_file.cpp", - "src/object-store/src/sync/impl/sync_metadata.cpp" + "src/object-store/src/sync/impl/sync_metadata.cpp", + "src/object-store/src/sync/impl/work_queue.cpp" ], }] ], diff --git a/src/RealmJS.xcodeproj/project.pbxproj b/src/RealmJS.xcodeproj/project.pbxproj index 9ec1b86c..93138bab 100644 --- a/src/RealmJS.xcodeproj/project.pbxproj +++ b/src/RealmJS.xcodeproj/project.pbxproj @@ -41,6 +41,8 @@ 3FCE2A931F58BE0300D4855B /* uuid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3FCE2A911F58BDFF00D4855B /* uuid.cpp */; }; 3FCE2A971F58BE2200D4855B /* sync_permission.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3FCE2A951F58BE1D00D4855B /* sync_permission.cpp */; }; 420FB79F1F7FBFE900D43D0F /* partial_sync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 423737AF1F7E333400FAEDFF /* partial_sync.cpp */; }; + 425A121120235A1400C2F932 /* work_queue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 425A120F20235A1400C2F932 /* work_queue.cpp */; }; + 4261AF8E203C42000052450D /* work_queue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 425A120F20235A1400C2F932 /* work_queue.cpp */; }; 502B07E41E2CD201007A84ED /* object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 502B07E31E2CD1FA007A84ED /* object.cpp */; }; 504CF85E1EBCAE3600A9A4B6 /* network_reachability_observer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 504CF8541EBCAE3600A9A4B6 /* network_reachability_observer.cpp */; }; 504CF85F1EBCAE3600A9A4B6 /* system_configuration.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 504CF8561EBCAE3600A9A4B6 /* system_configuration.cpp */; }; @@ -185,6 +187,8 @@ 3FCE2A991F58BE3600D4855B /* feature_checks.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = feature_checks.hpp; path = "object-store/src/feature_checks.hpp"; sourceTree = SOURCE_ROOT; }; 423737AF1F7E333400FAEDFF /* partial_sync.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = partial_sync.cpp; path = src/sync/partial_sync.cpp; sourceTree = ""; }; 423737B01F7E333400FAEDFF /* partial_sync.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = partial_sync.hpp; path = src/sync/partial_sync.hpp; sourceTree = ""; }; + 425A120F20235A1400C2F932 /* work_queue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = work_queue.cpp; sourceTree = ""; }; + 425A121020235A1400C2F932 /* work_queue.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = work_queue.hpp; sourceTree = ""; }; 426FCDFF1F7DA2F9005565DC /* sync_config.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sync_config.cpp; path = src/sync/sync_config.cpp; sourceTree = ""; }; 502B07E31E2CD1FA007A84ED /* object.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = object.cpp; path = src/object.cpp; sourceTree = ""; }; 502B07E51E2CD20D007A84ED /* object.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = object.hpp; path = src/object.hpp; sourceTree = ""; }; @@ -474,6 +478,8 @@ 504CF8521EBCAE3600A9A4B6 /* impl */ = { isa = PBXGroup; children = ( + 425A120F20235A1400C2F932 /* work_queue.cpp */, + 425A121020235A1400C2F932 /* work_queue.hpp */, 504CF8531EBCAE3600A9A4B6 /* apple */, 504CF8581EBCAE3600A9A4B6 /* network_reachability.hpp */, 504CF8591EBCAE3600A9A4B6 /* sync_client.hpp */, @@ -873,6 +879,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4261AF8E203C42000052450D /* work_queue.cpp in Sources */, F63FF2E21C15921A00B3B8E0 /* base64.cpp in Sources */, 022BF1021E7266DF00F382F1 /* binding_callback_thread_observer.cpp in Sources */, 02414BA51CE6ABCF00A8669F /* collection_change_builder.cpp in Sources */, @@ -934,6 +941,7 @@ F63FF31E1C1642BB00B3B8E0 /* GCDWebServerRequest.m in Sources */, F63FF31F1C1642BB00B3B8E0 /* GCDWebServerResponse.m in Sources */, F63FF3271C1642BB00B3B8E0 /* GCDWebServerStreamedResponse.m in Sources */, + 425A121120235A1400C2F932 /* work_queue.cpp in Sources */, F63FF3231C1642BB00B3B8E0 /* GCDWebServerURLEncodedFormRequest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1115,10 +1123,7 @@ "-isystem", "$(SRCROOT)/../vendor/sync/include", ); - OTHER_LIBTOOLFLAGS = ( - "$(SRCROOT)/../vendor/realm-ios/librealm-parser-ios-dbg.a", - "$(SRCROOT)/../vendor/realm-ios/librealm-ios-dbg.a", - ); + OTHER_LIBTOOLFLAGS = "$(SRCROOT)/../vendor/realm-ios/librealm-parser-ios-dbg.a $(SRCROOT)/../vendor/realm-ios/librealm-ios-dbg.a"; PRODUCT_NAME = RealmJS; SKIP_INSTALL = YES; }; @@ -1140,10 +1145,7 @@ "-isystem", "$(SRCROOT)/../vendor/sync/include", ); - OTHER_LIBTOOLFLAGS = ( - "$(SRCROOT)/../vendor/realm-ios/librealm-parser-ios.a", - "$(SRCROOT)/../vendor/realm-ios/librealm-ios.a", - ); + OTHER_LIBTOOLFLAGS = "$(SRCROOT)/../vendor/realm-ios/librealm-parser-ios.a $(SRCROOT)/../vendor/realm-ios/librealm-ios.a"; PRODUCT_NAME = RealmJS; SKIP_INSTALL = YES; }; diff --git a/src/js_collection.hpp b/src/js_collection.hpp index 4e537791..a83db75f 100644 --- a/src/js_collection.hpp +++ b/src/js_collection.hpp @@ -23,6 +23,9 @@ #include "js_observable.hpp" #include "collection_notifications.hpp" +#if REALM_ENABLE_SYNC +#include "sync/subscription_state.hpp" +#endif namespace realm { namespace js { @@ -39,7 +42,7 @@ struct CollectionClass : ClassDefinition> { using Value = js::Value; std::string const name = "Collection"; - + static inline ValueType create_collection_change_set(ContextType ctx, const CollectionChangeSet &change_set); }; @@ -48,7 +51,7 @@ typename T::Value CollectionClass::create_collection_change_set(ContextType c { ObjectType object = Object::create_empty(ctx); std::vector deletions, insertions, modifications; - + if (change_set.deletions.count() == std::numeric_limits::max()) { deletions.push_back(Value::from_null(ctx)); } @@ -58,12 +61,12 @@ typename T::Value CollectionClass::create_collection_change_set(ContextType c } } 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)); } diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 5a434e64..961032a7 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -186,9 +186,6 @@ public: static void compact(ContextType, ObjectType, Arguments, ReturnValue &); static void delete_model(ContextType, ObjectType, Arguments, ReturnValue &); static void object_for_object_id(ContextType, ObjectType, Arguments, ReturnValue&); -#if REALM_ENABLE_SYNC - static void subscribe_to_objects(ContextType, ObjectType, Arguments, ReturnValue &); -#endif // properties static void get_empty(ContextType, ObjectType, ReturnValue &); @@ -248,7 +245,6 @@ public: {"_objectForObjectId", wrap}, #if REALM_ENABLE_SYNC {"_waitForDownload", wrap}, - {"_subscribeToObjects", wrap}, #endif }; @@ -1061,51 +1057,5 @@ void RealmClass::object_for_object_id(ContextType ctx, ObjectType this_object #endif // REALM_ENABLE_SYNC } -#if REALM_ENABLE_SYNC -template -void RealmClass::subscribe_to_objects(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { - args.validate_count(3); - - SharedRealm realm = *get_internal>(this_object); - std::string object_type = Value::validated_to_string(ctx, args[0]); - std::string query = Value::validated_to_string(ctx, args[1]); - auto callback = Value::validated_to_function(ctx, args[2]); - - auto &schema = realm->schema(); - auto object_schema = schema.find(object_type); - - if (object_schema == schema.end()) { - throw std::runtime_error("Object type '" + object_type + "' not found in schema."); - } - - Protected protected_this(ctx, this_object); - Protected protected_ctx(Context::get_global_context(ctx)); - Protected protected_callback(ctx, callback); - auto cb = [=](realm::Results results, std::exception_ptr err) { - HANDLESCOPE - - if (err) { - try { - std::rethrow_exception(err); - } - catch (const std::exception& e) { - ValueType callback_arguments[2]; - callback_arguments[0] = Value::from_string(protected_ctx, e.what()); - callback_arguments[1] = Value::from_null(protected_ctx); - Function::callback(ctx, protected_callback, protected_this, 2, callback_arguments); - } - return; - } - - ValueType callback_arguments[2]; - callback_arguments[0] = Value::from_null(protected_ctx); - callback_arguments[1] = ResultsClass::create_instance(protected_ctx, results); - Function::callback(protected_ctx, protected_callback, protected_this, 2, callback_arguments); - }; - - partial_sync::register_query(realm, object_type, query, std::move(cb)); -} -#endif - } // js } // realm diff --git a/src/js_results.hpp b/src/js_results.hpp index f3694d08..8de32108 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -28,6 +28,11 @@ #include #include +#include +#ifdef REALM_ENABLE_SYNC +#include "js_sync.hpp" +#include "sync/partial_sync.hpp" +#endif namespace realm { namespace js { @@ -82,6 +87,9 @@ struct ResultsClass : ClassDefinition, CollectionClass< static void filtered(ContextType, ObjectType, Arguments, ReturnValue &); static void sorted(ContextType, ObjectType, Arguments, ReturnValue &); static void is_valid(ContextType, ObjectType, Arguments, ReturnValue &); +#if REALM_ENABLE_SYNC + static void subscribe(ContextType, ObjectType, Arguments, ReturnValue &); +#endif static void index_of(ContextType, ObjectType, Arguments, ReturnValue &); @@ -107,6 +115,9 @@ struct ResultsClass : ClassDefinition, CollectionClass< {"filtered", wrap}, {"sorted", wrap}, {"isValid", wrap}, +#if REALM_ENABLE_SYNC + {"subscribe", wrap}, +#endif {"min", wrap, AggregateFunc::Min>>}, {"max", wrap, AggregateFunc::Max>>}, {"sum", wrap, AggregateFunc::Sum>>}, @@ -254,6 +265,28 @@ void ResultsClass::is_valid(ContextType ctx, ObjectType this_object, Argument return_value.set(get_internal>(this_object)->is_valid()); } +#if REALM_ENABLE_SYNC +template +void ResultsClass::subscribe(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); + + auto results = get_internal>(this_object); + auto realm = results->get_realm(); + auto sync_config = realm->config().sync_config; + + util::Optional subscription_name; + if (args.count == 1) { + subscription_name = util::Optional(Value::validated_to_string(ctx, args[0])); + } + else { + subscription_name = util::none; + } + + auto subscription = partial_sync::subscribe(*results, subscription_name); + return_value.set(SubscriptionClass::create_instance(ctx, std::move(subscription))); +} +#endif + template template void ResultsClass::index_of(ContextType ctx, Fn& fn, Arguments args, ReturnValue &return_value) { @@ -326,13 +359,13 @@ void ResultsClass::add_listener(ContextType ctx, U& collection, ObjectType th Protected protected_ctx(Context::get_global_context(ctx)); auto token = collection.add_notification_callback([=](CollectionChangeSet const& change_set, std::exception_ptr exception) { - HANDLESCOPE - ValueType arguments[] { - static_cast(protected_this), - CollectionClass::create_collection_change_set(protected_ctx, change_set) - }; - Function::callback(protected_ctx, protected_callback, protected_this, 2, arguments); - }); + HANDLESCOPE + ValueType arguments[] { + static_cast(protected_this), + CollectionClass::create_collection_change_set(protected_ctx, change_set) + }; + Function::callback(protected_ctx, protected_callback, protected_this, 2, arguments); + }); collection.m_notification_tokens.emplace_back(protected_callback, std::move(token)); } diff --git a/src/js_sync.hpp b/src/js_sync.hpp index 238ab94c..b78f9cba 100644 --- a/src/js_sync.hpp +++ b/src/js_sync.hpp @@ -29,6 +29,7 @@ #include "sync/sync_config.hpp" #include "sync/sync_session.hpp" #include "sync/sync_user.hpp" +#include "sync/partial_sync.hpp" #include "realm/util/logger.hpp" #include "realm/util/uri.hpp" @@ -555,6 +556,127 @@ void SessionClass::override_server(ContextType ctx, ObjectType this_object, A } } +template +class Subscription : public partial_sync::Subscription { +public: + Subscription(partial_sync::Subscription s) : partial_sync::Subscription(std::move(s)) {} + Subscription(Subscription &&) = default; + + std::vector, partial_sync::SubscriptionNotificationToken>> m_notification_tokens; +}; + +template +class SubscriptionClass : public ClassDefinition> { + 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; + using Object = js::Object; + using Value = js::Value; + using Function = js::Function; + using ReturnValue = js::ReturnValue; + using Arguments = js::Arguments; + +public: + std::string const name = "Subscription"; + + static FunctionType create_constructor(ContextType); + static ObjectType create_instance(ContextType, partial_sync::Subscription); + + static void get_state(ContextType, ObjectType, ReturnValue &); + static void get_error(ContextType, ObjectType, ReturnValue &); + + static void unsubscribe(ContextType, ObjectType, Arguments, ReturnValue &); + static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &); + static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &); + + PropertyMap const properties = { + {"state", {wrap, nullptr}}, + {"error", {wrap, nullptr}} + }; + + MethodMap const methods = { + {"unsubscribe", wrap}, + {"addListener", wrap}, + {"removeListener", wrap}, + }; +}; + +template +typename T::Object SubscriptionClass::create_instance(ContextType ctx, partial_sync::Subscription subscription) { + return create_object>(ctx, new Subscription(std::move(subscription))); +} + +template +void SubscriptionClass::get_state(ContextType ctx, ObjectType object, ReturnValue &return_value) { + auto subscription = get_internal>(object); + return_value.set(static_cast(subscription->state())); +} + +template +void SubscriptionClass::get_error(ContextType ctx, ObjectType object, ReturnValue &return_value) { + auto subscription = get_internal>(object); + if (auto error = subscription->error()) { + try { + std::rethrow_exception(error); + } + catch (const std::exception& e) { + return_value.set(e.what()); + } + } + else { + return_value.set_undefined(); + } +} + +template +void SubscriptionClass::unsubscribe(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); + auto subscription = get_internal>(this_object); + partial_sync::unsubscribe(*subscription); + return_value.set_undefined(); +} + +template +void SubscriptionClass::add_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); + auto subscription = get_internal>(this_object); + + auto callback = Value::validated_to_function(ctx, args[0]); + Protected protected_callback(ctx, callback); + Protected protected_this(ctx, this_object); + Protected protected_ctx(Context::get_global_context(ctx)); + + auto token = subscription->add_notification_callback([=]() { + HANDLESCOPE + + ValueType arguments[2]; + arguments[0] = static_cast(protected_this), + arguments[1] = Value::from_number(ctx, static_cast(subscription->state())); + Function::callback(protected_ctx, protected_callback, protected_this, 2, arguments); + }); + + subscription->m_notification_tokens.emplace_back(protected_callback, std::move(token)); +} + +template +void SubscriptionClass::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); + auto subscription = get_internal>(this_object); + + auto callback = Value::validated_to_function(ctx, args[0]); + auto protected_function = Protected(ctx, callback); + + auto& tokens = subscription->m_notification_tokens; + auto compare = [&](auto&& token) { + return typename Protected::Comparator()(token.first, protected_function); + }; + tokens.erase(std::remove_if(tokens.begin(), tokens.end(), compare), tokens.end()); +} + + template class SyncClass : public ClassDefinition { using GlobalContextType = typename T::GlobalContext; diff --git a/src/object-store b/src/object-store index 56f24683..d8324724 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit 56f246831c6bd1bd33cf0f852d049b11e94e50e9 +Subproject commit d8324724eff33b768d02dba7cc268520ed335536 diff --git a/tests/js/index.js b/tests/js/index.js index 7aab4946..2e9e5670 100644 --- a/tests/js/index.js +++ b/tests/js/index.js @@ -55,7 +55,7 @@ if (global.enableSyncTests) { // FIXME: Permission tests currently fail in chrome debugging mode. if (typeof navigator === 'undefined' || !/Chrome/.test(navigator.userAgent)) { // eslint-disable-line no-undef - TESTS.PermissionTests = require('./permission-tests'); + TESTS.PermissionTests = require('./permission-tests'); } } diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index 90fc45ed..8fa54fc1 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -523,7 +523,7 @@ module.exports = { }); }, - testProgressNotificationsForRealmConstructor() { +/* testProgressNotificationsForRealmConstructor() { if (!isNodeProccess) { return; } @@ -555,7 +555,7 @@ module.exports = { }); }); }); - }, + },*/ testProgressNotificationsUnregisterForRealmConstructor() { if (!isNodeProccess) { @@ -707,8 +707,7 @@ module.exports = { }); }, - /* Disabled: waiting for new implementation - testPartialSync() { + testPartialSyncAnonymous_SubscriptionListener() { // FIXME: try to enable for React Native if (!isNodeProccess) { return; @@ -733,14 +732,205 @@ module.exports = { Realm.deleteFile(config); const realm = new Realm(config); TestCase.assertEqual(realm.objects('Dog').length, 0); - return realm.subscribeToObjects("Dog", "name == 'Lassy 1'").then(results => { - TestCase.assertEqual(results.length, 1); - TestCase.assertTrue(results[0].name === 'Lassy 1', "The object is not synced correctly"); + var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var subscription = results.subscribe(); + TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); + return new Promise((resolve, reject) => { + subscription.addListener((subscription, state) => { + if (state == Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(results.length, 1); + TestCase.assertTrue(results[0].name === 'Lassy 1', "The object is not synced correctly"); + resolve(); + } + }); + setTimeout(function() { + reject("listener never called"); + }, 5000); }); }) }) }, - */ + + testPartialSyncAnonymous_ResultsListener() { + // FIXME: try to enable for React Native + if (!isNodeProccess) { + return; + } + + const username = uuid(); + const realmName = uuid(); + + return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) + .then(() => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: true, + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; + + Realm.deleteFile(config); + const realm = new Realm(config); + TestCase.assertEqual(realm.objects('Dog').length, 0); + var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var subscription = results.subscribe(); + TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); + return new Promise((resolve, reject) => { + results.addListener((collection, changes) => { + if (subscription.state === Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(collection.length, 1); + TestCase.assertTrue(collection[0].name === 'Lassy 1', "The object is not synced correctly"); + resolve(); + } + }); + setTimeout(function() { + reject("listener never called"); + }, 5000); + }); + }) + }) + }, + + testPartialSyncMultipleSubscriptions() { + // FIXME: try to enable for React Native + if (!isNodeProccess) { + return; + } + + const username = uuid(); + const realmName = uuid(); + + return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) + .then(() => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: true, + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; + + Realm.deleteFile(config); + const realm = new Realm(config); + TestCase.assertEqual(realm.objects('Dog').length, 0); + var results1 = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var results2 = realm.objects('Dog').filtered("name == 'Lassy 2'"); + var subscription1 = results1.subscribe(); + var subscription2 = results2.subscribe(); + + return new Promise((resolve, reject) => { + let called1 = false; + let called2 = false; + results1.addListener((collection, changeset) => { + if (subscription1.state == Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(collection.length, 1); + TestCase.assertTrue(collection[0].name === 'Lassy 1', "The object is not synced correctly"); + called1 = true; + if (called1 && called2) { + resolve(); + } + } + }); + results2.addListener((collection, changeset) => { + if (subscription2.state == Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(collection.length, 1); + TestCase.assertTrue(collection[0].name === 'Lassy 2', "The object is not synced correctly"); + called2 = true; + if (called1 && called2) { + resolve(); + } + } + }); + + setTimeout(function() { + reject("listener never called"); + }, 5000); + }); + }) + }) + }, + + testPartialSyncFailing() { + // FIXME: try to enable for React Native + if (!isNodeProccess) { + return; + } + + const username = uuid(); + const realmName = uuid(); + + return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) + .then(() => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: false, // <---- calling subscribe should fail + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; + + Realm.deleteFile(config); + const realm = new Realm(config); + TestCase.assertEqual(realm.objects('Dog').length, 0); + TestCase.assertThrows(function () { var subscription = realm.objects('Dog').filtered("name == 'Lassy 1'").subscribe(); } ); + }); + }); + }, + + testPartialSyncUnsubscribe() { + // FIXME: try to enable for React Native + if (!isNodeProccess) { + return; + } + + const username = uuid(); + const realmName = uuid(); + + return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) + .then(() => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: true, + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; + + Realm.deleteFile(config); + const realm = new Realm(config); + var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var subscription = results.subscribe(); + TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); + return new Promise((resolve, reject) => { + results.addListener((collection, changes) => { + if (subscription.state === Realm.Sync.SubscriptionState.Complete) { + subscription.unsubscribe(); + } + if (subscription.state === Realm.Sync.SubscriptionState.Invalidated) { + resolve(); + } + }); + setTimeout(function() { + reject("listener never called"); + }, 5000); + }); + }); + }); + }, + testClientReset() { // FIXME: try to enable for React Native if (!isNodeProccess) { From 9580997ba92b9641acfd39c0141c09e82395e703 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 23 Feb 2018 18:17:58 +0000 Subject: [PATCH 22/61] [2.3.0-alpha.12] Bump version --- dependencies.list | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies.list b/dependencies.list index f68a8de1..fea3bec3 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.11 +VERSION=2.3.0-alpha.12 REALM_CORE_VERSION=5.2.0 -REALM_SYNC_VERSION=3.0.0-beta.6 +REALM_SYNC_VERSION=3.0.0-beta.8 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.2 From e77ca6c11927b20593fe3c2f47a9a440e720b945 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 23 Feb 2018 18:23:48 +0000 Subject: [PATCH 23/61] updated package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 714e7346..65a77b90 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.11", + "version": "2.3.0-alpha.12", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From 4122305ea3a755531c7687a67ca1b60a444fa67b Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 26 Feb 2018 17:33:50 +0000 Subject: [PATCH 24/61] [2.3.0-alpha.13] Bump version --- dependencies.list | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dependencies.list b/dependencies.list index fea3bec3..df28f8a4 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.12 +VERSION=2.3.0-alpha.13 REALM_CORE_VERSION=5.2.0 -REALM_SYNC_VERSION=3.0.0-beta.8 +REALM_SYNC_VERSION=3.0.0-beta.9 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.2 diff --git a/package.json b/package.json index 65a77b90..b08bb4a2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.12", + "version": "2.3.0-alpha.13", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From ec5ea3e32acfcb8f16623d7cc89bdd08787c94c3 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 27 Feb 2018 11:28:53 +0100 Subject: [PATCH 25/61] Remember to mention which sync version we are using. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c07b5d87..a23d782e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ ### Internal * Updated to Realm Core 5.2.0. -* Updated to Realm Sync 3.0.0-beta.5. +* Updated to Realm Sync 3.0.0-beta.9. * Tested against Realm Object Server 3.0.0-alpha.2. From 1ceccd30fd07c578f0fb0e7135fc052d8a18bff9 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 27 Feb 2018 11:40:36 +0100 Subject: [PATCH 26/61] Updating changelog. --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a23d782e..1ecd6d68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,15 @@ ### Enhancements * [Sync] Reduced initial download times in Realms with long transaction histories. -* [Sync] Wait for pending notifications to complete when removing a sync listener (1648). +* [Sync] Wait for pending notifications to complete when removing a sync listener (#1648). * Enabled sort and distinct in the query string. If sort or distinct are also applied outside of the query string, the conditions are stacked. - Example syntax: `age > 20 SORT(name ASC, age DESC) DISTINCT(name)` - The ordering for sorting can be one of the following case insensitive literals: `ASC`, `ASCENDING`, `DESC`, `DESCENDING`. - Any number of properties can appear inside the brackets in a comma separated list. - Any number of sort/distinct conditions can be indicated, they will be applied in the specified order. - Sort or distinct cannot operate independently, these conditions must be attached to at least one query filter. +* Added support for queries over named backlinks (#1498/#1660). + - Example syntax: `parents.age > 25` and `parents.@count == 2`. * [Sync] Added `Realm.Results.subscribe()` to subscribe to partial synced Realms. * [Sync] Added class `Realm.Sync.Subscription` to support partial synced Realms. From fd69bf7fc43cdfe7ca930078ae4bacc10c7c9e06 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 27 Feb 2018 13:24:39 +0100 Subject: [PATCH 27/61] Don't use latest NaN version. Use old version of node for tests. --- package.json | 2 +- scripts/test.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b08bb4a2..ba7d6c9b 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "decompress": "^4.2.0", "fs-extra": "^4.0.2", "ini": "^1.3.4", - "nan": "^2.3.3", + "nan": "2.8.0", "node-fetch": "^1.6.3", "node-pre-gyp": "^0.6.36", "progress": "^2.0.0", diff --git a/scripts/test.sh b/scripts/test.sh index 16832ea2..f4bd0784 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -211,7 +211,7 @@ if [[ -z "$(command -v nvm)" ]]; then set -e fi if [[ "$(command -v nvm)" ]]; then - nvm install 7.10.0 + nvm install 6.11.3 fi # Remove cached packages From c90a5a49032a62adbe03532c15243110fad4b990 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 27 Feb 2018 18:20:54 +0100 Subject: [PATCH 28/61] Update object store --- src/object-store | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/object-store b/src/object-store index d5fe9a62..703c391c 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit d5fe9a626c0adc0345d128201e0df8bb4004725d +Subproject commit 703c391c64f73c812358940993942338f1bc706d From 2f2c557cc16f31d6719c35ac7ab7369ac412f668 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 1 Mar 2018 15:37:44 +0100 Subject: [PATCH 29/61] [2.3.0-alpha.14] Bump version --- CHANGELOG.md | 6 +++--- dependencies.list | 6 +++--- package.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a23d782e..c8c427cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-2-19) +2.3.0 Release notes (2018-3-1) ============================================================= ### Breaking changes * [Sync] Sync protocol changed to version 24. @@ -18,9 +18,9 @@ * [Sync] Added class `Realm.Sync.Subscription` to support partial synced Realms. ### Internal -* Updated to Realm Core 5.2.0. +* Updated to Realm Core 5.8.0. * Updated to Realm Sync 3.0.0-beta.9. -* Tested against Realm Object Server 3.0.0-alpha.2. +* Tested against Realm Object Server 3.0.0-alpha.8. 2.2.10 Release notes (2018-2-20) diff --git a/dependencies.list b/dependencies.list index df28f8a4..8e1d7708 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.13 -REALM_CORE_VERSION=5.2.0 +VERSION=2.3.0-alpha.14 +REALM_CORE_VERSION=5.3.0 REALM_SYNC_VERSION=3.0.0-beta.9 -REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.2 +REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.8 diff --git a/package.json b/package.json index b08bb4a2..4fa0a72a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.13", + "version": "2.3.0-alpha.14", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From 27349fc0b9c1dd34dc4366084265af63000fb116 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 1 Mar 2018 15:38:26 +0100 Subject: [PATCH 30/61] [2.3.0-alpha.14] Bump version --- scripts/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test.sh b/scripts/test.sh index 16832ea2..f4bd0784 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -211,7 +211,7 @@ if [[ -z "$(command -v nvm)" ]]; then set -e fi if [[ "$(command -v nvm)" ]]; then - nvm install 7.10.0 + nvm install 6.11.3 fi # Remove cached packages From 9698ea651d7abc6038cc454046743a2215b92aeb Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 1 Mar 2018 16:38:47 +0100 Subject: [PATCH 31/61] Update --- src/object-store | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/object-store b/src/object-store index d5fe9a62..bb559df9 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit d5fe9a626c0adc0345d128201e0df8bb4004725d +Subproject commit bb559df9237ece49f9c889993f7c1aff619b48f9 From e93ff333eded7261d86e53372bbc2f3063627522 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 1 Mar 2018 20:30:57 +0000 Subject: [PATCH 32/61] [2.3.0-alpha.15] Bump version --- dependencies.list | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dependencies.list b/dependencies.list index 8e1d7708..a87aea2f 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.14 +VERSION=2.3.0-alpha.15 REALM_CORE_VERSION=5.3.0 -REALM_SYNC_VERSION=3.0.0-beta.9 +REALM_SYNC_VERSION=3.0.0-beta.10 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.8 diff --git a/package.json b/package.json index 4fa0a72a..eedaa630 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.14", + "version": "2.3.0-alpha.15", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From d89c7ea24f4b7062650a733410a57ac671475ae4 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 28 Feb 2018 11:29:53 -0800 Subject: [PATCH 33/61] Delete a helper function that's now exposed directly by sync --- src/js_realm.hpp | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 961032a7..72ce3b31 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -1002,36 +1002,6 @@ void RealmClass::compact(ContextType ctx, ObjectType this_object, Arguments a return_value.set(realm->compact()); } -#if REALM_ENABLE_SYNC -namespace { - -// FIXME: Sync should provide this: https://github.com/realm/realm-sync/issues/1796 -inline sync::ObjectID object_id_from_string(std::string const& string) -{ - if (string.front() != '{' || string.back() != '}') - throw std::invalid_argument("Invalid object ID."); - - size_t dash_index = string.find('-'); - if (dash_index == std::string::npos) - throw std::invalid_argument("Invalid object ID."); - - std::string high_string = string.substr(1, dash_index - 1); - std::string low_string = string.substr(dash_index + 1, string.size() - dash_index - 2); - - if (high_string.size() == 0 || high_string.size() > 16 || low_string.size() == 0 || low_string.size() > 16) - throw std::invalid_argument("Invalid object ID."); - - auto isxdigit = static_cast(std::isxdigit); - if (!std::all_of(high_string.begin(), high_string.end(), isxdigit) || - !std::all_of(low_string.begin(), low_string.end(), isxdigit)) { - throw std::invalid_argument("Invalid object ID."); - } - return sync::ObjectID(strtoull(high_string.c_str(), nullptr, 16), strtoull(low_string.c_str(), nullptr, 16)); -} - -} // unnamed namespace -#endif // REALM_ENABLE_SYNC - template void RealmClass::object_for_object_id(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue& return_value) { args.validate_count(2); @@ -1045,7 +1015,7 @@ void RealmClass::object_for_object_id(ContextType ctx, ObjectType this_object validated_object_schema_for_value(ctx, realm, args[0], object_type); std::string object_id_string = Value::validated_to_string(ctx, args[1]); - auto object_id = object_id_from_string(object_id_string); + auto object_id = sync::ObjectID::from_string(object_id_string); const Group& group = realm->read_group(); size_t ndx = sync::row_for_object_id(group, *ObjectStore::table_for_object_type(group, object_type), object_id); From 4722e75eba64273fd7a68a69291f65150a964d6e Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 28 Feb 2018 12:45:58 -0800 Subject: [PATCH 34/61] Expose computed privileges --- src/js_realm.hpp | 77 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 72ce3b31..6fc662df 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -186,6 +186,7 @@ public: static void compact(ContextType, ObjectType, Arguments, ReturnValue &); static void delete_model(ContextType, ObjectType, Arguments, ReturnValue &); static void object_for_object_id(ContextType, ObjectType, Arguments, ReturnValue&); + static void privileges(ContextType, ObjectType, Arguments, ReturnValue&); // properties static void get_empty(ContextType, ObjectType, ReturnValue &); @@ -242,6 +243,7 @@ public: {"close", wrap}, {"compact", wrap}, {"deleteModel", wrap}, + {"privileges", wrap}, {"_objectForObjectId", wrap}, #if REALM_ENABLE_SYNC {"_waitForDownload", wrap}, @@ -291,7 +293,8 @@ public: return name; } - static const ObjectSchema& validated_object_schema_for_value(ContextType ctx, const SharedRealm &realm, const ValueType &value, std::string& object_type) { + static const ObjectSchema& validated_object_schema_for_value(ContextType ctx, const SharedRealm &realm, const ValueType &value) { + std::string object_type; if (Value::is_constructor(ctx, value)) { FunctionType constructor = Value::to_constructor(ctx, value); @@ -541,7 +544,7 @@ void RealmClass::constructor(ContextType ctx, ObjectType this_object, size_t template SharedRealm RealmClass::create_shared_realm(ContextType ctx, realm::Realm::Config config, bool schema_updated, - ObjectDefaultsMap && defaults, ConstructorMap && constructors) { + ObjectDefaultsMap&& defaults, ConstructorMap&& constructors) { config.execution_context = Context::get_execution_context_id(ctx); SharedRealm realm; @@ -551,9 +554,6 @@ SharedRealm RealmClass::create_shared_realm(ContextType ctx, realm::Realm::Co catch (const RealmFileException& ex) { handleRealmFileException(ctx, config, ex); } - catch (...) { - throw; - } GlobalContextType global_context = Context::get_global_context(ctx); if (!realm->m_binding_context) { @@ -790,10 +790,8 @@ void RealmClass::objects(ContextType ctx, ObjectType this_object, Arguments a args.validate_maximum(1); SharedRealm realm = *get_internal>(this_object); - std::string object_type; - validated_object_schema_for_value(ctx, realm, args[0], object_type); - - return_value.set(ResultsClass::create_instance(ctx, realm, object_type)); + auto& object_schema = validated_object_schema_for_value(ctx, realm, args[0]); + return_value.set(ResultsClass::create_instance(ctx, realm, object_schema.name)); } template @@ -802,7 +800,7 @@ void RealmClass::object_for_primary_key(ContextType ctx, ObjectType this_obje SharedRealm realm = *get_internal>(this_object); std::string object_type; - auto &object_schema = validated_object_schema_for_value(ctx, realm, args[0], object_type); + auto &object_schema = validated_object_schema_for_value(ctx, realm, args[0]); NativeAccessor accessor(ctx, realm, object_schema); auto realm_object = realm::Object::get_for_primary_key(accessor, realm, object_schema, args[1]); @@ -820,8 +818,7 @@ void RealmClass::create(ContextType ctx, ObjectType this_object, Arguments ar SharedRealm realm = *get_internal>(this_object); realm->verify_open(); - std::string object_type; - auto &object_schema = validated_object_schema_for_value(ctx, realm, args[0], object_type); + auto &object_schema = validated_object_schema_for_value(ctx, realm, args[0]); ObjectType object = Value::validated_to_object(ctx, args[1], "properties"); if (Value::is_array(ctx, args[1])) { @@ -1011,21 +1008,67 @@ void RealmClass::object_for_object_id(ContextType ctx, ObjectType this_object if (!sync::has_object_ids(realm->read_group())) throw std::logic_error("Realm._objectForObjectId() can only be used with synced Realms."); - std::string object_type = Value::validated_to_string(ctx, args[0]); - validated_object_schema_for_value(ctx, realm, args[0], object_type); - + auto& object_schema = validated_object_schema_for_value(ctx, realm, args[0]); std::string object_id_string = Value::validated_to_string(ctx, args[1]); auto object_id = sync::ObjectID::from_string(object_id_string); const Group& group = realm->read_group(); - size_t ndx = sync::row_for_object_id(group, *ObjectStore::table_for_object_type(group, object_type), object_id); + size_t ndx = sync::row_for_object_id(group, *ObjectStore::table_for_object_type(group, object_schema.name), object_id); if (ndx != realm::npos) { - return_value.set(RealmObjectClass::create_instance(ctx, realm::Object(realm, object_type, ndx))); + return_value.set(RealmObjectClass::create_instance(ctx, realm::Object(realm, object_schema.name, ndx))); } #else throw std::logic_error("Realm._objectForObjectId() can only be used with synced Realms."); #endif // REALM_ENABLE_SYNC } +template +void RealmClass::privileges(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); + + using Privilege = realm::ComputedPrivileges; + auto has_privilege = [](Privilege actual, Privilege expected) { + return (static_cast(actual) & static_cast(expected)) == static_cast(expected); + }; + + SharedRealm realm = *get_internal>(this_object); + if (args.count == 0) { + auto p = realm->get_privileges(); + ObjectType object = Object::create_empty(ctx); + Object::set_property(ctx, object, "read", Value::from_boolean(ctx, has_privilege(p, Privilege::Read))); + Object::set_property(ctx, object, "update", Value::from_boolean(ctx,has_privilege(p, Privilege::Update))); + Object::set_property(ctx, object, "modifySchema", Value::from_boolean(ctx, has_privilege(p, Privilege::ModifySchema))); + Object::set_property(ctx, object, "setPermissions", Value::from_boolean(ctx, has_privilege(p, Privilege::SetPermissions))); + return_value.set(object); + return; + } + + if (Value::is_object(ctx, args[0])) { + auto arg = Value::to_object(ctx, args[0]); + if (Object::template is_instance>(ctx, arg)) { + auto obj = get_internal>(arg); + auto p = realm->get_privileges(obj->row()); + + ObjectType object = Object::create_empty(ctx); + Object::set_property(ctx, object, "read", Value::from_boolean(ctx, has_privilege(p, Privilege::Read))); + Object::set_property(ctx, object, "update", Value::from_boolean(ctx,has_privilege(p, Privilege::Update))); + Object::set_property(ctx, object, "delete", Value::from_boolean(ctx,has_privilege(p, Privilege::Delete))); + Object::set_property(ctx, object, "setPermissions", Value::from_boolean(ctx, has_privilege(p, Privilege::SetPermissions))); + return_value.set(object); + return; + } + } + + auto& object_schema = validated_object_schema_for_value(ctx, realm, args[0]); + auto p = realm->get_privileges(object_schema.name); + ObjectType object = Object::create_empty(ctx); + Object::set_property(ctx, object, "read", Value::from_boolean(ctx, has_privilege(p, Privilege::Read))); + Object::set_property(ctx, object, "update", Value::from_boolean(ctx,has_privilege(p, Privilege::Update))); + Object::set_property(ctx, object, "create", Value::from_boolean(ctx, has_privilege(p, Privilege::Create))); + Object::set_property(ctx, object, "subscribe", Value::from_boolean(ctx, has_privilege(p, Privilege::Query))); + Object::set_property(ctx, object, "setPermissions", Value::from_boolean(ctx, has_privilege(p, Privilege::SetPermissions))); + return_value.set(object); +} + } // js } // realm From ac7f7d9c4ec2cd520afb4708d43bf59ec0dc9a68 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 28 Feb 2018 14:56:00 -0800 Subject: [PATCH 35/61] Add schema definitions for the permissions types --- lib/extensions.js | 62 +++++++++++++++++++++++++++++++++++++++++++++++ lib/index.d.ts | 27 +++++++++++++++++++++ src/object-store | 2 +- 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/lib/extensions.js b/lib/extensions.js index 5c793e34..033d305c 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -167,6 +167,68 @@ module.exports = function(realmConstructor) { Complete: 1, // The subscription has been processed by the sync server and data is being synced to the device. Invalidated: 3, // The subscription has been removed. }; + + // Define the permission schemas as constructors so that they can be + // passed into directly to functions which want object type names + const permissionsSchema = Object.freeze({ + Class: function() {}, + Permission: function() {}, + Realm: function() {}, + Role: function() {}, + User: function() {}, + }); + permissionsSchema.Permission.schema = Object.freeze({ + name: '__Permission', + properties: { + role: '__Role', + canRead: {type: 'bool', default: false}, + canUpdate: {type: 'bool', default: false}, + canDelete: {type: 'bool', default: false}, + canSetPermissions: {type: 'bool', default: false}, + canQuery: {type: 'bool', default: false}, + canCreate: {type: 'bool', default: false}, + canModifySchema: {type: 'bool', default: false}, + } + }); + + permissionsSchema.User.schema = Object.freeze({ + name: '__User', + primaryKey: 'id', + properties: { + id: 'string' + } + }); + + permissionsSchema.Role.schema = Object.freeze({ + name: '__Role', + primaryKey: 'name', + properties: { + name: 'string', + members: '__User[]' + } + }); + + permissionsSchema.Class.schema = Object.freeze({ + name: '__Class', + primaryKey: 'class_name', + properties: { + class_name: 'string', + permissions: '__Permission[]' + } + }); + + permissionsSchema.Realm.schema = Object.freeze({ + name: '__Realm', + primaryKey: 'id', + properties: { + id: 'int', + permissions: '__Permission[]' + } + }); + Object.defineProperty(realmConstructor, 'Permissions', { + value: permissionsSchema, + configurable: false + }); } // TODO: Remove this now useless object. diff --git a/lib/index.d.ts b/lib/index.d.ts index 76cd5432..490b43fa 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -622,6 +622,33 @@ declare class Realm { compact(): boolean; } +declare namespace Realm.Permissions { + interface Permission { + identity: string; + canRead: boolean; + canUpdate: boolean; + canDelete: boolean; + canSetPermissions: boolean; + canQuery: boolean; + canCreate: boolean; + canModifySchema: boolean; + } + interface User { + identity: string; + } + interface Role { + name: string; + members: User[]; + } + interface Class { + class_name: string; + permissions: Permission[]; + } + interface Realm { + permissions: Permission[]; + } +} + declare module 'realm' { export = Realm } diff --git a/src/object-store b/src/object-store index bb559df9..39c133a9 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit bb559df9237ece49f9c889993f7c1aff619b48f9 +Subproject commit 39c133a9aff6feaa76e9631696bdf67bc57ed5d5 From 0047ffba13f6cb34e299123e370a947a7abc1c58 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 1 Mar 2018 11:54:56 -0800 Subject: [PATCH 36/61] Add object-level permissions tests --- src/js_realm.hpp | 1 - tests/js/asserts.js | 3 +- tests/js/permission-tests.js | 93 ++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 6fc662df..9e6da957 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -35,7 +35,6 @@ #include "js_sync.hpp" #include "sync/sync_config.hpp" #include "sync/sync_manager.hpp" -#include "sync/partial_sync.hpp" #endif #include "shared_realm.hpp" diff --git a/tests/js/asserts.js b/tests/js/asserts.js index ff01df46..154c4821 100644 --- a/tests/js/asserts.js +++ b/tests/js/asserts.js @@ -37,7 +37,8 @@ module.exports = { } else if (type === 'object') { for (const key of Object.keys(val1)) { - this.assertEqual(val1[key], val2[key], errorMessage, depth + 1); + const message = errorMessage ? `${errorMessage}: ${key}` : key; + this.assertEqual(val1[key], val2[key], message, depth + 1); } } else if (type === 'list') { diff --git a/tests/js/permission-tests.js b/tests/js/permission-tests.js index 4379e494..8b849005 100644 --- a/tests/js/permission-tests.js +++ b/tests/js/permission-tests.js @@ -57,6 +57,34 @@ function repeatUntil(fn, predicate) { return check; } +function subscribe(results) { + const subscription = results.subscribe() + return new Promise((resolve, reject) => { + subscription.addListener((subscription, state) => { + if (state == Realm.Sync.SubscriptionState.Complete) { + resolve(); + } + else if (state == Realm.Sync.SubscriptionState.Error) { + reject(); + } + }); + setTimeout(() => reject("listener never called"), 5000); + }); +} + +function waitForUpload(realm) { + let session = realm.syncSession; + return new Promise(resolve => { + let callback = (transferred, total) => { + if (transferred === total) { + session.removeProgressNotification(callback); + resolve(realm); + } + } + session.addProgressNotification('upload', 'forCurrentlyOutstandingWork', callback); + }); +} + module.exports = { testApplyAndGetGrantedPermissions() { return createUsersWithTestRealms(1) @@ -118,5 +146,70 @@ module.exports = { } }); }, + + testObjectPermissions() { + let config = (user, url) => { + return { + schema: [ + Realm.Permissions.Permission, + Realm.Permissions.User, + Realm.Permissions.Role, + { + name: 'Object', + properties: { + value: 'int', + permissions: '__Permission[]' + } + } + ], + sync: {user, url, partial: true} + }; + }; + let owner, otherUser + return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password') + .then(user => { + owner = user; + new Realm({sync: {user, url: 'realm://localhost:9080/~/test'}}).close(); + return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password') + }) + .then(user => { + otherUser = user; + return owner.applyPermissions({userId: otherUser.identity}, `/${owner.identity}/test`, 'read') + }) + .then(() => { + let realm = new Realm(config(owner, 'realm://localhost:9080/~/test')); + realm.write(() => { + let user = realm.create(Realm.Permissions.User, {id: otherUser.identity}) + let role = realm.create(Realm.Permissions.Role, {name: 'reader'}) + role.members.push(user) + + let obj1 = realm.create('Object', {value: 1}) + let obj2 = realm.create('Object', {value: 2}) + obj2.permissions.push(realm.create(Realm.Permissions.Permission, + {role: role, canRead: true, canUpdate: false})) + }); + return waitForUpload(realm).then(() => realm.close()); + }) + .then(() => Realm.open(config(otherUser, `realm://localhost:9080/${owner.identity}/test`))) + .then((realm) => subscribe(realm.objects('Object')).then(() => realm)) + .then((realm) => { + // Should have full access to the Realm as a whole + TestCase.assertSimilar('object', realm.privileges(), + {read: true, update: true, modifySchema: true, setPermissions: true}); + TestCase.assertSimilar('object', realm.privileges('Object'), + {read: true, update: true, create: true, subscribe: true, setPermissions: true}); + // Verify that checking via constructor works too + TestCase.assertSimilar('object', realm.privileges(Realm.Permissions.User), + {read: true, update: true, create: true, subscribe: true, setPermissions: true}); + + // Should only be able to see the second object + let results = realm.objects('Object') + TestCase.assertEqual(results.length, 1); + TestCase.assertEqual(results[0].value, 2); + TestCase.assertSimilar('object', realm.privileges(results[0]), + {read: true, update: false, delete: false, setPermissions: false}); + realm.close(); + }); + } } From 3934413180374e7d55fd909330573f2f4109dea6 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 2 Mar 2018 10:07:29 -0800 Subject: [PATCH 37/61] Update object store --- src/object-store | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/object-store b/src/object-store index 39c133a9..b250563e 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit 39c133a9aff6feaa76e9631696bdf67bc57ed5d5 +Subproject commit b250563ea1eb9f32ec7dbd76f2c6f8f1a26914cc From 61126aef5329dabf39e4c8f1926050c2db75b7d0 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 2 Mar 2018 10:47:04 -0800 Subject: [PATCH 38/61] Use node.execPath in runOutOfProcess() --- tests/js/session-tests.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index 24a47288..a85410fa 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -61,27 +61,23 @@ function copyFileToTempDir(filename) { return tmpFile.name; } -function runOutOfProcess(nodeJsFilePath) { - var nodeArgs = Array.prototype.slice.call(arguments); +function runOutOfProcess() { + const args = Array.prototype.slice.call(arguments); let tmpDir = tmp.dirSync(); - let content = fs.readFileSync(nodeJsFilePath, 'utf8'); - let tmpFile = tmp.fileSync({ dir: tmpDir.name }); - fs.appendFileSync(tmpFile.fd, content, { encoding: 'utf8' }); - nodeArgs[0] = tmpFile.name; + console.log(`runOutOfProcess : ${args.join(' ')}`); return new Promise((resolve, reject) => { try { - console.log('runOutOfProcess command\n node ' + nodeArgs.join(" ")); - const child = execFile('node', nodeArgs, { cwd: tmpDir.name }, (error, stdout, stderr) => { + execFile(process.execPath, args, {cwd: tmpDir.name}, (error, stdout, stderr) => { if (error) { - console.error("runOutOfProcess failed\n" + error); - reject(new Error(`Running ${nodeJsFilePath} failed. error: ${error}`)); + console.error("runOutOfProcess failed\n", error); + reject(new Error(`Running ${modulePath} failed. error: ${error}`)); return; } console.log('runOutOfProcess success\n' + stdout); resolve(); }); - } + } catch (e) { reject(e); } From 302db024bfe8b8bb50fda3381ff624731e0d0839 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 2 Mar 2018 12:05:09 -0800 Subject: [PATCH 39/61] Eliminate some gratuitous promise nesting in tests --- tests/js/session-tests.js | 628 ++++++++++++++++++-------------------- 1 file changed, 299 insertions(+), 329 deletions(-) diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index a85410fa..1ef8a21c 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -271,43 +271,40 @@ module.exports = { const expectedObjectsCount = 3; return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - return new Promise((resolve, reject) => { - const accessTokenRefreshed = this; - let successCounter = 0; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + const accessTokenRefreshed = this; + let successCounter = 0; - let config = { - sync: { user, url: `realm://localhost:9080/~/${realmName}` } - }; + let config = { + sync: { user, url: `realm://localhost:9080/~/${realmName}` } + }; + return new Promise((resolve, reject) => { + Realm.openAsync(config, (error, realm) => { + if (error) { + reject(error); + return; + } + try { + let actualObjectsCount = realm.objects('Dog').length; + TestCase.assertEqual(actualObjectsCount, expectedObjectsCount, "Synced realm does not contain the expected objects count"); - Realm.openAsync(config, (error, realm) => { - try { - if (error) { - reject(error); - } + let firstDog = realm.objects('Dog')[0]; + TestCase.assertTrue(({}).hasOwnProperty.call(firstDog, 'name'), "Synced realm does not have an inffered schema"); + TestCase.assertTrue(firstDog.name, "Synced realm object's property should have a value"); + TestCase.assertTrue(firstDog.name.indexOf('Lassy') !== -1, "Synced realm object's property should contain the actual written value"); - let actualObjectsCount = realm.objects('Dog').length; - TestCase.assertEqual(actualObjectsCount, expectedObjectsCount, "Synced realm does not contain the expected objects count"); - - let firstDog = realm.objects('Dog')[0]; - TestCase.assertTrue(({}).hasOwnProperty.call(firstDog, 'name'), "Synced realm does not have an inffered schema"); - TestCase.assertTrue(firstDog.name, "Synced realm object's property should have a value"); - TestCase.assertTrue(firstDog.name.indexOf('Lassy') !== -1, "Synced realm object's property should contain the actual written value"); - - - const session = realm.syncSession; - TestCase.assertInstanceOf(session, Realm.Sync.Session); - TestCase.assertEqual(session.user.identity, user.identity); - TestCase.assertEqual(session.config.url, config.sync.url); - TestCase.assertEqual(session.config.user.identity, config.sync.user.identity); - TestCase.assertEqual(session.state, 'active'); - resolve(); - } - catch (e) { - reject(e); - } - }); + const session = realm.syncSession; + TestCase.assertInstanceOf(session, Realm.Sync.Session); + TestCase.assertEqual(session.user.identity, user.identity); + TestCase.assertEqual(session.config.url, config.sync.url); + TestCase.assertEqual(session.config.user.identity, config.sync.user.identity); + TestCase.assertEqual(session.state, 'active'); + resolve(); + } + catch (e) { + reject(e); + } }); }); }); @@ -317,26 +314,22 @@ module.exports = { const username = uuid(); const expectedObjectsCount = 3; + const accessTokenRefreshed = this; + let successCounter = 0; - return new Promise((resolve, reject) => { - const accessTokenRefreshed = this; - let successCounter = 0; + let config = { + schema: [{ name: 'Dog', properties: { name: 'string' } }], + }; - let config = { - schema: [{ name: 'Dog', properties: { name: 'string' } }], - }; + return Realm.open(config).then(realm => { + realm.write(() => { + for (let i = 1; i <= 3; i++) { + realm.create('Dog', { name: `Lassy ${i}` }); + } + }); - Realm.open(config).then(realm => { - realm.write(() => { - for (let i = 1; i <= 3; i++) { - realm.create('Dog', { name: `Lassy ${i}` }); - } - }); - - let actualObjectsCount = realm.objects('Dog').length; - TestCase.assertEqual(actualObjectsCount, expectedObjectsCount, "Local realm does not contain the expected objects count"); - resolve(); - }).catch(error => reject(error)); + let actualObjectsCount = realm.objects('Dog').length; + TestCase.assertEqual(actualObjectsCount, expectedObjectsCount, "Local realm does not contain the expected objects count"); }); }, @@ -408,37 +401,33 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/nested-list-helper.js', __dirname + '/schemas.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - return new Promise((resolve, reject) => { - let config = { - schema: [schemas.ParentObject, schemas.NameObject], - sync: { user, url: `realm://localhost:9080/~/${realmName}` } - }; - Realm.open(config).then(realm => { - let objects = realm.objects('ParentObject'); + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + schema: [schemas.ParentObject, schemas.NameObject], + sync: { user, url: `realm://localhost:9080/~/${realmName}` } + }; + return Realm.open(config) + }).then(realm => { + let objects = realm.objects('ParentObject'); - let json = JSON.stringify(objects); - TestCase.assertEqual(json, '{"0":{"id":1,"name":{"0":{"family":"Larsen","given":{"0":"Hans","1":"Jørgen"},"prefix":{}},"1":{"family":"Hansen","given":{"0":"Ib"},"prefix":{}}}},"1":{"id":2,"name":{"0":{"family":"Petersen","given":{"0":"Gurli","1":"Margrete"},"prefix":{}}}}}'); - TestCase.assertEqual(objects.length, 2); - TestCase.assertEqual(objects[0].name.length, 2); - TestCase.assertEqual(objects[0].name[0].given.length, 2); - TestCase.assertEqual(objects[0].name[0].prefix.length, 0); - TestCase.assertEqual(objects[0].name[0].given[0], 'Hans'); - TestCase.assertEqual(objects[0].name[0].given[1], 'Jørgen') - TestCase.assertEqual(objects[0].name[1].given.length, 1); - TestCase.assertEqual(objects[0].name[1].given[0], 'Ib'); - TestCase.assertEqual(objects[0].name[1].prefix.length, 0); + let json = JSON.stringify(objects); + TestCase.assertEqual(json, '{"0":{"id":1,"name":{"0":{"family":"Larsen","given":{"0":"Hans","1":"Jørgen"},"prefix":{}},"1":{"family":"Hansen","given":{"0":"Ib"},"prefix":{}}}},"1":{"id":2,"name":{"0":{"family":"Petersen","given":{"0":"Gurli","1":"Margrete"},"prefix":{}}}}}'); + TestCase.assertEqual(objects.length, 2); + TestCase.assertEqual(objects[0].name.length, 2); + TestCase.assertEqual(objects[0].name[0].given.length, 2); + TestCase.assertEqual(objects[0].name[0].prefix.length, 0); + TestCase.assertEqual(objects[0].name[0].given[0], 'Hans'); + TestCase.assertEqual(objects[0].name[0].given[1], 'Jørgen') + TestCase.assertEqual(objects[0].name[1].given.length, 1); + TestCase.assertEqual(objects[0].name[1].given[0], 'Ib'); + TestCase.assertEqual(objects[0].name[1].prefix.length, 0); - TestCase.assertEqual(objects[1].name.length, 1); - TestCase.assertEqual(objects[1].name[0].given.length, 2); - TestCase.assertEqual(objects[1].name[0].prefix.length, 0); - TestCase.assertEqual(objects[1].name[0].given[0], 'Gurli'); - TestCase.assertEqual(objects[1].name[0].given[1], 'Margrete'); - resolve(); - }).catch(() => reject()); - }); - }); + TestCase.assertEqual(objects[1].name.length, 1); + TestCase.assertEqual(objects[1].name[0].given.length, 2); + TestCase.assertEqual(objects[1].name[0].prefix.length, 0); + TestCase.assertEqual(objects[1].name[0].given[0], 'Gurli'); + TestCase.assertEqual(objects[1].name[0].given[1], 'Margrete'); }); }, @@ -606,60 +595,59 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - return new Promise((resolve, reject) => { - let config = { - sync: { - user, - url: `realm://localhost:9080/~/${realmName}` - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }], - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + sync: { + user, + url: `realm://localhost:9080/~/${realmName}` + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }], + }; - let realm = new Realm(config); - let unregisterFunc; + let realm = new Realm(config); + let unregisterFunc; - let writeDataFunc = () => { - realm.write(() => { - for (let i = 1; i <= 3; i++) { - realm.create('Dog', { name: `Lassy ${i}` }); - } - }); + let writeDataFunc = () => { + realm.write(() => { + for (let i = 1; i <= 3; i++) { + realm.create('Dog', { name: `Lassy ${i}` }); + } + }); + } + + return new Promise((resolve, reject) => { + let syncFinished = false; + let failOnCall = false; + const progressCallback = (transferred, total) => { + if (failOnCall) { + reject(new Error("Progress callback should not be called after removeProgressNotification")); } - let syncFinished = false; - let failOnCall = false; - const progressCallback = (transferred, total) => { - if (failOnCall) { - reject(new Error("Progress callback should not be called after removeProgressNotification")); - } + syncFinished = transferred === total; - syncFinished = transferred === total; + //unregister and write some new data. + if (syncFinished) { + failOnCall = true; + unregisterFunc(); - //unregister and write some new data. - if (syncFinished) { - failOnCall = true; - unregisterFunc(); + //use second callback to wait for sync finished + realm.syncSession.addProgressNotification('upload', 'reportIndefinitely', (transferred, transferable) => { + if (transferred === transferable) { + resolve(); + } + }); + writeDataFunc(); + } + }; - //use second callback to wait for sync finished - realm.syncSession.addProgressNotification('upload', 'reportIndefinitely', (transferred, transferable) => { - if (transferred === transferable) { - resolve(); - } - }); - writeDataFunc(); - } - }; + realm.syncSession.addProgressNotification('upload', 'reportIndefinitely', progressCallback); - realm.syncSession.addProgressNotification('upload', 'reportIndefinitely', progressCallback); + unregisterFunc = () => { + realm.syncSession.removeProgressNotification(progressCallback); + }; - unregisterFunc = () => { - realm.syncSession.removeProgressNotification(progressCallback); - }; - - writeDataFunc(); - }); + writeDataFunc(); }); }); }, @@ -671,36 +659,24 @@ module.exports = { const username = uuid(); const realmName = uuid(); + let progressCalled = false; return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - return new Promise((resolve, reject) => { - let config = { - sync: { - user, - url: `realm://localhost:9080/~/${realmName}` - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }], - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + sync: { + user, + url: `realm://localhost:9080/~/${realmName}` + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }], + }; - let progressCalled = false; - Realm.open(config) - .progress((transferred, total) => { - progressCalled = true; - }) - .then(() => { - TestCase.assertTrue(progressCalled); - resolve(); - }) - .catch((e) => reject(e)); - - setTimeout(function() { - reject("Progress Notifications API failed to call progress callback for Realm constructor"); - }, 5000); - }); - }); - }); + return Promise.race([ + Realm.open(config).progress((transferred, total) => { progressCalled = true; }), + new Promise((_, reject) => setTimeout(() => reject("Progress Notifications API failed to call progress callback for Realm constructor"), 5000)) + ]); + }).then(() => TestCase.assertTrue(progressCalled)); }, testProgressNotificationsForRealmOpenAsync() { @@ -712,37 +688,36 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - return new Promise((resolve, reject) => { - let config = { - sync: { - user, - url: `realm://localhost:9080/~/${realmName}` - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }], - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + return new Promise((resolve, reject) => { + let config = { + sync: { + user, + url: `realm://localhost:9080/~/${realmName}` + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }], + }; - let progressCalled = false; + let progressCalled = false; - Realm.openAsync(config, - (error, realm) => { - if (error) { - reject(error); - return; - } + Realm.openAsync(config, + (error, realm) => { + if (error) { + reject(error); + return; + } - TestCase.assertTrue(progressCalled); - resolve(); - }, - (transferred, total) => { - progressCalled = true; - }); + TestCase.assertTrue(progressCalled); + resolve(); + }, + (transferred, total) => { + progressCalled = true; + }); - setTimeout(function() { - reject("Progress Notifications API failed to call progress callback for Realm constructor"); - }, 5000); - }); + setTimeout(function() { + reject("Progress Notifications API failed to call progress callback for Realm constructor"); + }, 5000); }); }); }, @@ -757,38 +732,37 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - let config = { - sync: { - user: user, - url: `realm://localhost:9080/~/${realmName}`, - partial: true, - error: (session, error) => console.log(error) - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }] - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: true, + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; - Realm.deleteFile(config); - const realm = new Realm(config); - TestCase.assertEqual(realm.objects('Dog').length, 0); - var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); - var subscription = results.subscribe(); - TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); - return new Promise((resolve, reject) => { - subscription.addListener((subscription, state) => { - if (state == Realm.Sync.SubscriptionState.Complete) { - TestCase.assertEqual(results.length, 1); - TestCase.assertTrue(results[0].name === 'Lassy 1', "The object is not synced correctly"); - resolve(); - } - }); - setTimeout(function() { - reject("listener never called"); - }, 5000); + Realm.deleteFile(config); + const realm = new Realm(config); + TestCase.assertEqual(realm.objects('Dog').length, 0); + var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var subscription = results.subscribe(); + TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); + return new Promise((resolve, reject) => { + subscription.addListener((subscription, state) => { + if (state == Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(results.length, 1); + TestCase.assertTrue(results[0].name === 'Lassy 1', "The object is not synced correctly"); + resolve(); + } }); - }) - }) + setTimeout(function() { + reject("listener never called"); + }, 5000); + }); + }); }, testPartialSyncAnonymous_ResultsListener() { @@ -801,38 +775,37 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - let config = { - sync: { - user: user, - url: `realm://localhost:9080/~/${realmName}`, - partial: true, - error: (session, error) => console.log(error) - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }] - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: true, + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; - Realm.deleteFile(config); - const realm = new Realm(config); - TestCase.assertEqual(realm.objects('Dog').length, 0); - var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); - var subscription = results.subscribe(); - TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); - return new Promise((resolve, reject) => { - results.addListener((collection, changes) => { - if (subscription.state === Realm.Sync.SubscriptionState.Complete) { - TestCase.assertEqual(collection.length, 1); - TestCase.assertTrue(collection[0].name === 'Lassy 1', "The object is not synced correctly"); - resolve(); - } - }); - setTimeout(function() { - reject("listener never called"); - }, 5000); + Realm.deleteFile(config); + const realm = new Realm(config); + TestCase.assertEqual(realm.objects('Dog').length, 0); + var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var subscription = results.subscribe(); + TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); + return new Promise((resolve, reject) => { + results.addListener((collection, changes) => { + if (subscription.state === Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(collection.length, 1); + TestCase.assertTrue(collection[0].name === 'Lassy 1', "The object is not synced correctly"); + resolve(); + } }); - }) - }) + setTimeout(function() { + reject("listener never called"); + }, 5000); + }); + }); }, testPartialSyncMultipleSubscriptions() { @@ -845,56 +818,55 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - let config = { - sync: { - user: user, - url: `realm://localhost:9080/~/${realmName}`, - partial: true, - error: (session, error) => console.log(error) - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }] - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: true, + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; - Realm.deleteFile(config); - const realm = new Realm(config); - TestCase.assertEqual(realm.objects('Dog').length, 0); - var results1 = realm.objects('Dog').filtered("name == 'Lassy 1'"); - var results2 = realm.objects('Dog').filtered("name == 'Lassy 2'"); - var subscription1 = results1.subscribe(); - var subscription2 = results2.subscribe(); + Realm.deleteFile(config); + const realm = new Realm(config); + TestCase.assertEqual(realm.objects('Dog').length, 0); + var results1 = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var results2 = realm.objects('Dog').filtered("name == 'Lassy 2'"); + var subscription1 = results1.subscribe(); + var subscription2 = results2.subscribe(); - return new Promise((resolve, reject) => { - let called1 = false; - let called2 = false; - results1.addListener((collection, changeset) => { - if (subscription1.state == Realm.Sync.SubscriptionState.Complete) { - TestCase.assertEqual(collection.length, 1); - TestCase.assertTrue(collection[0].name === 'Lassy 1', "The object is not synced correctly"); - called1 = true; - if (called1 && called2) { - resolve(); - } + return new Promise((resolve, reject) => { + let called1 = false; + let called2 = false; + results1.addListener((collection, changeset) => { + if (subscription1.state == Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(collection.length, 1); + TestCase.assertTrue(collection[0].name === 'Lassy 1', "The object is not synced correctly"); + called1 = true; + if (called1 && called2) { + resolve(); } - }); - results2.addListener((collection, changeset) => { - if (subscription2.state == Realm.Sync.SubscriptionState.Complete) { - TestCase.assertEqual(collection.length, 1); - TestCase.assertTrue(collection[0].name === 'Lassy 2', "The object is not synced correctly"); - called2 = true; - if (called1 && called2) { - resolve(); - } - } - }); - - setTimeout(function() { - reject("listener never called"); - }, 5000); + } }); - }) - }) + results2.addListener((collection, changeset) => { + if (subscription2.state == Realm.Sync.SubscriptionState.Complete) { + TestCase.assertEqual(collection.length, 1); + TestCase.assertTrue(collection[0].name === 'Lassy 2', "The object is not synced correctly"); + called2 = true; + if (called1 && called2) { + resolve(); + } + } + }); + + setTimeout(function() { + reject("listener never called"); + }, 5000); + }); + }); }, testPartialSyncFailing() { @@ -907,23 +879,22 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - let config = { - sync: { - user: user, - url: `realm://localhost:9080/~/${realmName}`, - partial: false, // <---- calling subscribe should fail - error: (session, error) => console.log(error) - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }] - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: false, // <---- calling subscribe should fail + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; - Realm.deleteFile(config); - const realm = new Realm(config); - TestCase.assertEqual(realm.objects('Dog').length, 0); - TestCase.assertThrows(function () { var subscription = realm.objects('Dog').filtered("name == 'Lassy 1'").subscribe(); } ); - }); + Realm.deleteFile(config); + const realm = new Realm(config); + TestCase.assertEqual(realm.objects('Dog').length, 0); + TestCase.assertThrows(function () { var subscription = realm.objects('Dog').filtered("name == 'Lassy 1'").subscribe(); } ); }); }, @@ -937,36 +908,35 @@ module.exports = { const realmName = uuid(); return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) - .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { - let config = { - sync: { - user: user, - url: `realm://localhost:9080/~/${realmName}`, - partial: true, - error: (session, error) => console.log(error) - }, - schema: [{ name: 'Dog', properties: { name: 'string' } }] - }; + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(user => { + let config = { + sync: { + user: user, + url: `realm://localhost:9080/~/${realmName}`, + partial: true, + error: (session, error) => console.log(error) + }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; - Realm.deleteFile(config); - const realm = new Realm(config); - var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); - var subscription = results.subscribe(); - TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); - return new Promise((resolve, reject) => { - results.addListener((collection, changes) => { - if (subscription.state === Realm.Sync.SubscriptionState.Complete) { - subscription.unsubscribe(); - } - if (subscription.state === Realm.Sync.SubscriptionState.Invalidated) { - resolve(); - } - }); - setTimeout(function() { - reject("listener never called"); - }, 5000); + Realm.deleteFile(config); + const realm = new Realm(config); + var results = realm.objects('Dog').filtered("name == 'Lassy 1'"); + var subscription = results.subscribe(); + TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating); + return new Promise((resolve, reject) => { + results.addListener((collection, changes) => { + if (subscription.state === Realm.Sync.SubscriptionState.Complete) { + subscription.unsubscribe(); + } + if (subscription.state === Realm.Sync.SubscriptionState.Invalidated) { + resolve(); + } }); + setTimeout(function() { + reject("listener never called"); + }, 5000); }); }); }, From 5ff5df97be9e87e3c478de720aab7c8d97dc8022 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 2 Mar 2018 12:06:00 -0800 Subject: [PATCH 40/61] Fix running nested-list-helper.js with node 8 --- tests/js/nested-list-helper.js | 2 ++ tests/js/session-tests.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/js/nested-list-helper.js b/tests/js/nested-list-helper.js index e4b0803c..7de46e5e 100644 --- a/tests/js/nested-list-helper.js +++ b/tests/js/nested-list-helper.js @@ -9,6 +9,8 @@ const realmName = process.argv[4]; const realmModule = process.argv[5]; const Realm = require(realmModule); +// Ensure that schemas.js gets the correct module with `require('realm')` +require.cache[require.resolve('realm')] = require.cache[require.resolve(realmModule)]; let schemas = require(process.argv[2]); function createObjects(user) { diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index 1ef8a21c..ee01c3c6 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -69,8 +69,8 @@ function runOutOfProcess() { try { execFile(process.execPath, args, {cwd: tmpDir.name}, (error, stdout, stderr) => { if (error) { - console.error("runOutOfProcess failed\n", error); - reject(new Error(`Running ${modulePath} failed. error: ${error}`)); + console.error("runOutOfProcess failed\n", error, stdout, stderr); + reject(new Error(`Running ${args[0]} failed. error: ${error}`)); return; } From 384f43c6379e74a54cb45148f9c0e075adf6aeed Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 2 Mar 2018 12:13:59 -0800 Subject: [PATCH 41/61] Make testOfferPermissions more reliable The order of the permissions is undefined and will vary based on what order things happen to resolve on the server. --- tests/js/permission-tests.js | 39 +++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/tests/js/permission-tests.js b/tests/js/permission-tests.js index 8b849005..b92fb031 100644 --- a/tests/js/permission-tests.js +++ b/tests/js/permission-tests.js @@ -85,18 +85,31 @@ function waitForUpload(realm) { }); } +function permissionForPath(permissions, path) { + for (const permission of permissions) { + if (permission.path == path) { + return permission; + } + } +} + module.exports = { testApplyAndGetGrantedPermissions() { return createUsersWithTestRealms(1) .then(([user]) => { - return user.applyPermissions({ userId: `${user.identity}` }, `/${user.identity}/test`, 'read') + const path = `/${user.identity}/test`; + return user.applyPermissions({userId: `${user.identity}`}, `/${user.identity}/test`, 'read') .then(repeatUntil(() => user.getGrantedPermissions('any'), - permissions => permissions.length > 1)) + permissions => { + let permission = permissionForPath(permissions, path); + return permission && !permission.mayWrite; + })) .then(permissions => { - TestCase.assertEqual(permissions[0].path, `/${user.identity}/test`); - TestCase.assertEqual(permissions[0].mayRead, true); - TestCase.assertEqual(permissions[0].mayWrite, false); - TestCase.assertEqual(permissions[0].mayManage, false); + let permission = permissionForPath(permissions, path); + TestCase.assertDefined(permission); + TestCase.assertEqual(permission.mayRead, true); + TestCase.assertEqual(permission.mayWrite, false); + TestCase.assertEqual(permission.mayManage, false); }); }); }, @@ -104,19 +117,21 @@ module.exports = { testOfferPermissions() { return createUsersWithTestRealms(2) .then(([user1, user2]) => { + const path = `/${user1.identity}/test`; return user1.offerPermissions(`/${user1.identity}/test`, 'read') .then(token => user2.acceptPermissionOffer(token)) .then(realmUrl => { - TestCase.assertEqual(realmUrl, `/${user1.identity}/test`); + TestCase.assertEqual(realmUrl, path); return realmUrl; }) .then(repeatUntil(() => user2.getGrantedPermissions('any'), - permissions => permissions.length > 1)) + permissions => permissions.length > 2 && permissionForPath(permissions, path))) .then(permissions => { - TestCase.assertEqual(permissions[2].path, `/${user1.identity}/test`); - TestCase.assertEqual(permissions[2].mayRead, true); - TestCase.assertEqual(permissions[2].mayWrite, false); - TestCase.assertEqual(permissions[2].mayManage, false); + let permission = permissionForPath(permissions, path) + TestCase.assertDefined(permission); + TestCase.assertEqual(permission.mayRead, true); + TestCase.assertEqual(permission.mayWrite, false); + TestCase.assertEqual(permission.mayManage, false); }); }); }, From fadd35bb262c0632475cf00a4ac57970fa119714 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 2 Mar 2018 15:08:06 -0800 Subject: [PATCH 42/61] Add a typescript declaration for Sync.SubscriptionState --- lib/index.d.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/index.d.ts b/lib/index.d.ts index 490b43fa..3358b83d 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -406,7 +406,7 @@ declare namespace Realm.Sync { * @see { @link https://realm.io/docs/javascript/latest/api/Realm.Sync.Subscription.html } */ class Subscription { - readonly state: number; + readonly state: SubscriptionState; readonly error: string; unsubscribe(): void; @@ -414,6 +414,14 @@ declare namespace Realm.Sync { removeListener(subscruptionCallback: SubscriptionNotificationCallback): void; } + enum SubscriptionState { + Error, + Creating, + Pending, + Complete, + Invalidated, + } + /** * AuthError * @see { @link https://realm.io/docs/javascript/latest/api/Realm.Sync.AuthError.html } From d1a4e899d87e4bf900fbdb29fc45c8713fa0b132 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 2 Mar 2018 15:21:43 -0800 Subject: [PATCH 43/61] Fix the Permissions typescript definitions --- lib/index.d.ts | 69 +++++++++++++++++++++--------------- tests/js/permission-tests.js | 2 +- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/lib/index.d.ts b/lib/index.d.ts index 3358b83d..21f0e93a 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -81,7 +81,7 @@ declare namespace Realm { path?: string; readOnly?: boolean; inMemory?: boolean; - schema?: ObjectClass[] | ObjectSchema[]; + schema?: (ObjectClass | ObjectSchema)[]; schemaVersion?: number; sync?: Realm.Sync.SyncConfiguration; deleteRealmIfMigrationNeeded?: boolean; @@ -483,6 +483,42 @@ declare namespace Realm.Sync { } } +declare namespace Realm.Permissions { + class Permission { + static schema: ObjectSchema; + + identity: string; + canRead: boolean; + canUpdate: boolean; + canDelete: boolean; + canSetPermissions: boolean; + canQuery: boolean; + canCreate: boolean; + canModifySchema: boolean; + } + + class User { + static schema: ObjectSchema; + identity: string; + } + + class Role { + static schema: ObjectSchema; + name: string; + members: User[]; + } + + class Class { + static schema: ObjectSchema; + class_name: string; + permissions: Permission[]; + } + + class Realm { + static schema: ObjectSchema; + permissions: Permission[]; + } +} interface ProgressPromise extends Promise { progress(callback: Realm.Sync.ProgressNotificationCallback): Promise @@ -552,7 +588,7 @@ declare class Realm { * @param {boolean} update? * @returns T */ - create(type: string | Realm.ObjectClass | Function, properties: T & Realm.ObjectPropsType, update?: boolean): T; + create(type: string | Realm.ObjectClass | Function, properties: T | Realm.ObjectPropsType, update?: boolean): T; /** * @param {Realm.Object|Realm.Object[]|Realm.List|Realm.Results|any} object @@ -628,33 +664,10 @@ declare class Realm { * @returns boolean */ compact(): boolean; -} -declare namespace Realm.Permissions { - interface Permission { - identity: string; - canRead: boolean; - canUpdate: boolean; - canDelete: boolean; - canSetPermissions: boolean; - canQuery: boolean; - canCreate: boolean; - canModifySchema: boolean; - } - interface User { - identity: string; - } - interface Role { - name: string; - members: User[]; - } - interface Class { - class_name: string; - permissions: Permission[]; - } - interface Realm { - permissions: Permission[]; - } + privileges() : Realm.Permissions.Realm; + privileges(objectType: string | Realm.ObjectSchema | Function) : Realm.Permissions.Class; + privileges(obj: Realm.Object) : Realm.Permissions.Class; } declare module 'realm' { diff --git a/tests/js/permission-tests.js b/tests/js/permission-tests.js index b92fb031..8378dfbc 100644 --- a/tests/js/permission-tests.js +++ b/tests/js/permission-tests.js @@ -147,7 +147,7 @@ module.exports = { .then(t => { token = t; return user1.invalidatePermissionOffer(token); }) // Since we don't yet support notification when the invalidation has gone through, // wait for a bit and hope the server is done processing. - .then(wait(100)) + .then(() => wait(100)) .then(() => user2.acceptPermissionOffer(token)) // We want the call to fail, i.e. the catch() below should be called. .then(() => { throw new Error("User was able to accept an invalid permission offer token"); }) From 1f2173b5999297e082da518e5d3631b3db21fc9b Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Sat, 3 Mar 2018 12:55:11 +0100 Subject: [PATCH 44/61] Avoid Table::clear() when using partial sync (#1680) * Avoid Table::clear() when using partial sync * Updated to sync 3.0.0-beta.10 * Update object store --- CHANGELOG.md | 4 ++-- src/js_realm.hpp | 8 +++++++- src/object-store | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8c427cf..384a78f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,8 +18,8 @@ * [Sync] Added class `Realm.Sync.Subscription` to support partial synced Realms. ### Internal -* Updated to Realm Core 5.8.0. -* Updated to Realm Sync 3.0.0-beta.9. +* Updated to Realm Core 5.3.0. +* Updated to Realm Sync 3.0.0-beta.10. * Tested against Realm Object Server 3.0.0-alpha.8. diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 961032a7..2d0a8d57 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -898,7 +898,13 @@ void RealmClass::delete_all(ContextType ctx, ObjectType this_object, Argument } for (auto objectSchema : realm->schema()) { - ObjectStore::table_for_object_type(realm->read_group(), objectSchema.name)->clear(); + auto table = ObjectStore::table_for_object_type(realm->read_group(), objectSchema.name); + if (realm->is_partial()) { + realm::Results(realm, *table).clear(); + } + else { + table->clear(); + } } } diff --git a/src/object-store b/src/object-store index bb559df9..b250563e 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit bb559df9237ece49f9c889993f7c1aff619b48f9 +Subproject commit b250563ea1eb9f32ec7dbd76f2c6f8f1a26914cc From 4e4c16946f4d8fcf1a068cf590b5c8fcb3dbe745 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Sat, 3 Mar 2018 12:57:49 +0100 Subject: [PATCH 45/61] [2.3.0-beta.1] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 384a78f9..211d1c50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-3-1) +2.3.0 Release notes (2018-3-3) ============================================================= ### Breaking changes * [Sync] Sync protocol changed to version 24. diff --git a/dependencies.list b/dependencies.list index a87aea2f..1bff81a7 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-alpha.15 +VERSION=2.3.0-beta.1 REALM_CORE_VERSION=5.3.0 REALM_SYNC_VERSION=3.0.0-beta.10 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.8 diff --git a/package.json b/package.json index eedaa630..3dbfadaa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-alpha.15", + "version": "2.3.0-beta.1", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From 9da59d8f04734a3f1cf63242b89e5eee0ccb15ed Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Mon, 5 Mar 2018 09:46:24 -0800 Subject: [PATCH 46/61] Reformat permissions-tests.js to a consistent 4-space indent --- tests/js/permission-tests.js | 305 ++++++++++++++++++----------------- 1 file changed, 157 insertions(+), 148 deletions(-) diff --git a/tests/js/permission-tests.js b/tests/js/permission-tests.js index 8378dfbc..ccfe3f2b 100644 --- a/tests/js/permission-tests.js +++ b/tests/js/permission-tests.js @@ -16,33 +16,33 @@ // //////////////////////////////////////////////////////////////////////////// - 'use strict'; var Realm = require('realm'); var TestCase = require('./asserts'); function uuid() { - return '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); - }); + return '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); + }); } function createUsersWithTestRealms(count) { - const createUserWithTestRealm = () => { - return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password') - .then(user => { - new Realm({sync: {user, url: 'realm://localhost:9080/~/test'}}).close(); - return user; - }); - }; + const createUserWithTestRealm = () => { + return Realm.Sync.User + .register('http://localhost:9080', uuid(), 'password') + .then(user => { + new Realm({sync: {user, url: 'realm://localhost:9080/~/test'}}).close(); + return user; + }); + }; - return Promise.all(Array.from({length: count}, createUserWithTestRealm)); + return Promise.all(Array.from({length: count}, createUserWithTestRealm)); } function wait(t) { - return new Promise(resolve => setTimeout(resolve, t)); + return new Promise(resolve => setTimeout(resolve, t)); } function repeatUntil(fn, predicate) { @@ -58,18 +58,18 @@ function repeatUntil(fn, predicate) { } function subscribe(results) { - const subscription = results.subscribe() - return new Promise((resolve, reject) => { - subscription.addListener((subscription, state) => { - if (state == Realm.Sync.SubscriptionState.Complete) { - resolve(); - } - else if (state == Realm.Sync.SubscriptionState.Error) { - reject(); - } - }); - setTimeout(() => reject("listener never called"), 5000); - }); + const subscription = results.subscribe(); + return new Promise((resolve, reject) => { + subscription.addListener((subscription, state) => { + if (state == Realm.Sync.SubscriptionState.Complete) { + resolve(); + } + else if (state == Realm.Sync.SubscriptionState.Error) { + reject(); + } + }); + setTimeout(() => reject("listener never called"), 5000); + }); } function waitForUpload(realm) { @@ -80,151 +80,160 @@ function waitForUpload(realm) { session.removeProgressNotification(callback); resolve(realm); } - } + }; session.addProgressNotification('upload', 'forCurrentlyOutstandingWork', callback); }); } function permissionForPath(permissions, path) { - for (const permission of permissions) { - if (permission.path == path) { - return permission; + for (const permission of permissions) { + if (permission.path == path) { + return permission; + } } - } } module.exports = { testApplyAndGetGrantedPermissions() { - return createUsersWithTestRealms(1) - .then(([user]) => { - const path = `/${user.identity}/test`; - return user.applyPermissions({userId: `${user.identity}`}, `/${user.identity}/test`, 'read') - .then(repeatUntil(() => user.getGrantedPermissions('any'), - permissions => { - let permission = permissionForPath(permissions, path); - return permission && !permission.mayWrite; - })) - .then(permissions => { - let permission = permissionForPath(permissions, path); - TestCase.assertDefined(permission); - TestCase.assertEqual(permission.mayRead, true); - TestCase.assertEqual(permission.mayWrite, false); - TestCase.assertEqual(permission.mayManage, false); - }); + return createUsersWithTestRealms(1).then(([user]) => { + const path = `/${user.identity}/test`; + return user + .applyPermissions({userId: `${user.identity}`}, + `/${user.identity}/test`, 'read') + .then(repeatUntil(() => user.getGrantedPermissions('any'), + permissions => { + let permission = permissionForPath(permissions, path); + return permission && !permission.mayWrite; + })) + .then(permissions => { + let permission = permissionForPath(permissions, path); + TestCase.assertDefined(permission); + TestCase.assertEqual(permission.mayRead, true); + TestCase.assertEqual(permission.mayWrite, false); + TestCase.assertEqual(permission.mayManage, false); + }); }); }, testOfferPermissions() { - return createUsersWithTestRealms(2) - .then(([user1, user2]) => { - const path = `/${user1.identity}/test`; - return user1.offerPermissions(`/${user1.identity}/test`, 'read') - .then(token => user2.acceptPermissionOffer(token)) - .then(realmUrl => { - TestCase.assertEqual(realmUrl, path); - return realmUrl; - }) - .then(repeatUntil(() => user2.getGrantedPermissions('any'), - permissions => permissions.length > 2 && permissionForPath(permissions, path))) - .then(permissions => { - let permission = permissionForPath(permissions, path) - TestCase.assertDefined(permission); - TestCase.assertEqual(permission.mayRead, true); - TestCase.assertEqual(permission.mayWrite, false); - TestCase.assertEqual(permission.mayManage, false); - }); + return createUsersWithTestRealms(2).then(([user1, user2]) => { + const path = `/${user1.identity}/test`; + return user1.offerPermissions(`/${user1.identity}/test`, 'read') + .then(token => user2.acceptPermissionOffer(token)) + .then(realmUrl => { + TestCase.assertEqual(realmUrl, path); + return realmUrl; + }) + .then(repeatUntil(() => user2.getGrantedPermissions('any'), + permissions => permissions.length > 2 + && permissionForPath(permissions, path))) + .then(permissions => { + let permission = permissionForPath(permissions, path) + TestCase.assertDefined(permission); + TestCase.assertEqual(permission.mayRead, true); + TestCase.assertEqual(permission.mayWrite, false); + TestCase.assertEqual(permission.mayManage, false); + }); }); }, testInvalidatePermissionOffer() { - let user1, user2, token; - return createUsersWithTestRealms(2) - .then(users => { - user1 = users[0]; - user2 = users[1]; - return user1.offerPermissions(`/${user1.identity}/test`, 'read'); - }) - .then(t => { token = t; return user1.invalidatePermissionOffer(token); }) - // Since we don't yet support notification when the invalidation has gone through, - // wait for a bit and hope the server is done processing. - .then(() => wait(100)) - .then(() => user2.acceptPermissionOffer(token)) - // We want the call to fail, i.e. the catch() below should be called. - .then(() => { throw new Error("User was able to accept an invalid permission offer token"); }) - .catch(error => { - try { - TestCase.assertEqual(error.message, 'The permission offer is expired.'); - TestCase.assertEqual(error.statusCode, 701); - } - catch (e) { - throw new Error(e); - } - }); + let user1, user2, token; + return createUsersWithTestRealms(2) + .then(users => { + user1 = users[0]; + user2 = users[1]; + return user1.offerPermissions(`/${user1.identity}/test`, 'read'); + }) + .then(t => { + token = t; + return user1.invalidatePermissionOffer(token); + }) + // Since we don't yet support notification when the invalidation has + // gone through, wait for a bit and hope the server is done + // processing. + .then(() => wait(100)) + .then(() => user2.acceptPermissionOffer(token)) + // We want the call to fail, i.e. the catch() below should be + // called. + .then(() => { + throw new Error("User was able to accept an invalid permission offer token"); + }) + .catch(error => { + try { + TestCase.assertEqual(error.message, 'The permission offer is expired.'); + TestCase.assertEqual(error.statusCode, 701); + } + catch (e) { + throw new Error(e); + } + }); }, testObjectPermissions() { - let config = (user, url) => { - return { - schema: [ - Realm.Permissions.Permission, - Realm.Permissions.User, - Realm.Permissions.Role, - { - name: 'Object', - properties: { - value: 'int', - permissions: '__Permission[]' - } - } - ], - sync: {user, url, partial: true} + let config = (user, url) => { + return { + schema: [ + Realm.Permissions.Permission, + Realm.Permissions.User, + Realm.Permissions.Role, + { + name: 'Object', + properties: { + value: 'int', + permissions: '__Permission[]' + } + } + ], + sync: {user, url, partial: true} + }; }; - }; - let owner, otherUser - return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password') - .then(user => { - owner = user; - new Realm({sync: {user, url: 'realm://localhost:9080/~/test'}}).close(); - return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password') - }) - .then(user => { - otherUser = user; - return owner.applyPermissions({userId: otherUser.identity}, `/${owner.identity}/test`, 'read') - }) - .then(() => { - let realm = new Realm(config(owner, 'realm://localhost:9080/~/test')); - realm.write(() => { - let user = realm.create(Realm.Permissions.User, {id: otherUser.identity}) - let role = realm.create(Realm.Permissions.Role, {name: 'reader'}) - role.members.push(user) + let owner, otherUser + return Realm.Sync.User + .register('http://localhost:9080', uuid(), 'password') + .then(user => { + owner = user; + new Realm({sync: {user, url: 'realm://localhost:9080/~/test'}}).close(); + return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password') + }) + .then(user => { + otherUser = user; + return owner.applyPermissions({userId: otherUser.identity}, + `/${owner.identity}/test`, 'read') + }) + .then(() => { + let realm = new Realm(config(owner, 'realm://localhost:9080/~/test')); + realm.write(() => { + let user = realm.create(Realm.Permissions.User, {id: otherUser.identity}) + let role = realm.create(Realm.Permissions.Role, {name: 'reader'}) + role.members.push(user) - let obj1 = realm.create('Object', {value: 1}) - let obj2 = realm.create('Object', {value: 2}) - obj2.permissions.push(realm.create(Realm.Permissions.Permission, - {role: role, canRead: true, canUpdate: false})) - }); - return waitForUpload(realm).then(() => realm.close()); - }) - .then(() => Realm.open(config(otherUser, `realm://localhost:9080/${owner.identity}/test`))) - .then((realm) => subscribe(realm.objects('Object')).then(() => realm)) - .then((realm) => { - // Should have full access to the Realm as a whole - TestCase.assertSimilar('object', realm.privileges(), - {read: true, update: true, modifySchema: true, setPermissions: true}); - TestCase.assertSimilar('object', realm.privileges('Object'), - {read: true, update: true, create: true, subscribe: true, setPermissions: true}); - // Verify that checking via constructor works too - TestCase.assertSimilar('object', realm.privileges(Realm.Permissions.User), - {read: true, update: true, create: true, subscribe: true, setPermissions: true}); + let obj1 = realm.create('Object', {value: 1}); + let obj2 = realm.create('Object', {value: 2}); + obj2.permissions.push(realm.create(Realm.Permissions.Permission, + {role: role, canRead: true, canUpdate: false})) + }); + return waitForUpload(realm).then(() => realm.close()); + }) + .then(() => Realm.open(config(otherUser, `realm://localhost:9080/${owner.identity}/test`))) + .then((realm) => subscribe(realm.objects('Object')).then(() => realm)) + .then((realm) => { + // Should have full access to the Realm as a whole + TestCase.assertSimilar('object', realm.privileges(), + {read: true, update: true, modifySchema: true, setPermissions: true}); + TestCase.assertSimilar('object', realm.privileges('Object'), + {read: true, update: true, create: true, subscribe: true, setPermissions: true}); + // Verify that checking via constructor works too + TestCase.assertSimilar('object', realm.privileges(Realm.Permissions.User), + {read: true, update: true, create: true, subscribe: true, setPermissions: true}); - // Should only be able to see the second object - let results = realm.objects('Object') - TestCase.assertEqual(results.length, 1); - TestCase.assertEqual(results[0].value, 2); - TestCase.assertSimilar('object', realm.privileges(results[0]), - {read: true, update: false, delete: false, setPermissions: false}); - realm.close(); - }); + // Should only be able to see the second object + let results = realm.objects('Object') + TestCase.assertEqual(results.length, 1); + TestCase.assertEqual(results[0].value, 2); + TestCase.assertSimilar('object', realm.privileges(results[0]), + {read: true, update: false, delete: false, setPermissions: false}); + realm.close(); + }); } } - From 83715e840fbde675b5d25a46f3bacf9a993418a8 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 6 Mar 2018 18:05:17 +0100 Subject: [PATCH 47/61] Adding API doc and changelog. --- CHANGELOG.md | 9 ++- docs/permission.js | 162 ++++++++++++++++++++++++++++++++++++++++++++- docs/realm.js | 21 ++++++ 3 files changed, 189 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8c427cf..2fc2cd79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,14 @@ - Any number of sort/distinct conditions can be indicated, they will be applied in the specified order. - Sort or distinct cannot operate independently, these conditions must be attached to at least one query filter. * [Sync] Added `Realm.Results.subscribe()` to subscribe to partial synced Realms. -* [Sync] Added class `Realm.Sync.Subscription` to support partial synced Realms. +* [Sync] Added class `Realm.Sync.Subscription` and enum `Realm.Sync.SubscriptionState` to support partial synced Realms. +* [Sync] Added an object-level permission subsystem. It is possible to grant fine-grained priviliges to users. +* Added object-level permissions: + - Schemas `Realm.Permissions.Realm`, `Realm.Permissions.Class`, `Realm.Permissions.Role`, `Realm.Permissions.User`, and `Realm.Permissions.Permission` to support working with permissions. These schemas can be used in user-defined Realms and schemas. + - Permissions are enforced by the object server but connectivity is not required. + - Method `Realm.privilges()` to compute privileges on a Realm, a Realm object schema, or a Realm object. The method returns either a `Realm.Permissions.Realm` or `Realm.Permissions.Class` object. + - For non-synced Realms, all privileges are always granted. + - For more details, please read the reference documentation. ### Internal * Updated to Realm Core 5.8.0. diff --git a/docs/permission.js b/docs/permission.js index 450ae7f9..dc4d03fb 100644 --- a/docs/permission.js +++ b/docs/permission.js @@ -21,7 +21,7 @@ * They are created exclusively by the client and are processed by the server * as indicated by the status fields. * PermissionChange objects allow to grant and revoke permissions by setting - * mayRead, mayWrite and mayManage accordingly. + * mayRead, mayWrite and mayManage accordingly. * If any of these flags are not set, these are merged * with either the existing or default permissions as applicable. As a * side-effect this causes that the default permissions are permanently @@ -30,7 +30,7 @@ * ErrorCode will be updated accordingly. */ class PermissionChange { - + /** * Gets the unique identifier of this object in the Management realm. * @type {string} @@ -229,3 +229,161 @@ class PermissionOfferResponse { */ get realmUrl() {} } + + + +/** + * A permission which can be applied to a Realm, Class, or specific Object. + * Permissions are applied by adding the permission to the Realm.Permission singleton + * object, the RealmClass.Permission object for the desired class, or to a user-defined + * Realm.List property on a specific Object instance. The meaning of each of + * the properties of Permission depend on what the permission is applied to, and so are + * left undocumented here. + * @since 2.3.0 +*/ +class Permission { + + /** + * The Role which this Permission applies to. All users within the Role are + * granted the permissions specified by the fields below any + * objects/classes/realms which use this Permission. + * + * This property cannot be modified once set. + * @type {Role} + */ + get role() {} + + /** + * Whether the user can read the object to which this Permission is attached. + * @type {boolean} + */ + get canRead() {} + + /** + * Whether the user can modify the object to which this Permission is attached. + * @type {boolean} + */ + get canUpdate() {} + + /** + * Whether the user can delete the object to which this Permission is attached. + * + * This property is only applicable to Permissions attached to Objects, and not + * to Realms or Classes. + * @type {boolean} + */ + get canDelete() {} + + /** + * Whether the user can add or modify Permissions for the object which this + * Permission is attached to. + * @type {boolean} + */ + get canSetPermissions() {} + + /** + * Whether the user can subscribe to queries for this object type. + * + * This property is only applicable to Permissions attached to Classes, and not + * to Realms or Objects. + * @type {boolean} + */ + get canQuery() {} + + /** + * Whether the user can create new objects of the type this Permission is attached to. + * + * This property is only applicable to Permissions attached to Classes, and not + * to Realms or Objects. + * @type {boolean} + */ + get canCreate() {} + + /** + * Whether the user can modify the schema of the Realm which this + * Permission is attached to. + * + * This property is only applicable to Permissions attached to Realms, and not + * to Realms or Objects. + * @type {boolean} + */ + get canModifySchema() {} +} + +/** + * A representation of a sync user within the permissions system. + * + * User objects are created automatically for each sync user which connects to + * a Realm, and can also be created manually if you wish to grant permissions to a user + * which has not yet connected to this Realm. + * @since 2.3.0 + */ +class User { + /** + * The unique Realm Object Server user ID string identifying this user. This will have + * the same value as Realm.Sync.User.identity. + * @type {string} + */ + get id() {} +} + +/** + * A Role within the permissions system. + * + * A Role consists of a name for the role and a list of users which are members of the role. + * Roles are granted privileges on Realms, Classes and Objects, and in turn grant those + * privileges to all users which are members of the role. + * A role named "everyone" is automatically created in new Realms, and all new users which + * connect to the Realm are automatically added to it. Any other roles you wish to use are + * managed as normal Realm objects. + * @since 2.3.0 + */ +class Role { + /** + * The name of the Role. + * @type {string} + */ + get name() {} + + /** + * The users which belong to the role. + * @type {Array} + */ + get members() {} +} + +/** + * An object which describes class-wide permissions. + * + * An instance of this object is automatically created in the Realm for class in your schema, + * and should not be created manually. + * @since 2.3.0 + */ +class Class { + /** + * The name of the class which these permissions apply to. + * @type {string} + */ + get class_name() {} + + /** + * The permissions for this class. + * @type {Array} + */ + get permissions() {} +} + +/** + * A singleton object which describes Realm-wide permissions. + * + * An object of this type is automatically created in the Realm for you, and more objects + * cannot be created manually. + * @since 2.3.0 + */ +class Realm { + /** + * The permissions for the Realm. + * @type {Array} + */ + get permissions() {} +} diff --git a/docs/realm.js b/docs/realm.js index a04c259d..359a714a 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -125,6 +125,27 @@ class Realm { */ close() {} + /** + * Returns the granted privilges. + * + * This combines all privileges granted on the Realm/Class/Object by all Roles which + * the current User is a member of into the final privileges which will + * be enforced by the server. + * + * The privilege calculation is done locally using cached data, and inherently may + * be stale. It is possible that this method may indicate that an operation is + * permitted but the server will still reject it if permission is revoked before + * the changes have been integrated on the server. + * + * Non-synchronized Realms always have permission to perform all operations. + * + * @param {(Realm~ObjectType|Realm.Object)} arg - the object type or the object to compute priviliges from + * @returns {Object} as the computed priviliges as properties + * @since 2.3.0 + * @see {Realm.Permissions} for details of priviliges and roles. + */ + privileges(arg) {} + /** * Create a new Realm object of the given type and with the specified properties. * @param {Realm~ObjectType} type - The type of Realm object to create. From 928db78339aa97ba12fa64984250e122d0da63a9 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Wed, 7 Mar 2018 10:04:15 +0100 Subject: [PATCH 48/61] [2.3.0-beta.2] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 120afc60..da96615f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-3-3) +2.3.0 Release notes (2018-3-7) ============================================================= ### Breaking changes * [Sync] Sync protocol changed to version 24. diff --git a/dependencies.list b/dependencies.list index 1bff81a7..9dca6ef3 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-beta.1 +VERSION=2.3.0-beta.2 REALM_CORE_VERSION=5.3.0 REALM_SYNC_VERSION=3.0.0-beta.10 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.8 diff --git a/package.json b/package.json index 154dc4dc..842cb48b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-beta.1", + "version": "2.3.0-beta.2", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From d846794cd535deddda8266c19f2b68767b5155f9 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 8 Mar 2018 15:44:11 +0100 Subject: [PATCH 49/61] Deprecated setFeatureToken(). --- CHANGELOG.md | 1 + lib/extensions.js | 6 +----- lib/index.d.ts | 4 ++++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da96615f..34b157a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Method `Realm.privilges()` to compute privileges on a Realm, a Realm object schema, or a Realm object. The method returns either a `Realm.Permissions.Realm` or `Realm.Permissions.Class` object. - For non-synced Realms, all privileges are always granted. - For more details, please read the reference documentation. +* [Sync] Decrepated `Realm.Sync.setFeatureToken` (#1689). ### Internal * Updated to Realm Core 5.3.0. diff --git a/lib/extensions.js b/lib/extensions.js index 033d305c..2f3792f4 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -151,11 +151,7 @@ module.exports = function(realmConstructor) { if (realmConstructor.Sync._setFeatureToken) { realmConstructor.Sync.setFeatureToken = function(featureToken) { - if (typeof featureToken !== 'string' && !(featureToken instanceof String)) { - throw new Error("featureToken should be a string"); - } - - realmConstructor.Sync._setFeatureToken(featureToken.trim()); + console.log('Realm.Sync.setFeatureToken() is deprecated and you can remove any calls to it.'); } } diff --git a/lib/index.d.ts b/lib/index.d.ts index 21f0e93a..f7fa3cdd 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -448,6 +448,10 @@ declare namespace Realm.Sync { function removeListener(regex: string, name: string, changeCallback: (changeEvent: ChangeEvent) => void): Promise; function setLogLevel(logLevel: 'all' | 'trace' | 'debug' | 'detail' | 'info' | 'warn' | 'error' | 'fatal' | 'off'): void; function initiateClientReset(path: string): void; + + /** + * @deprecated, to be removed in future versions + */ function setFeatureToken(token: string): void; type Instruction = { From 1066722a09d8f34d90e7ae810d851486124c76d2 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 8 Mar 2018 15:47:50 +0100 Subject: [PATCH 50/61] Revert "Deprecated setFeatureToken()." This reverts commit d846794cd535deddda8266c19f2b68767b5155f9. --- CHANGELOG.md | 1 - lib/extensions.js | 6 +++++- lib/index.d.ts | 4 ---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b157a7..da96615f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,6 @@ - Method `Realm.privilges()` to compute privileges on a Realm, a Realm object schema, or a Realm object. The method returns either a `Realm.Permissions.Realm` or `Realm.Permissions.Class` object. - For non-synced Realms, all privileges are always granted. - For more details, please read the reference documentation. -* [Sync] Decrepated `Realm.Sync.setFeatureToken` (#1689). ### Internal * Updated to Realm Core 5.3.0. diff --git a/lib/extensions.js b/lib/extensions.js index 2f3792f4..033d305c 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -151,7 +151,11 @@ module.exports = function(realmConstructor) { if (realmConstructor.Sync._setFeatureToken) { realmConstructor.Sync.setFeatureToken = function(featureToken) { - console.log('Realm.Sync.setFeatureToken() is deprecated and you can remove any calls to it.'); + if (typeof featureToken !== 'string' && !(featureToken instanceof String)) { + throw new Error("featureToken should be a string"); + } + + realmConstructor.Sync._setFeatureToken(featureToken.trim()); } } diff --git a/lib/index.d.ts b/lib/index.d.ts index f7fa3cdd..21f0e93a 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -448,10 +448,6 @@ declare namespace Realm.Sync { function removeListener(regex: string, name: string, changeCallback: (changeEvent: ChangeEvent) => void): Promise; function setLogLevel(logLevel: 'all' | 'trace' | 'debug' | 'detail' | 'info' | 'warn' | 'error' | 'fatal' | 'off'): void; function initiateClientReset(path: string): void; - - /** - * @deprecated, to be removed in future versions - */ function setFeatureToken(token: string): void; type Instruction = { From dfc6aea6a6089acbac347f2a151084c53b5c2626 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 8 Mar 2018 17:27:13 +0100 Subject: [PATCH 51/61] Deprecated setFeatureToken(). (#1694) --- CHANGELOG.md | 1 + lib/extensions.js | 6 +----- lib/index.d.ts | 4 ++++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da96615f..34b157a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Method `Realm.privilges()` to compute privileges on a Realm, a Realm object schema, or a Realm object. The method returns either a `Realm.Permissions.Realm` or `Realm.Permissions.Class` object. - For non-synced Realms, all privileges are always granted. - For more details, please read the reference documentation. +* [Sync] Decrepated `Realm.Sync.setFeatureToken` (#1689). ### Internal * Updated to Realm Core 5.3.0. diff --git a/lib/extensions.js b/lib/extensions.js index 033d305c..2f3792f4 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -151,11 +151,7 @@ module.exports = function(realmConstructor) { if (realmConstructor.Sync._setFeatureToken) { realmConstructor.Sync.setFeatureToken = function(featureToken) { - if (typeof featureToken !== 'string' && !(featureToken instanceof String)) { - throw new Error("featureToken should be a string"); - } - - realmConstructor.Sync._setFeatureToken(featureToken.trim()); + console.log('Realm.Sync.setFeatureToken() is deprecated and you can remove any calls to it.'); } } diff --git a/lib/index.d.ts b/lib/index.d.ts index 21f0e93a..f7fa3cdd 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -448,6 +448,10 @@ declare namespace Realm.Sync { function removeListener(regex: string, name: string, changeCallback: (changeEvent: ChangeEvent) => void): Promise; function setLogLevel(logLevel: 'all' | 'trace' | 'debug' | 'detail' | 'info' | 'warn' | 'error' | 'fatal' | 'off'): void; function initiateClientReset(path: string): void; + + /** + * @deprecated, to be removed in future versions + */ function setFeatureToken(token: string): void; type Instruction = { From 945181b3abec6b056ce98cad105d28fe199f0c9a Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 9 Mar 2018 09:56:30 +0100 Subject: [PATCH 52/61] Updating to Realm Sync 3.0.0-rc.1 and Realm Core 5.4.0. (#1698) --- CHANGELOG.md | 7 +++++-- dependencies.list | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b157a7..dccdc632 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,9 +25,12 @@ - For more details, please read the reference documentation. * [Sync] Decrepated `Realm.Sync.setFeatureToken` (#1689). +### Bug fixes +* Fixed usage of disk space preallocation which would occasionally fail on recent MacOS running with the APFS filesystem (Realm Core #3005). + ### Internal -* Updated to Realm Core 5.3.0. -* Updated to Realm Sync 3.0.0-beta.10. +* Updated to Realm Core 5.4.0. +* Updated to Realm Sync 3.0.0-rc.1. * Tested against Realm Object Server 3.0.0-alpha.8. diff --git a/dependencies.list b/dependencies.list index 9dca6ef3..1247f305 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js VERSION=2.3.0-beta.2 -REALM_CORE_VERSION=5.3.0 -REALM_SYNC_VERSION=3.0.0-beta.10 +REALM_CORE_VERSION=5.4.0 +REALM_SYNC_VERSION=3.0.0-rc.1 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.8 From abb04e399be2a6a47559fa507bdc824990b4bd6f Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 9 Mar 2018 10:40:45 +0100 Subject: [PATCH 53/61] Default sync realm (#1693) * Configuration for default Realm. * Use url-parse to make RN happy. --- CHANGELOG.md | 1 + docs/realm.js | 9 +++++++++ lib/extensions.js | 24 ++++++++++++++++++++++++ lib/index.d.ts | 7 ++++++- package.json | 2 +- tests/js/realm-tests.js | 2 +- tests/js/session-tests.js | 27 +++++++++++++++++++++++++++ 7 files changed, 69 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dccdc632..0fcd769e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Method `Realm.privilges()` to compute privileges on a Realm, a Realm object schema, or a Realm object. The method returns either a `Realm.Permissions.Realm` or `Realm.Permissions.Class` object. - For non-synced Realms, all privileges are always granted. - For more details, please read the reference documentation. +* Added `Realm.defaultSyncConfiguration()` which will return the configuration for a default synced Realm (#1688). * [Sync] Decrepated `Realm.Sync.setFeatureToken` (#1689). ### Bug fixes diff --git a/docs/realm.js b/docs/realm.js index 359a714a..1959151e 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -119,6 +119,15 @@ class Realm { */ static openAsync(config, callback, progressCallback) {} + /** + * Return a configuration for a default synced Realm. The server URL for the current user will be used as base for + * the URL for the synced Realm. + * @throws {Error} if zero or multiple users are logged in + * @returns {Realm~Configuration} - a configuration matching a default synced Realm. + * @since 2.3.0 + */ + static defaultSyncConfiguration() {} + /** * Closes this Realm so it may be re-opened with a newer schema version. * All objects and collections from this Realm are no longer valid after calling this method. diff --git a/lib/extensions.js b/lib/extensions.js index 2f3792f4..93d08322 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -18,6 +18,8 @@ 'use strict'; +const URL = require('url-parse'); + let getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors || function(obj) { return Object.getOwnPropertyNames(obj).reduce(function (descriptors, name) { descriptors[name] = Object.getOwnPropertyDescriptor(obj, name); @@ -149,6 +151,28 @@ module.exports = function(realmConstructor) { setConstructorOnPrototype(realmConstructor.Sync.User); setConstructorOnPrototype(realmConstructor.Sync.Session); + // A configuration for a default Realm + realmConstructor.defaultSyncConfiguration = function() { + let users = this.Sync.User.all; + let identities = Object.keys(users); + if (identities.length === 1) { + let user = users[identities[0]]; + let url = new URL(user.server); + let secure = (url.protocol === 'https:')?'s':''; + let port = (url.port === undefined)?'9080':url.port + let realmUrl = `realm${secure}://${url.hostname}:${port}/~/default`; + + let config = { + sync: { + user, + url: realmUrl + } + }; + return config; + } + new Error(`One and only one user should be logged in but found ${users.length} users.`); + } + if (realmConstructor.Sync._setFeatureToken) { realmConstructor.Sync.setFeatureToken = function(featureToken) { console.log('Realm.Sync.setFeatureToken() is deprecated and you can remove any calls to it.'); diff --git a/lib/index.d.ts b/lib/index.d.ts index f7fa3cdd..79730398 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -565,11 +565,16 @@ declare class Realm { */ static openAsync(config: Realm.Configuration, callback: (error: any, realm: Realm) => void, progressCallback?: Realm.Sync.ProgressNotificationCallback): void + /** + * Return a configuration for a default Realm. + */ + static defaultSyncConfiguration(): string; + /** * Delete the Realm file for the given configuration. * @param {Configuration} config */ - static deleteFile(config: Realm.Configuration): void + static deleteFile(config: Realm.Configuration): void; /** * @param {Realm.Configuration} config? diff --git a/package.json b/package.json index 842cb48b..fc8c2276 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "request": "^2.78.0", "stream-counter": "^1.0.0", "sync-request": "^3.0.1", - "url-parse": "^1.1.7" + "url-parse": "^1.2.0" }, "devDependencies": { "@types/node": "^4.0.35", diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index 61a07b66..9fe9ab76 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -56,7 +56,7 @@ module.exports = { const realm2 = new Realm({schema: [], path: testPath2}); TestCase.assertEqual(realm2.path, defaultDir + testPath2); }, - + testRealmIsClosed: function() { const realm = new Realm({schema: []}); TestCase.assertFalse(realm.isClosed); diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index ee01c3c6..97c8db41 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -166,6 +166,33 @@ module.exports = { }); }, + testDefaultRealm() { + if (!isNodeProccess) { + return; + } + + const username = uuid(); + const realmName = 'default'; + const expectedObjectsCount = 3; + + let user; + return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) + .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) + .then(u => { + user = u; + return Realm.open(Realm.defaultSyncConfiguration()); + }) + .then(realm => { + let actualObjectsCount = realm.objects('Dog').length; + TestCase.assertEqual(actualObjectsCount, expectedObjectsCount, "Synced realm does not contain the expected objects count"); + + const session = realm.syncSession; + TestCase.assertInstanceOf(session, Realm.Sync.Session); + TestCase.assertEqual(session.user.identity, user.identity); + TestCase.assertEqual(session.state, 'active'); + }); + }, + testRealmOpenWithExistingLocalRealm() { if (!isNodeProccess) { return; From 9d8a677ecff1b367f3bee86ac4b8603e8248bcf6 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 9 Mar 2018 15:51:45 +0100 Subject: [PATCH 54/61] Revoke refresh token upon logout. (#1696) * Revoke refresh token upon logout. * Always log out --- CHANGELOG.md | 1 + lib/user-methods.js | 21 +++++++++++++++++++++ src/js_sync.hpp | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fcd769e..5d29b90c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Method `Realm.privilges()` to compute privileges on a Realm, a Realm object schema, or a Realm object. The method returns either a `Realm.Permissions.Realm` or `Realm.Permissions.Class` object. - For non-synced Realms, all privileges are always granted. - For more details, please read the reference documentation. +* [Sync] Revoke refresh tiken upon logout (#1354). * Added `Realm.defaultSyncConfiguration()` which will return the configuration for a default synced Realm (#1688). * [Sync] Decrepated `Realm.Sync.setFeatureToken` (#1689). diff --git a/lib/user-methods.js b/lib/user-methods.js index 9634ef37..efbdfae8 100644 --- a/lib/user-methods.js +++ b/lib/user-methods.js @@ -348,6 +348,27 @@ const staticMethods = { const instanceMethods = { + logout() { + this._logout(); + const url = url_parse(this.server); + url.set('pathname', '/auth/revoke'); + const headers = { + Authorization: this.token + }; + const body = { + token: this.token + }; + const options = { + method: 'POST', + headers, + body: body, + open_timeout: 5000 + }; + + performFetch(url.href, options) + .then(() => console.log('User is logged out')) + .catch((e) => print_error(e)); + }, openManagementRealm() { let url = url_parse(this.server); if (url.protocol === 'http:') { diff --git a/src/js_sync.hpp b/src/js_sync.hpp index b78f9cba..1a3ff6d0 100644 --- a/src/js_sync.hpp +++ b/src/js_sync.hpp @@ -103,7 +103,7 @@ public: static void session_for_on_disk_path(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); MethodMap const methods = { - {"logout", wrap}, + {"_logout", wrap}, {"_sessionForOnDiskPath", wrap} }; }; From 30c7d901dc10affed9d0fdf84fb2d49a0e986057 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 9 Mar 2018 15:55:37 +0100 Subject: [PATCH 55/61] [2.3.0-beta.3] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d29b90c..496a86f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-3-7) +2.3.0 Release notes (2018-3-9) ============================================================= ### Breaking changes * [Sync] Sync protocol changed to version 24. diff --git a/dependencies.list b/dependencies.list index 1247f305..97edfddc 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-beta.2 +VERSION=2.3.0-beta.3 REALM_CORE_VERSION=5.4.0 REALM_SYNC_VERSION=3.0.0-rc.1 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.8 diff --git a/package.json b/package.json index fc8c2276..4ea4e0c4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-beta.2", + "version": "2.3.0-beta.3", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ From 6d5c1e42e854c610fc8008119d76a72a1c6772d0 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 12 Mar 2018 10:42:51 +0100 Subject: [PATCH 56/61] Fix typos --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6da089d..7aadbf13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,9 +23,9 @@ - Method `Realm.privilges()` to compute privileges on a Realm, a Realm object schema, or a Realm object. The method returns either a `Realm.Permissions.Realm` or `Realm.Permissions.Class` object. - For non-synced Realms, all privileges are always granted. - For more details, please read the reference documentation. -* [Sync] Revoke refresh tiken upon logout (#1354). +* [Sync] Revoke refresh token upon logout (#1354). * Added `Realm.defaultSyncConfiguration()` which will return the configuration for a default synced Realm (#1688). -* [Sync] Decrepated `Realm.Sync.setFeatureToken` (#1689). +* [Sync] Deprecated `Realm.Sync.setFeatureToken` (#1689). ### Bug fixes * Fixed usage of disk space preallocation which would occasionally fail on recent MacOS running with the APFS filesystem (Realm Core #3005). From 187c4354f18ac0d2412175607df8a888a3a50c03 Mon Sep 17 00:00:00 2001 From: James Stone Date: Mon, 12 Mar 2018 10:48:57 -0700 Subject: [PATCH 57/61] Inline method in hpp to remove duplicate definitions --- src/js_results.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js_results.hpp b/src/js_results.hpp index 350cd836..f7d033e3 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -152,7 +152,7 @@ typename T::Object ResultsClass::create_instance(ContextType ctx, SharedRealm return create_object>(ctx, new realm::js::Results(realm, *table)); } -void alias_backlinks(parser::KeyPathMapping &mapping, const realm::SharedRealm &realm) +inline void alias_backlinks(parser::KeyPathMapping &mapping, const realm::SharedRealm &realm) { const realm::Schema &schema = realm->schema(); for (auto it = schema.begin(); it != schema.end(); ++it) { From 2f20006e4748aa7bc92692a3c0cd1fc3584b94b1 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 13 Mar 2018 08:27:47 +0100 Subject: [PATCH 58/61] Kneth/allow partial in urls (#1704) * Adding _disablePartialSyncUrlChecks. --- CHANGELOG.md | 1 + lib/index.d.ts | 1 + src/js_sync.hpp | 14 +++++++++++++- tests/js/session-tests.js | 30 ++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3639f9a2..cb2d327e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ * Updated to Realm Core 5.4.0. * Updated to Realm Sync 3.0.0-rc.1. * Tested against Realm Object Server 3.0.0-alpha.8. +* Added `_disablePartialSyncUrlChecks` to `Realm.Configuration`. 2.2.15 Release notes (2018-3-9) ============================================================= diff --git a/lib/index.d.ts b/lib/index.d.ts index 79730398..367ece6f 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -379,6 +379,7 @@ declare namespace Realm.Sync { open_ssl_verify_callback?: SSLVerifyCallback; error?: ErrorCallback; partial?: boolean; + _disablePartialSyncUrlChecks?:boolean; } type ProgressNotificationCallback = (transferred: number, transferable: number) => void; diff --git a/src/js_sync.hpp b/src/js_sync.hpp index 1a3ff6d0..9958c523 100644 --- a/src/js_sync.hpp +++ b/src/js_sync.hpp @@ -823,7 +823,19 @@ void SyncClass::populate_sync_config(ContextType ctx, ObjectType realm_constr is_partial = Value::validated_to_boolean(ctx, partial_value); } - config.sync_config = std::make_shared(shared_user, std::move(raw_realm_url)); + bool disable_partial_sync_url_checks = false; + ValueType disable_partial_sync_url_checks_value = Object::get_property(ctx, sync_config_object, "_disablePartialSyncUrlChecks"); + if (!Value::is_undefined(ctx, disable_partial_sync_url_checks_value)) { + disable_partial_sync_url_checks = Value::validated_to_boolean(ctx, disable_partial_sync_url_checks_value); + } + + if (disable_partial_sync_url_checks) { + config.sync_config = std::make_shared(shared_user, std::move("")); + config.sync_config->reference_realm_url = std::move(raw_realm_url); + } + else { + config.sync_config = std::make_shared(shared_user, std::move(raw_realm_url)); + } config.sync_config->bind_session_handler = std::move(bind); config.sync_config->error_handler = std::move(error_handler); config.sync_config->client_validate_ssl = client_validate_ssl; diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index 97c8db41..7edc22d1 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -749,6 +749,36 @@ module.exports = { }); }, + testOpenPartialSyncUrl() { + if (!isNodeProccess) { + return; + } + + const username = uuid(); + return Realm.Sync.User.register('http://localhost:9080', username, 'password') + .then(user => { + let config1 = { + sync: { + user: user, + url: `realm://localhost:9080/~/default/__partial/`, + partial: true, + _disablePartialSyncUrlChecks: true + } + }; + const realm = new Realm(config1); + TestCase.assertFalse(realm.isClosed); + + let config2 = { + sync: { + user: user, + url: `realm://localhost:9080/~/default/__partial/`, // <--- not allowed URL + partial: true, + } + }; + TestCase.assertThrows(() => new Realm(config2)); + }); + }, + testPartialSyncAnonymous_SubscriptionListener() { // FIXME: try to enable for React Native if (!isNodeProccess) { From edfdd933b0827a812f86aa05b3fdde6e00678f82 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 13 Mar 2018 12:45:23 +0100 Subject: [PATCH 59/61] Updating API docs and TS definitions (closes #1770). (#1707) --- docs/collection.js | 5 +++-- docs/sync.js | 4 ++-- lib/index.d.ts | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/collection.js b/docs/collection.js index 648738e7..fe791327 100644 --- a/docs/collection.js +++ b/docs/collection.js @@ -140,9 +140,10 @@ class Collection { * to add a listener. * * @example - * let wines = realm.objects('Wine').filtered('vintage <= $0', maxYear).subscribe(); + * let wines = realm.objects('Wine').filtered('vintage <= $0', maxYear); + * let subscription = wines.subscribe(); * wines.addListener((collection, changes) => { - * if (changes.partial_sync.new_state == Realm.Sync.SubscriptionState.Initialized) { + * if (subscription.state === Realm.Sync.SubscriptionState.Complete) { * // update UI * } * }); diff --git a/docs/sync.js b/docs/sync.js index 77dfeedc..4cd19c27 100644 --- a/docs/sync.js +++ b/docs/sync.js @@ -500,7 +500,7 @@ class Subscription { /** * Adds a listener `callback` which will be called when the state of the subscription changes. - * @param {function(state)} callback - A function to be called when changes occur. + * @param {function(subscription, state)} callback - A function to be called when changes to the subscription occur. * @throws {Error} If `callback` is not a function. * @example * let subscription = results.subscribe(); @@ -519,7 +519,7 @@ class Subscription { /** * Remove the listener `callback` from the subscription instance. - * @param {function(collection, changes)} callback - Callback function that was previously + * @param {function(subscription, state)} callback - Callback function that was previously * added as a listener through the {@link Subscription#addListener addListener} method. * @throws {Error} If `callback` is not a function. */ diff --git a/lib/index.d.ts b/lib/index.d.ts index 367ece6f..95c0b991 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -400,7 +400,7 @@ declare namespace Realm.Sync { removeProgressNotification(progressCallback: ProgressNotificationCallback): void; } - type SubscriptionNotificationCallback = (state: number) => void; + type SubscriptionNotificationCallback = (subscription: Subscription, state: number) => void; /** * Subscription From 9435490ea376c8b6e516768c6a34194f5c44f655 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 13 Mar 2018 14:27:31 +0100 Subject: [PATCH 60/61] Upgrading to Realm Sync 3.0.0-rc.2 (#1706) * Adding role to __User. * Update to Realm Sync 3.0.0 * Renamed defaultSyncConfiguration to automaticSyncConfiguration --- CHANGELOG.md | 4 ++-- dependencies.list | 4 ++-- docs/realm.js | 2 +- lib/extensions.js | 5 +++-- lib/index.d.ts | 2 +- tests/js/session-tests.js | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb2d327e..db60b94e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ - For non-synced Realms, all privileges are always granted. - For more details, please read the reference documentation. * [Sync] Revoke refresh token upon logout (#1354). -* Added `Realm.defaultSyncConfiguration()` which will return the configuration for a default synced Realm (#1688). +* Added `Realm.automaticSyncConfiguration()` which will return the configuration for a default synced Realm (#1688). * [Sync] Deprecated `Realm.Sync.setFeatureToken` (#1689). ### Bug fixes @@ -34,7 +34,7 @@ ### Internal * Updated to Realm Core 5.4.0. -* Updated to Realm Sync 3.0.0-rc.1. +* Updated to Realm Sync 3.0.0. * Tested against Realm Object Server 3.0.0-alpha.8. * Added `_disablePartialSyncUrlChecks` to `Realm.Configuration`. diff --git a/dependencies.list b/dependencies.list index 492c6379..be3714c3 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js VERSION=2.3.0-beta.3 REALM_CORE_VERSION=5.4.0 -REALM_SYNC_VERSION=3.0.0-rc.1 -REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.8 \ No newline at end of file +REALM_SYNC_VERSION=3.0.0 +REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.8 diff --git a/docs/realm.js b/docs/realm.js index 1959151e..7c259441 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -126,7 +126,7 @@ class Realm { * @returns {Realm~Configuration} - a configuration matching a default synced Realm. * @since 2.3.0 */ - static defaultSyncConfiguration() {} + static automaticSyncConfiguration() {} /** * Closes this Realm so it may be re-opened with a newer schema version. diff --git a/lib/extensions.js b/lib/extensions.js index 93d08322..852b57a4 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -152,7 +152,7 @@ module.exports = function(realmConstructor) { setConstructorOnPrototype(realmConstructor.Sync.Session); // A configuration for a default Realm - realmConstructor.defaultSyncConfiguration = function() { + realmConstructor.automaticSyncConfiguration = function() { let users = this.Sync.User.all; let identities = Object.keys(users); if (identities.length === 1) { @@ -215,7 +215,8 @@ module.exports = function(realmConstructor) { name: '__User', primaryKey: 'id', properties: { - id: 'string' + id: 'string', + role: '__Role' } }); diff --git a/lib/index.d.ts b/lib/index.d.ts index 95c0b991..4ef88293 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -569,7 +569,7 @@ declare class Realm { /** * Return a configuration for a default Realm. */ - static defaultSyncConfiguration(): string; + static automaticSyncConfiguration(): string; /** * Delete the Realm file for the given configuration. diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index 7edc22d1..dfce3715 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -180,7 +180,7 @@ module.exports = { .then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password')) .then(u => { user = u; - return Realm.open(Realm.defaultSyncConfiguration()); + return Realm.open(Realm.automaticSyncConfiguration()); }) .then(realm => { let actualObjectsCount = realm.objects('Dog').length; From 55a4224d8323d31d4bfe05a5bd17d53c44f130b2 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 13 Mar 2018 14:29:50 +0100 Subject: [PATCH 61/61] [2.3.0] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db60b94e..606b3a3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -2.3.0 Release notes (2018-3-9) +2.3.0 Release notes (2018-3-13) ============================================================= ### Breaking changes * [Sync] Sync protocol changed to version 24. diff --git a/dependencies.list b/dependencies.list index be3714c3..5b71c035 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.3.0-beta.3 +VERSION=2.3.0 REALM_CORE_VERSION=5.4.0 REALM_SYNC_VERSION=3.0.0 REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.8 diff --git a/package.json b/package.json index 4ea4e0c4..1d90ff48 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.3.0-beta.3", + "version": "2.3.0", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [