Merge branch '2.3.x' of github.com:realm/realm-js into jas/backlink-queries

This commit is contained in:
Kenneth Geisshirt 2018-02-27 11:32:33 +01:00
commit 8e63737008
26 changed files with 828 additions and 152 deletions

View File

@ -1,32 +1,65 @@
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.
* [Sync] Sync protocol changed to version 24.
* [Sync] 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.
* [Sync] `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).
* [Sync] Reduced initial download times in Realms with long transaction histories.
* [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.
* [Sync] Added `Realm.Results.subscribe()` to subscribe to partial synced Realms.
* [Sync] Added class `Realm.Sync.Subscription` to support partial synced Realms.
### 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.
2.2.10 Release notes (2018-2-20)
=============================================================
### Breaking changes
* None.
### Enhancements
* None.
### Bug fixes
* [Sync] A use-after-free bug was fixed which could cause arrays of primitives to behave unexpectedly.
### Internal
* Updated to Realm Sync 2.2.12.
2.2.9 Release notes (2018-2-19)
=============================================================
### Breaking changes
* None.
### Enhancements
* None.
### Bug fixes
* Improved root certificate checking.
### Internal
* Updated to Realm Sync 2.2.11.
2.2.8 Release notes (2018-2-13)
=============================================================
### Breaking changes
* None.
### Enhancements
* [Object Server] For OpenSSL, the sync client includes a fixed list of certificates in its SSL certificate verification besides the default trust store in the case where the user is not specifying its own trust certificates or callback.
* [Sync] For OpenSSL, the sync client includes a fixed list of certificates in its SSL certificate verification besides the default trust store in the case where the user is not specifying its own trust certificates or callback.
### Bug fixes
* None.
@ -41,15 +74,16 @@
* None.
### Enhancements
* [Object Server] 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).
* Add schema name to missing primary key error message
### Bug fixes
* [Object Server] Fixed a bug causing use-after-free crashes in Global Notifier (realm-js-private #405).
* [Sync] Fixed a bug causing use-after-free crashes in Global Notifier (realm-js-private #405).
### Internal
* None.
2.2.6 Release notes (2018-1-26)
=============================================================
### Breaking changes
@ -59,7 +93,7 @@
* None.
### Bug fixes
* [Object Server] Fixed a bug where arguments were not transferred when debugging.
* [Sync] Fixed a bug where arguments were not transferred when debugging.
### Internal
* None.
@ -73,7 +107,7 @@
* None.
### Bug fixes
* [Object Server] Fixed a typing error leading to `_getExistingUser` wasn't defined in the Chrome debugging support library (#1625).
* [Sync] Fixed a typing error leading to `_getExistingUser` wasn't defined in the Chrome debugging support library (#1625).
* Fixed a bug in the TypeScript definition of `PermissionCondition` (#1574).
* [Electron] Fixed a `dlopen` error related to OpenSSL that prevented using realm-js on Linux (#1636).
@ -89,8 +123,8 @@
* None.
### Bug fixes
* [Object Server] Fixed a bug where errors in `refreshAdminToken` wasn't catched (#1627).
* [Object Server] Added `_getExitingUser` to the Chrome debugging support library.
* [Sync] Fixed a bug where errors in `refreshAdminToken` wasn't catched (#1627).
* [Sync] Added `_getExitingUser` to the Chrome debugging support library.
### Internal
* None.
@ -104,8 +138,8 @@
* None.
### Bug fixes
* [Object Server] Fixed a bug in upload progress reporting.
* [Object Server] Fixed a bug where any errors which occurred when trying to sync the admin Realm were ignored, which made attempting to add a listener with an invalid admin user silently do nothing.
* [Sync] Fixed a bug in upload progress reporting.
* [Sync] Fixed a bug where any errors which occurred when trying to sync the admin Realm were ignored, which made attempting to add a listener with an invalid admin user silently do nothing.
### Internal
* None.
@ -119,7 +153,7 @@
* None.
### Bug fixes
* [Object Server] Added missing `Realm.Sync` listener functions.
* [Sync] Added missing `Realm.Sync` listener functions.
### Internal
* None.
@ -134,7 +168,7 @@
* None.
### Bug fixes
* [Object Server] Fixed a bug preventing opening Realms with an admin token without a working ROS directory service (#1615).
* [Sync] Fixed a bug preventing opening Realms with an admin token without a working ROS directory service (#1615).
### Internal
* None.
@ -147,8 +181,8 @@
### Enhancements
* Added new query features to support a subset of `NSPredicates` for example `LIKE` for string matches, `@count` and `@sum` in lists. See documentation for more details.
* Potential performance enhancements in cases of many writes between queries.
* [Object Server] Added method `Realm.Sync.User.authenticate` to unify authentication of users.
* [Object Server] Added JWT authenfication (#1548).
* [Sync] Added method `Realm.Sync.User.authenticate` to unify authentication of users.
* [Sync] Added JWT authenfication (#1548).
### Bug fixes
* Fix a bug where `Realm.open` could unexpectedly raise a "Realm at path ... already opened with different schema version" error.
@ -169,10 +203,10 @@
* None.
### Bug fixes
* [Object Server] Fixed a bug where long reconnection happens when a proxy in front of the sync worker returns one of those.
* [Sync] Fixed a bug where long reconnection happens when a proxy in front of the sync worker returns one of those.
### Internal
* [Object Server] Updated to Realm Object Server v2.2.0 for testing.
* [Sync] Updated to Realm Object Server v2.2.0 for testing.
* Updated to Realm Sync 2.1.10 (see "Bug fixes").
@ -200,11 +234,11 @@
* None.
### Bug fixes
* [Object Server] When authentication fails due to a misbehaving server, a proper error is thrown.
* [Sync] When authentication fails due to a misbehaving server, a proper error is thrown.
### Internal
* [Object Server] Strings can now be assigned to Date columns. When that happens the JavaScript Date constructor will be invoked to parse the string.
* [Object Server] Base64 strings can now be assigned to Data columns.
* [Sync] Strings can now be assigned to Date columns. When that happens the JavaScript Date constructor will be invoked to parse the string.
* [Sync] Base64 strings can now be assigned to Data columns.
2.0.12 Release notes (2017-12-1)
=============================================================
@ -230,8 +264,8 @@
* None
### Bug fixes
* [Object Server] Fixed a bug where deleted-then-recreated objects with identical primary keys to become empty.
* [Object Server] Fixed a bug in outward partial sync is changed to ensure convergence of partial sync in the case where the client creates a primary key object, that is already present on the server, and subscribes to it in the same transaction.
* [Sync] Fixed a bug where deleted-then-recreated objects with identical primary keys to become empty.
* [Sync] Fixed a bug in outward partial sync is changed to ensure convergence of partial sync in the case where the client creates a primary key object, that is already present on the server, and subscribes to it in the same transaction.
### Internal
* Updated to Realm Sync 2.1.7 (see under "Bug fixes").
@ -270,10 +304,10 @@
* None.
### Enhancements
* [Object Server] Improving performance of processing large changesets.
* [Sync] Improving performance of processing large changesets.
### Bug fixes
* [Object Server] Changesets over 16MB in size are now handled correctly.
* [Sync] Changesets over 16MB in size are now handled correctly.
### Internal
* Updated to Realm Sync 2.1.6.

View File

@ -1,5 +1,5 @@
PACKAGE_NAME=realm-js
VERSION=2.3.0-alpha.11
VERSION=2.3.0-alpha.13
REALM_CORE_VERSION=5.2.0
REALM_SYNC_VERSION=3.0.0-beta.6
REALM_SYNC_VERSION=3.0.0-beta.9
REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.2

View File

@ -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<T>} 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) {}

View File

@ -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) {}
}
/**

View File

@ -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.
*

View File

@ -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;
}

View File

@ -41,6 +41,7 @@ export const propTypes = {};
'RESULTS',
'USER',
'SESSION',
'SUBSCRIPTION',
'UNDEFINED',
].forEach(function(type) {
Object.defineProperty(objectTypes, type, {

View File

@ -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, {

View File

@ -30,6 +30,7 @@ createMethods(Results.prototype, objectTypes.RESULTS, [
'filtered',
'sorted',
'snapshot',
'subscribe',
'isValid',
'indexOf',
'min',

View File

@ -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'
]);

View File

@ -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.
};
}

29
lib/index.d.ts vendored
View File

@ -161,6 +161,11 @@ declare namespace Realm {
sorted(descriptor: SortDescriptor[]): Results<T>;
sorted(descriptor: string, reverse?: boolean): Results<T>;
/**
* @returns Results<T>
*/
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 }
@ -545,9 +565,9 @@ declare class Realm {
/**
* @param {string|Realm.ObjectSchema|Function} type
* @param {number|string} key
* @returns T
* @returns {T | undefined}
*/
objectForPrimaryKey<T>(type: string | Realm.ObjectSchema | Function, key: number | string): T | null;
objectForPrimaryKey<T>(type: string | Realm.ObjectSchema | Function, key: number | string): T | undefined;
/**
* @param {string|Realm.ObjectType|Function} type
@ -600,11 +620,6 @@ declare class Realm {
* @returns boolean
*/
compact(): boolean;
/**
* @returns Promise<Results<T>>
*/
subscribeToObjects<T>(objectType: string, query: string): Promise<Realm.Results<T>>;
}
declare module 'realm' {

View File

@ -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.13",
"license": "Apache-2.0",
"homepage": "https://realm.io",
"keywords": [

View File

@ -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

View File

@ -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"
],
}]
],

View File

@ -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 = "<group>"; };
423737B01F7E333400FAEDFF /* partial_sync.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = partial_sync.hpp; path = src/sync/partial_sync.hpp; sourceTree = "<group>"; };
425A120F20235A1400C2F932 /* work_queue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = work_queue.cpp; sourceTree = "<group>"; };
425A121020235A1400C2F932 /* work_queue.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = work_queue.hpp; sourceTree = "<group>"; };
426FCDFF1F7DA2F9005565DC /* sync_config.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sync_config.cpp; path = src/sync/sync_config.cpp; sourceTree = "<group>"; };
502B07E31E2CD1FA007A84ED /* object.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = object.cpp; path = src/object.cpp; sourceTree = "<group>"; };
502B07E51E2CD20D007A84ED /* object.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = object.hpp; path = src/object.hpp; sourceTree = "<group>"; };
@ -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;
};

View File

@ -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<T, Collection, ObservableClass<T>> {
using Value = js::Value<T>;
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<T>::create_collection_change_set(ContextType c
{
ObjectType object = Object::create_empty(ctx);
std::vector<ValueType> deletions, insertions, modifications;
if (change_set.deletions.count() == std::numeric_limits<size_t>::max()) {
deletions.push_back(Value::from_null(ctx));
}
@ -58,12 +61,12 @@ typename T::Value CollectionClass<T>::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));
}

View File

@ -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<object_for_object_id>},
#if REALM_ENABLE_SYNC
{"_waitForDownload", wrap<wait_for_download_completion>},
{"_subscribeToObjects", wrap<subscribe_to_objects>},
#endif
};
@ -1061,51 +1057,5 @@ void RealmClass<T>::object_for_object_id(ContextType ctx, ObjectType this_object
#endif // REALM_ENABLE_SYNC
}
#if REALM_ENABLE_SYNC
template<typename T>
void RealmClass<T>::subscribe_to_objects(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_count(3);
SharedRealm realm = *get_internal<T, RealmClass<T>>(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<ObjectType> protected_this(ctx, this_object);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
Protected<FunctionType> 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<T>::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<T>::create_instance(protected_ctx, results);
Function<T>::callback(protected_ctx, protected_callback, protected_this, 2, callback_arguments);
};
partial_sync::register_query(realm, object_type, query, std::move(cb));
}
#endif
} // js
} // realm

View File

@ -28,6 +28,11 @@
#include <realm/parser/parser.hpp>
#include <realm/parser/query_builder.hpp>
#include <realm/util/optional.hpp>
#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<T, realm::js::Results<T>, 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<T, realm::js::Results<T>, CollectionClass<
{"filtered", wrap<filtered>},
{"sorted", wrap<sorted>},
{"isValid", wrap<is_valid>},
#if REALM_ENABLE_SYNC
{"subscribe", wrap<subscribe>},
#endif
{"min", wrap<compute_aggregate_on_collection<ResultsClass<T>, AggregateFunc::Min>>},
{"max", wrap<compute_aggregate_on_collection<ResultsClass<T>, AggregateFunc::Max>>},
{"sum", wrap<compute_aggregate_on_collection<ResultsClass<T>, AggregateFunc::Sum>>},
@ -271,6 +282,28 @@ void ResultsClass<T>::is_valid(ContextType ctx, ObjectType this_object, Argument
return_value.set(get_internal<T, ResultsClass<T>>(this_object)->is_valid());
}
#if REALM_ENABLE_SYNC
template<typename T>
void ResultsClass<T>::subscribe(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
auto results = get_internal<T, ResultsClass<T>>(this_object);
auto realm = results->get_realm();
auto sync_config = realm->config().sync_config;
util::Optional<std::string> subscription_name;
if (args.count == 1) {
subscription_name = util::Optional<std::string>(Value::validated_to_string(ctx, args[0]));
}
else {
subscription_name = util::none;
}
auto subscription = partial_sync::subscribe(*results, subscription_name);
return_value.set(SubscriptionClass<T>::create_instance(ctx, std::move(subscription)));
}
#endif
template<typename T>
template<typename Fn>
void ResultsClass<T>::index_of(ContextType ctx, Fn& fn, Arguments args, ReturnValue &return_value) {
@ -343,13 +376,13 @@ void ResultsClass<T>::add_listener(ContextType ctx, U& collection, ObjectType th
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
auto token = collection.add_notification_callback([=](CollectionChangeSet const& change_set, std::exception_ptr exception) {
HANDLESCOPE
ValueType arguments[] {
static_cast<ObjectType>(protected_this),
CollectionClass<T>::create_collection_change_set(protected_ctx, change_set)
};
Function<T>::callback(protected_ctx, protected_callback, protected_this, 2, arguments);
});
HANDLESCOPE
ValueType arguments[] {
static_cast<ObjectType>(protected_this),
CollectionClass<T>::create_collection_change_set(protected_ctx, change_set)
};
Function<T>::callback(protected_ctx, protected_callback, protected_this, 2, arguments);
});
collection.m_notification_tokens.emplace_back(protected_callback, std::move(token));
}

View File

@ -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<T>::override_server(ContextType ctx, ObjectType this_object, A
}
}
template<typename T>
class Subscription : public partial_sync::Subscription {
public:
Subscription(partial_sync::Subscription s) : partial_sync::Subscription(std::move(s)) {}
Subscription(Subscription &&) = default;
std::vector<std::pair<Protected<typename T::Function>, partial_sync::SubscriptionNotificationToken>> m_notification_tokens;
};
template<typename T>
class SubscriptionClass : public ClassDefinition<T, Subscription<T>> {
using GlobalContextType = typename T::GlobalContext;
using ContextType = typename T::Context;
using FunctionType = typename T::Function;
using ObjectType = typename T::Object;
using ValueType = typename T::Value;
using String = js::String<T>;
using Object = js::Object<T>;
using Value = js::Value<T>;
using Function = js::Function<T>;
using ReturnValue = js::ReturnValue<T>;
using Arguments = js::Arguments<T>;
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<T> const properties = {
{"state", {wrap<get_state>, nullptr}},
{"error", {wrap<get_error>, nullptr}}
};
MethodMap<T> const methods = {
{"unsubscribe", wrap<unsubscribe>},
{"addListener", wrap<add_listener>},
{"removeListener", wrap<remove_listener>},
};
};
template<typename T>
typename T::Object SubscriptionClass<T>::create_instance(ContextType ctx, partial_sync::Subscription subscription) {
return create_object<T, SubscriptionClass<T>>(ctx, new Subscription<T>(std::move(subscription)));
}
template<typename T>
void SubscriptionClass<T>::get_state(ContextType ctx, ObjectType object, ReturnValue &return_value) {
auto subscription = get_internal<T, SubscriptionClass<T>>(object);
return_value.set(static_cast<int8_t>(subscription->state()));
}
template<typename T>
void SubscriptionClass<T>::get_error(ContextType ctx, ObjectType object, ReturnValue &return_value) {
auto subscription = get_internal<T, SubscriptionClass<T>>(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<typename T>
void SubscriptionClass<T>::unsubscribe(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
auto subscription = get_internal<T, SubscriptionClass<T>>(this_object);
partial_sync::unsubscribe(*subscription);
return_value.set_undefined();
}
template<typename T>
void SubscriptionClass<T>::add_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
auto subscription = get_internal<T, SubscriptionClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, args[0]);
Protected<FunctionType> protected_callback(ctx, callback);
Protected<ObjectType> protected_this(ctx, this_object);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
auto token = subscription->add_notification_callback([=]() {
HANDLESCOPE
ValueType arguments[2];
arguments[0] = static_cast<ObjectType>(protected_this),
arguments[1] = Value::from_number(ctx, static_cast<double>(subscription->state()));
Function::callback(protected_ctx, protected_callback, protected_this, 2, arguments);
});
subscription->m_notification_tokens.emplace_back(protected_callback, std::move(token));
}
template<typename T>
void SubscriptionClass<T>::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
auto subscription = get_internal<T, SubscriptionClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, args[0]);
auto protected_function = Protected<FunctionType>(ctx, callback);
auto& tokens = subscription->m_notification_tokens;
auto compare = [&](auto&& token) {
return typename Protected<FunctionType>::Comparator()(token.first, protected_function);
};
tokens.erase(std::remove_if(tokens.begin(), tokens.end(), compare), tokens.end());
}
template<typename T>
class SyncClass : public ClassDefinition<T, void*> {
using GlobalContextType = typename T::GlobalContext;

@ -1 +1 @@
Subproject commit 56f246831c6bd1bd33cf0f852d049b11e94e50e9
Subproject commit d5fe9a626c0adc0345d128201e0df8bb4004725d

View File

@ -44,7 +44,7 @@ var TESTS = {
MigrationTests: require('./migration-tests'),
EncryptionTests: require('./encryption-tests'),
ObjectIDTests: require('./object-id-tests'),
// GarbageCollectionTests: require('./garbage-collection'),
// Garbagecollectiontests: require('./garbage-collection'),
};
// If sync is enabled, run the sync tests
@ -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');
}
}

View File

@ -37,7 +37,7 @@ module.exports = {
TestCase.assertInstanceOf(obj.list, Realm.List);
TestCase.assertInstanceOf(obj.list, Realm.Collection);
});
TestCase.assertThrowsContaining(() => new Realm.List(), 'constructor');
TestCase.assertInstanceOf(Realm.List, Function);
},
@ -1223,4 +1223,83 @@ module.exports = {
TestCase.assertThrowsContaining(() => object.list.avg(),
"JS value must be of type 'string', got (undefined)");
},
testListNested: function() {
const realm = new Realm({schema: [schemas.ParentObject, schemas.NameObject]});
realm.write(() => {
realm.create('ParentObject', {
id: 1,
name: [
{ family: 'Larsen', given: ['Hans', 'Jørgen'], prefix: [] },
{ family: 'Hansen', given: ['Ib'], prefix: [] }
]
});
realm.create('ParentObject', {
id: 2,
name: [
{family: 'Petersen', given: ['Gurli', 'Margrete'], prefix: [] }
]
});
});
let objects = realm.objects('ParentObject');
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');
},
testListNestedFromJSON: function() {
let json = '{"id":1, "name": [{ "family": "Larsen", "given": ["Hans", "Jørgen"], "prefix": [] }, { "family": "Hansen", "given": ["Ib"], "prefix": [] }] }';
let parent = JSON.parse(json);
const realm = new Realm({schema: [schemas.ParentObject, schemas.NameObject]});
realm.write(() => {
realm.create('ParentObject', parent);
});
let objects = realm.objects('ParentObject');
TestCase.assertEqual(objects.length, 1);
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].prefix.length, 0);
TestCase.assertEqual(objects[0].name[1].given[0], 'Ib');
},
testMultipleLists: function() {
const realm = new Realm({schema: [schemas.MultiListObject]});
realm.write(() => {
realm.create('MultiListObject', { id: 0, list1: ["Hello"], list2: ["World"] });
realm.create('MultiListObject', { id: 1, list1: ["Foo"], list2: ["Bar"] });
});
let objects = realm.objects('MultiListObject');
TestCase.assertEqual(objects.length, 2);
TestCase.assertEqual(objects[0].id, 0);
TestCase.assertEqual(objects[0].list1.length, 1);
TestCase.assertEqual(objects[0].list1[0], "Hello");
TestCase.assertEqual(objects[0].list2.length, 1);
TestCase.assertEqual(objects[0].list2[0], "World");
TestCase.assertEqual(objects[1].id, 1);
TestCase.assertEqual(objects[1].list1.length, 1);
TestCase.assertEqual(objects[1].list1[0], "Foo");
TestCase.assertEqual(objects[1].list2.length, 1);
TestCase.assertEqual(objects[1].list2[0], "Bar");
}
};

View File

@ -0,0 +1,67 @@
/*
This script creates new nested objects into a new Realm.
*/
'use strict';
console.log("nested-list-helper started");
const username = process.argv[3];
const realmName = process.argv[4];
const realmModule = process.argv[5];
const Realm = require(realmModule);
let schemas = require(process.argv[2]);
function createObjects(user) {
const config = {
sync: { user,
url: `realm://localhost:9080/~/${realmName}`,
error: err => console.log(err)
},
schema: [schemas.ParentObject, schemas.NameObject],
};
const realm = new Realm(config);
realm.write(() => {
realm.create('ParentObject', {
id: 1,
name: [
{ family: 'Larsen', given: ['Hans', 'Jørgen'], prefix: [] },
{ family: 'Hansen', given: ['Ib'], prefix: [] }
]
});
realm.create('ParentObject', {
id: 2,
name: [
{family: 'Petersen', given: ['Gurli', 'Margrete'], prefix: [] }
]
});
});
console.log("JSON: " + JSON.stringify(realm.objects('ParentObject')));
let session = realm.syncSession;
return new Promise((resolve, reject) => {
let callback = (transferred, total) => {
if (transferred === total) {
session.removeProgressNotification(callback);
resolve(realm);
}
}
session.addProgressNotification('upload', 'forCurrentlyOutstandingWork', callback);
});
}
let registrationError;
Realm.Sync.User.register('http://localhost:9080', username, 'password')
.catch((error) => {
registrationError = JSON.stringify(error);
return Realm.Sync.User.login('http://localhost:9080', username, 'password')
})
.catch((error) => {
const loginError = JSON.stringify(error);
console.error(`nested-list-helper failed:\n User.register() error:\n${registrationError}\n User.login() error:\n${registrationError}`);
process.exit(-2);
})
.then((user) => createObjects(user))
.then(() => process.exit(0));

View File

@ -280,4 +280,30 @@ exports.LinkingObjectsObject = {
links: 'LinkingObjectsObject[]',
linkingObjects: {type: 'linkingObjects', objectType: 'LinkingObjectsObject', property: 'links'}
}
}
};
exports.ParentObject = {
name: 'ParentObject',
properties: {
id: 'int',
name: 'NameObject[]'
}
};
exports.NameObject = {
name: 'NameObject',
properties: {
family: 'string',
given: 'string[]',
prefix: 'string[]'
}
};
exports.MultiListObject = {
name: 'MultiListObject',
properties: {
'id': 'int',
'list1': 'string[]',
'list2': 'string[]'
}
};

View File

@ -24,6 +24,7 @@
const Realm = require('realm');
const TestCase = require('./asserts');
let schemas = require('./schemas');
const isNodeProccess = (typeof process === 'object' && process + '' === '[object process]');
@ -402,6 +403,49 @@ module.exports = {
});
},
testListNestedSync() {
if (!isNodeProccess) {
return;
}
const username = uuid();
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');
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());
});
});
});
},
testIncompatibleSyncedRealmOpen() {
let realm = "sync-v1.realm";
if (isNodeProccess) {
@ -523,7 +567,7 @@ module.exports = {
});
},
testProgressNotificationsForRealmConstructor() {
/* testProgressNotificationsForRealmConstructor() {
if (!isNodeProccess) {
return;
}
@ -555,7 +599,7 @@ module.exports = {
});
});
});
},
},*/
testProgressNotificationsUnregisterForRealmConstructor() {
if (!isNodeProccess) {
@ -707,8 +751,7 @@ module.exports = {
});
},
/* Disabled: waiting for new implementation
testPartialSync() {
testPartialSyncAnonymous_SubscriptionListener() {
// FIXME: try to enable for React Native
if (!isNodeProccess) {
return;
@ -733,14 +776,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) {