Merge branch 'master' of github.com:realm/realm-js into kneth/updating-tg-branch

This commit is contained in:
Kenneth Geisshirt 2017-09-13 11:18:57 +02:00
commit ce25143680
20 changed files with 681 additions and 88 deletions

1
.vscode/launch.json vendored
View File

@ -54,6 +54,7 @@
"type": "node",
"request": "attach",
"name": "Attach to Port",
"protocol": "legacy",
"address": "localhost",
"port": 5858
}

View File

@ -5,10 +5,13 @@
### Enhancements
* Improve performance of the RPC worker for chrome debugging.
* Added Progress API `realm.syncSession.addProgressNotification` and `realm.syncSession.removeProgressNotification`
* Added additional parameter for `Realm.open` and `Realm.openAsync` for download progress notifications
* Added `Realm.deleteFile` for deleting a Realm (#363).
* Added `Realm.deleteModel` for deleting a Realm model in a migration (#573).
### Bug fixes
* Adding missing TypeScript declation (#1283).
* Adding missing TypeScript definitions; Permissions (#1283), `setFeatureToken()`, and instructions (#1298).
1.11.1 Release notes (2017-9-1)
=============================================================

View File

@ -87,4 +87,5 @@ To finish adding your new function, you will have to add your function a few pla
* In `lib/index.d.ts` you add the TypeScript declaration
* Documentation is added in `docs/realm.js`
* Add your function to `lib/browser/index.js` in order to enable it in the Chrome Debugger
* Add your function to `lib/browser/index.js` in order to enable it in the Chrome Debugger
* Add an entry to `CHANGELOG.md` if applicable (Breaking changes/Enhancements/Bug fixes)

View File

@ -132,6 +132,12 @@ class Realm {
*/
delete(object) {}
/**
* Deletes a Realm model, including all of its objects.
* @param {string} name - the model name
*/
deleteModel(name) {}
/**
* **WARNING:** This will delete **all** objects in the Realm!
*/

View File

@ -334,6 +334,28 @@ class Session {
* @type {string}
*/
get state() {}
/**
* Register a progress notification callback on a session object
* @param {string} direction - The progress direction to register for.
* Can be either:
* - `download` - report download progress
* - `upload` - report upload progress
* @param {string} mode - The progress notification mode to use for the registration.
* Can be either:
* - `reportIndefinitely` - the registration will stay active until the callback is unregistered
* - `forCurrentlyOutstandingWork` - the registration will be active until only the currently transferable bytes are synced
* @param {callback(transferred, transferable)} callback - called with the following arguments:
* - `transferred` - the current number of bytes already transferred
* - `transferable` - the total number of transferable bytes (the number of bytes already transferred plus the number of bytes pending transfer)
*/
addProgressNotification(direction, mode, progressCallback) {}
/** Unregister a progress notification callback that was previously registered with addProgressNotification.
* Calling the function multiple times with the same callback is ignored.
* @param {callback(transferred, transferable)} callback - a previously registered progress callback
*/
removeProgressNotification(progressCallback) {}
}

View File

@ -131,6 +131,7 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [
// Mutating methods:
util.createMethods(Realm.prototype, objectTypes.REALM, [
'delete',
'deleteModel',
'deleteAll',
'write',
'compact',
@ -187,7 +188,8 @@ Object.defineProperties(Realm, {
},
},
_waitForDownload: {
value: function(_config, callback) {
value: function(_config, sessionCallback, callback) {
sessionCallback();
callback();
}
},

View File

@ -22,7 +22,9 @@ import { keys, objectTypes } from './constants';
import { getterForProperty, createMethods } from './util';
import { deserialize } from './rpc';
export default class Session { }
export default class Session {
}
Object.defineProperties(Session.prototype, {
url: { get: getterForProperty('url') },
@ -31,7 +33,9 @@ Object.defineProperties(Session.prototype, {
createMethods(Session.prototype, objectTypes.SESSION, [
'_refreshAccessToken',
'_simulateError'
'_simulateError',
'addProgressNotification',
'removeProgressNotification'
]);
export function createSession(realmId, info) {

View File

@ -40,7 +40,7 @@ export function createMethod(type, name, mutates) {
let id = this[keys.id];
if (!realmId || !id) {
throw new TypeError(name + ' method was not called a Realm object!');
throw new TypeError(name + ' method was not called on a Realm object!');
}
if (this[keys.type] !== type) {
throw new TypeError(name + ' method was called on an object of the wrong type!');

View File

@ -43,28 +43,55 @@ module.exports = function(realmConstructor) {
//Add async open API
Object.defineProperties(realmConstructor, getOwnPropertyDescriptors({
open(config) {
return new Promise((resolve, reject) => {
realmConstructor._waitForDownload(config, (error) => {
if (error) {
reject(error);
}
else {
try {
let syncedRealm = new this(config);
//FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented
setTimeout(() => { resolve(syncedRealm); }, 1);
} catch (e) {
reject(e);
let syncSession;
let promise = new Promise((resolve, reject) => {
realmConstructor._waitForDownload(config,
(session) => {
syncSession = session;
},
(error) => {
if (error) {
setTimeout(() => { reject(error); }, 1);
}
}
});
else {
try {
let syncedRealm = new this(config);
//FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented
setTimeout(() => { resolve(syncedRealm); }, 1);
} catch (e) {
reject(e);
}
}
});
});
promise.progress = (callback) => {
if (syncSession) {
syncSession.addProgressNotification('download', 'forCurrentlyOutstandingWork', callback);
}
return promise;
};
return promise;
},
openAsync(config, callback) {
realmConstructor._waitForDownload(config, (error) => {
openAsync(config, progressCallback, callback) {
if (!callback) {
callback = progressCallback;
progressCallback = null;
}
realmConstructor._waitForDownload(config,
(syncSession) => {
if (progressCallback) {
syncSession.addProgressNotification('download', 'forCurrentlyOutstandingWork', progressCallback);;
}
},
(error) => {
if (error) {
callback(error);
setTimeout(() => { callback(error); }, 1);
}
else {
try {

30
lib/index.d.ts vendored
View File

@ -332,6 +332,10 @@ declare namespace Realm.Sync {
ssl_trust_certificate_path?: string;
}
type ProgressNotificationCallback = (transferred: number, transferable: number) => void;
type ProgressDirection = 'download' | 'upload';
type ProgressMode = 'reportIndefinitely' | 'forCurrentlyOutstandingWork';
/**
* Session
* @see { @link https://realm.io/docs/javascript/latest/api/Realm.Sync.Session.html }
@ -341,6 +345,9 @@ declare namespace Realm.Sync {
readonly state: 'invalid' | 'active' | 'inactive';
readonly url: string;
readonly user: User;
addProgressNotification(direction: ProgressDirection, mode: ProgressMode, progressCallback: ProgressNotificationCallback): void;
removeProgressNotification(progressCallback: ProgressNotificationCallback): void;
}
/**
@ -367,10 +374,14 @@ declare namespace Realm.Sync {
function removeAllListeners(name?: string): void;
function removeListener(regex: string, name: string, changeCallback: (changeEvent: ChangeEvent) => void): void;
function setLogLevel(logLevel: 'all' | 'trace' | 'debug' | 'detail' | 'info' | 'warn' | 'error' | 'fatal' | 'off'): void;
function setFeatureToken(token: string): void;
/**
* @deprecated, to be removed in 2.0
*/
function setAccessToken(accessToken: string): void;
type Instruction = {
type: 'INSERT' | 'SET' | 'DELETE' | 'CLEAR' | 'LIST_SET' | 'LIST_INSERT' | 'LIST_ERASE' | 'LIST_CLEAR' | 'ADD_TYPE' | 'ADD_PROPERTIES'
type: 'INSERT' | 'SET' | 'DELETE' | 'CLEAR' | 'LIST_SET' | 'LIST_INSERT' | 'LIST_ERASE' | 'LIST_CLEAR' | 'ADD_TYPE' | 'ADD_PROPERTIES' | 'CHANGE_IDENTITY' | 'SWAP_IDENTITY'
object_type: string,
identity: string,
values: any | undefined
@ -402,6 +413,11 @@ declare namespace Realm.Sync {
}
}
interface ProgressPromise extends Promise<Realm> {
progress(callback: Realm.Sync.ProgressNotificationCallback) : Promise<Realm>
}
declare class Realm {
static defaultPath: string;
@ -422,17 +438,20 @@ declare class Realm {
*/
static schemaVersion(path: string, encryptionKey?: ArrayBuffer | ArrayBufferView): number;
/**
* Open a realm asynchronously with a promise. If the realm is synced, it will be fully synchronized before it is available.
* @param {Configuration} config
*/
static open(config: Realm.Configuration): Promise<Realm>
static open(config: Realm.Configuration): ProgressPromise;
/**
* Open a realm asynchronously with a callback. If the realm is synced, it will be fully synchronized before it is available.
* @param {Configuration} config
* @param {ProgressNotificationCallback} progressCallback? a progress notification callback for 'download' direction and 'forCurrentlyOutstandingWork' mode
* @param {Function} callback will be called when the realm is ready.
*/
static openAsync(config: Realm.Configuration, callback: (error: any, realm: Realm) => void): void
static openAsync(config: Realm.Configuration, progressCallback?: Realm.Sync.ProgressNotificationCallback, callback: (error: any, realm: Realm) => void): void
/**
* Delete the Realm file for the given configuration.
@ -469,6 +488,11 @@ declare class Realm {
*/
delete(object: Realm.Object | Realm.Object[] | Realm.List<any> | Realm.Results<any> | any): void;
/**
* @returns void
*/
deleteModel(name: string): void;
/**
* @returns void
*/

View File

@ -86,7 +86,7 @@ function getSpecialPurposeRealm(user, realmName, schema) {
setTimeout(() => {}, 1);
if (error) {
reject(error);
setTimeout(() => reject(error), 1);
}
else {
try {
@ -95,7 +95,7 @@ function getSpecialPurposeRealm(user, realmName, schema) {
//FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented (#1255)
setTimeout(() => resolve(syncedRealm), 1);
} catch (e) {
reject(e);
setTimeout(() => reject(e), 1);
}
}
});

View File

@ -28,7 +28,7 @@ function node_require(module) {
function checkTypes(args, types) {
args = Array.prototype.slice.call(args);
for (var i = 0; i < types.length; ++i) {
if (typeof args[i] !== types[i]) {
if (args.length > i && typeof args[i] !== types[i]) {
throw new TypeError('param ' + i + ' must be of type ' + types[i]);
}
}
@ -162,7 +162,11 @@ const staticMethods = {
adminUser(token, server) {
checkTypes(arguments, ['string', 'string']);
return this._adminUser(server, token);
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
return this.createUser(server || '', uuid, token, true);
},
register(server, username, password, callback) {

View File

@ -48,7 +48,11 @@ download_server() {
}
start_server() {
./object-server-for-testing/start-object-server.command &
#disabled ROS logging
sh ./object-server-for-testing/start-object-server.command &> /dev/null &
#enabled ROS logging
#sh ./object-server-for-testing/start-object-server.command &
SERVER_PID=$!
}

View File

@ -180,7 +180,7 @@ public:
static void remove_all_listeners(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void close(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void compact(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void delete_model(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
// properties
static void get_empty(ContextType, ObjectType, ReturnValue &);
@ -235,6 +235,7 @@ public:
{"removeAllListeners", wrap<remove_all_listeners>},
{"close", wrap<close>},
{"compact", wrap<compact>},
{"deleteModel", wrap<delete_model>},
};
PropertyMap<T> const properties = {
@ -559,6 +560,17 @@ void RealmClass<T>::delete_file(ContextType ctx, FunctionType, ObjectType this_o
}
template<typename T>
void RealmClass<T>::delete_model(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
ValueType value = arguments[0];
SharedRealm& realm = *get_internal<T, RealmClass<T>>(this_object);
std::string model_name = Value::validated_to_string(ctx, value, "deleteModel");
ObjectStore::delete_data_for_object(realm->read_group(), model_name);
}
template<typename T>
void RealmClass<T>::get_default_path(ContextType ctx, ObjectType object, ReturnValue &return_value) {
return_value.set(realm::js::default_path());
@ -619,11 +631,16 @@ void RealmClass<T>::get_sync_session(ContextType ctx, ObjectType object, ReturnV
template<typename T>
void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 2);
auto callback_function = Value::validated_to_function(ctx, arguments[1]);
validate_argument_count(argc, 2, 3);
auto config_object = Value::validated_to_object(ctx, arguments[0]);
auto callback_function = Value::validated_to_function(ctx, arguments[argc - 1]);
ValueType session_callback = Value::from_null(ctx);
if (argc == 3) {
session_callback = Value::validated_to_function(ctx, arguments[1]);
}
#if REALM_ENABLE_SYNC
auto config_object = Value::validated_to_object(ctx, arguments[0]);
ValueType sync_config_value = Object::get_property(ctx, config_object, "sync");
if (!Value::is_undefined(ctx, sync_config_value)) {
realm::Realm::Config config;
@ -694,6 +711,14 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType,
std::shared_ptr<SyncUser> user = sync_config->user;
if (user && user->state() != SyncUser::State::Error) {
if (auto session = user->session_for_on_disk_path(config.path)) {
if (!Value::is_null(ctx, session_callback)) {
FunctionType session_callback_func = Value::to_function(ctx, session_callback);
auto syncSession = create_object<T, SessionClass<T>>(ctx, new WeakSession(session));
ValueType callback_arguments[1];
callback_arguments[0] = syncSession;
Function<T>::callback(protected_ctx, session_callback_func, protected_this, 1, callback_arguments);
}
if (progressFuncDefined) {
session->register_progress_notifier(std::move(progressFunc), SyncSession::NotifierType::download, false);
}

View File

@ -175,6 +175,7 @@ class SessionClass : public ClassDefinition<T, WeakSession> {
public:
std::string const name = "Session";
using ProgressHandler = void(uint64_t transferred_bytes, uint64_t transferrable_bytes);
static FunctionType create_constructor(ContextType);
@ -185,6 +186,8 @@ public:
static void simulate_error(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void refresh_access_token(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void add_progress_notification(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &);
static void remove_progress_notification(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &);
PropertyMap<T> const properties = {
{"config", {wrap<get_config>, nullptr}},
@ -195,7 +198,9 @@ public:
MethodMap<T> const methods = {
{"_simulateError", wrap<simulate_error>},
{"_refreshAccessToken", wrap<refresh_access_token>}
{"_refreshAccessToken", wrap<refresh_access_token>},
{"addProgressNotification", wrap<add_progress_notification>},
{"removeProgressNotification", wrap<remove_progress_notification>},
};
};
@ -323,7 +328,83 @@ void SessionClass<T>::refresh_access_token(ContextType ctx, FunctionType, Object
}
template<typename T>
class SyncClass : public ClassDefinition<T, void *> {
void SessionClass<T>::add_progress_notification(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 3);
if (auto session = get_internal<T, SessionClass<T>>(this_object)->lock()) {
std::string direction = Value::validated_to_string(ctx, arguments[0], "direction");
std::string mode = Value::validated_to_string(ctx, arguments[1], "mode");
SyncSession::NotifierType notifierType;
if (direction == "download") {
notifierType = SyncSession::NotifierType::download;
}
else if (direction == "upload") {
notifierType = SyncSession::NotifierType::upload;
}
else {
throw std::invalid_argument("Invalid argument 'direction'. Only 'download' and 'upload' progress notification directions are supported");
}
bool is_streaming = false;
if (mode == "reportIndefinitely") {
is_streaming = true;
}
else if (mode == "forCurrentlyOutstandingWork") {
is_streaming = false;
}
else {
throw std::invalid_argument("Invalid argument 'mode'. Only 'reportIndefinitely' and 'forCurrentlyOutstandingWork' progress notification modes are supported");
}
auto callback_function = Value::validated_to_function(ctx, arguments[2], "callback");
Protected<FunctionType> protected_callback(ctx, callback_function);
Protected<ObjectType> protected_this(ctx, this_object);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
std::function<ProgressHandler> progressFunc;
EventLoopDispatcher<ProgressHandler> progress_handler([=](uint64_t transferred_bytes, uint64_t transferrable_bytes) {
HANDLESCOPE
ValueType callback_arguments[2];
callback_arguments[0] = Value::from_number(protected_ctx, transferred_bytes);
callback_arguments[1] = Value::from_number(protected_ctx, transferrable_bytes);
Function<T>::callback(protected_ctx, protected_callback, typename T::Object(), 2, callback_arguments);
});
progressFunc = std::move(progress_handler);
auto registrationToken = session->register_progress_notifier(std::move(progressFunc), notifierType, false);
auto syncSession = create_object<T, SessionClass<T>>(ctx, new WeakSession(session));
PropertyAttributes attributes = ReadOnly | DontEnum | DontDelete;
Object::set_property(ctx, callback_function, "_syncSession", syncSession, attributes);
Object::set_property(ctx, callback_function, "_registrationToken", Value::from_number(protected_ctx, registrationToken), attributes);
}
}
template<typename T>
void SessionClass<T>::remove_progress_notification(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
auto callback_function = Value::validated_to_function(ctx, arguments[0], "callback");
auto syncSessionProp = Object::get_property(ctx, callback_function, "_syncSession");
if (Value::is_undefined(ctx, syncSessionProp) || Value::is_null(ctx, syncSessionProp)) {
return;
}
auto syncSession = Value::validated_to_object(ctx, syncSessionProp);
auto registrationToken = Object::get_property(ctx, callback_function, "_registrationToken");
if (auto session = get_internal<T, SessionClass<T>>(syncSession)->lock()) {
auto reg = Value::validated_to_number(ctx, registrationToken);
session->unregister_progress_notifier(reg);
}
}
template<typename T>
class SyncClass : public ClassDefinition<T, void*> {
using GlobalContextType = typename T::GlobalContext;
using ContextType = typename T::Context;
using FunctionType = typename T::Function;
@ -343,6 +424,7 @@ public:
static void set_sync_log_level(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
// private
static std::function<SyncBindSessionHandler> session_bind_callback(ContextType ctx, ObjectType sync_constructor);
static void populate_sync_config(ContextType, ObjectType realm_constructor, ObjectType config_object, Realm::Config&);
// static properties
@ -382,6 +464,24 @@ void SyncClass<T>::set_sync_log_level(ContextType ctx, FunctionType, ObjectType
realm::SyncManager::shared().set_log_level(log_level_2);
}
template<typename T>
std::function<SyncBindSessionHandler> SyncClass<T>::session_bind_callback(ContextType ctx, ObjectType sync_constructor)
{
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
Protected<ObjectType> protected_sync_constructor(ctx, sync_constructor);
return EventLoopDispatcher<SyncBindSessionHandler>([protected_ctx, protected_sync_constructor](const std::string& path, const realm::SyncConfig& config, std::shared_ptr<SyncSession>) {
HANDLESCOPE
ObjectType user_constructor = Object::validated_get_object(protected_ctx, protected_sync_constructor, "User");
FunctionType refreshAccessToken = Object::validated_get_function(protected_ctx, user_constructor, "_refreshAccessToken");
ValueType arguments[3];
arguments[0] = create_object<T, UserClass<T>>(protected_ctx, new SharedUser(config.user));
arguments[1] = Value::from_string(protected_ctx, path);
arguments[2] = Value::from_string(protected_ctx, config.realm_url);
Function::call(protected_ctx, refreshAccessToken, 3, arguments);
});
}
template<typename T>
void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constructor, ObjectType config_object, Realm::Config& config)
{
@ -391,21 +491,8 @@ void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constr
} else if (!Value::is_undefined(ctx, sync_config_value)) {
auto sync_config_object = Value::validated_to_object(ctx, sync_config_value);
ObjectType sync_constructor = Object::validated_get_object(ctx, realm_constructor, std::string("Sync"));
Protected<ObjectType> protected_sync(ctx, sync_constructor);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
EventLoopDispatcher<SyncBindSessionHandler> bind([protected_ctx, protected_sync](const std::string& path, const realm::SyncConfig& config, std::shared_ptr<SyncSession>) {
HANDLESCOPE
ObjectType user_constructor = Object::validated_get_object(protected_ctx, protected_sync, std::string("User"));
FunctionType refreshAccessToken = Object::validated_get_function(protected_ctx, user_constructor, std::string("_refreshAccessToken"));
ValueType arguments[3];
arguments[0] = create_object<T, UserClass<T>>(protected_ctx, new SharedUser(config.user));
arguments[1] = Value::from_string(protected_ctx, path.c_str());
arguments[2] = Value::from_string(protected_ctx, config.realm_url.c_str());
Function::call(protected_ctx, refreshAccessToken, 3, arguments);
});
ObjectType sync_constructor = Object::validated_get_object(ctx, realm_constructor, "Sync");
auto bind = session_bind_callback(ctx, sync_constructor);
std::function<SyncSessionErrorHandler> error_handler;
ValueType error_func = Object::get_property(ctx, sync_config_object, "error");
@ -455,6 +542,5 @@ void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constr
}
}
}
} // js
} // realm

View File

@ -2,33 +2,49 @@
This script creates 3 new objects into a new realm. These are objects are validated to exists by the download api tests.
*/
'use strict';
console.log("download-api-helper started");
const username = process.argv[2];
const realmName = process.argv[3];
const realmModule = process.argv[4];
var Realm = require(realmModule);
Realm.Sync.User.register('http://localhost:9080', username, 'password', (error, user) => {
function createObjects(user) {
const config = {
sync: { user,
url: `realm://localhost:9080/~/${realmName}`,
error: err => console.log(err)
},
schema: [{ name: 'Dog', properties: { name: 'string' } }]
};
var realm = new Realm(config);
realm.write(() => {
for (let i = 1; i <= 3; i++) {
realm.create('Dog', { name: `Lassy ${i}` });
}
});
console.log("Dogs count " + realm.objects('Dog').length);
setTimeout(() => process.exit(0), 3000);
}
Realm.Sync.User.register('http://localhost:9080', username, 'password', (error, registeredUser) => {
if (error) {
console.log(error);
process.exit(-2);
} else {
const config = {
sync: { user, url: `realm://localhost:9080/~/${realmName}`, error: err => console.log(err) },
schema: [{ name: 'Dog', properties: { name: 'string' } }]
};
var realm = new Realm(config);
realm.write(() => {
for (let i = 1; i <= 3; i++) {
realm.create('Dog', { name: `Lassy ${i}` });
const registrationError = JSON.stringify(error);
Realm.Sync.User.login('http://localhost:9080', username, 'password', (err, loggedUser) => {
if (err) {
const loginError = JSON.stringify(err);
console.error("download-api-helper failed:\n User.register() error:\n" + registrationError + "\n User.login() error:\n" + loginError);
process.exit(-2);
}
else {
createObjects(loggedUser);
}
});
console.log("Dogs count " + realm.objects('Dog').length);
setTimeout(() => process.exit(0), 3000);
}
else {
createObjects(registeredUser);
}
});
});

View File

@ -158,4 +158,170 @@ module.exports = {
}
});
},
testDeleteModelMigration: function() {
const schema = [{
name: 'TestObject',
properties: {
prop0: 'string',
prop1: 'int',
}
}];
var realm = new Realm({schema: schema});
realm.write(function() {
realm.create('TestObject', ['stringValue', 1]);
});
realm.close();
realm = new Realm({schema: [], schemaVersion: 1});
TestCase.assertEqual(realm.schema.length, 0); // no models
realm.close(); // this won't delete the model
realm = new Realm({schema: schema, schemaVersion: 2});
TestCase.assertEqual(realm.objects('TestObject').length, 1); // the model objects are still there
realm.close();
// now delete the model explicitly, which should delete the objects too
realm = new Realm({schema: [], schemaVersion: 3, migration: function(oldRealm, newRealm) {
newRealm.deleteModel('TestObject');
}});
TestCase.assertEqual(realm.schema.length, 0); // no models
realm.close();
realm = new Realm({schema: schema, schemaVersion: 4});
TestCase.assertEqual(realm.objects('TestObject').length, 0);
realm.close();
},
testDeleteModelInSchema: function() {
const schema = [{
name: 'TestObject',
properties: {
prop0: 'string',
prop1: 'int',
}
}];
var realm = new Realm({schema: schema});
realm.write(function() {
realm.create('TestObject', ['stringValue', 1]);
});
realm.close();
// now delete the model explicitly, but it should remain as it's still in the schema
// only the rows should get deleted
realm = new Realm({schema: schema, schemaVersion: 1, migration: function(oldRealm, newRealm) {
newRealm.deleteModel('TestObject');
}});
TestCase.assertEqual(realm.schema.length, 1); // model should remain
TestCase.assertEqual(realm.objects('TestObject').length, 0); // objects should be gone
realm.close();
realm = new Realm({schema: schema, schemaVersion: 2});
TestCase.assertEqual(realm.objects('TestObject').length, 0);
realm.close();
},
testDeleteModelIgnoreNotExisting: function() {
const schema = [{
name: 'TestObject',
properties: {
prop0: 'string',
prop1: 'int',
}
}];
var realm = new Realm({schema: schema});
realm.write(function() {
realm.create('TestObject', ['stringValue', 1]);
});
realm.close();
// non-existing models should be ignore on delete
realm = new Realm({schema: schema, schemaVersion: 1, migration: function(oldRealm, newRealm) {
newRealm.deleteModel('NonExistingModel');
}});
realm.close();
realm = new Realm({schema: schema, schemaVersion: 2});
TestCase.assertEqual(realm.objects('TestObject').length, 1);
realm.close();
},
testDeleteModelWithRelationship: function() {
const ShipSchema = {
name: 'Ship',
properties: {
ship_name: 'string',
captain: 'Captain'
}
};
const CaptainSchema = {
name: 'Captain',
properties: {
captain_name: 'string',
ships: { type: 'linkingObjects', objectType: 'Ship', property: 'captain' }
}
};
var realm = new Realm({schema: [ShipSchema, CaptainSchema]});
realm.write(function() {
realm.create('Ship', {
ship_name: 'My Ship',
captain: {
captain_name: 'John Doe'
}
});
});
TestCase.assertEqual(realm.objects('Captain').length, 1);
TestCase.assertEqual(realm.objects('Ship').length, 1);
TestCase.assertEqual(realm.objects('Ship')[0].captain.captain_name, "John Doe");
TestCase.assertEqual(realm.objects('Captain')[0].ships[0].ship_name, "My Ship");
realm.close();
realm = new Realm({schema: [ShipSchema, CaptainSchema], schemaVersion: 1, migration: function(oldRealm, newRealm) {
TestCase.assertThrows(function(e) {
// deleting a model which is target of linkingObjects results in an exception
newRealm.deleteModel('Captain');
console.log(e);
}, "Table is target of cross-table link columns");
}});
TestCase.assertEqual(realm.objects('Captain').length, 1);
TestCase.assertEqual(realm.objects('Ship').length, 1);
realm.close();
realm = new Realm({schema: [ShipSchema, CaptainSchema], schemaVersion: 2, migration: function(oldRealm, newRealm) {
// deleting a model which isn't target of linkingObjects works fine
newRealm.deleteModel('Ship');
}});
TestCase.assertEqual(realm.objects('Captain').length, 1);
TestCase.assertEqual(realm.objects('Ship').length, 0);
TestCase.assertEqual(realm.objects('Captain')[0].ships.length, 0);
realm.close();
},
};

View File

@ -59,6 +59,7 @@ module.exports = {
return createUsersWithTestRealms(1)
.then(([user]) => {
return user.applyPermissions({ userId: '*' }, `/${user.identity}/test`, 'read')
.then(wait(100))
.then(() => user.getGrantedPermissions('any'))
.then(permissions => {
TestCase.assertEqual(permissions[1].path, `/${user.identity}/test`);

View File

@ -26,7 +26,7 @@ const Realm = require('realm');
const TestCase = require('./asserts');
const isNodeProccess = (typeof process === 'object' && process + '' === '[object process]');
console.log("isnode " + isNodeProccess + " typeof " + (typeof(process) === 'object'));
function node_require(module) {
return require(module);
}
@ -84,13 +84,23 @@ function runOutOfProcess(nodeJsFilePath) {
fs.appendFileSync(tmpFile.fd, content, { encoding: 'utf8' });
nodeArgs[0] = tmpFile.name;
return new Promise((resolve, reject) => {
const child = execFile('node', nodeArgs, { cwd: tmpDir.name }, (error, stdout, stderr) => {
if (error) {
reject(new Error(`Error executing ${nodeJsFilePath} Error: ${error}`));
try {
console.log('runOutOfProcess command\n node ' + nodeArgs.join(" "));
const child = execFile('node', nodeArgs, { cwd: tmpDir.name }, (error, stdout, stderr) => {
if (error) {
console.error("runOutOfProcess failed\n" + error);
reject(new Error(`Running ${nodeJsFilePath} failed. error: ${error}`));
return;
}
console.log('runOutOfProcess success\n' + stdout);
resolve();
});
}
resolve();
});
})
catch (e) {
reject(e);
};
});
}
module.exports = {
@ -150,7 +160,7 @@ module.exports = {
const realmName = uuid();
const expectedObjectsCount = 3;
runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => {
return promisifiedLogin('http://localhost:9080', username, 'password').then(user => {
const accessTokenRefreshed = this;
@ -186,7 +196,7 @@ module.exports = {
const realmName = uuid();
const expectedObjectsCount = 3;
runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => {
return promisifiedLogin('http://localhost:9080', username, 'password').then(user => {
return new Promise((resolve, reject) => {
@ -239,7 +249,7 @@ module.exports = {
const realmName = uuid();
const expectedObjectsCount = 3;
runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => {
return promisifiedLogin('http://localhost:9080', username, 'password').then(user => {
const accessTokenRefreshed = this;
@ -275,7 +285,7 @@ module.exports = {
const realmName = uuid();
const expectedObjectsCount = 3;
runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => {
return promisifiedLogin('http://localhost:9080', username, 'password').then(user => {
return new Promise((resolve, reject) => {
@ -322,7 +332,7 @@ module.exports = {
const realmName = uuid();
const expectedObjectsCount = 3;
runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => {
return promisifiedLogin('http://localhost:9080', username, 'password').then(user => {
return new Promise((resolve, reject) => {
@ -449,5 +459,191 @@ module.exports = {
session._simulateError(123, 'simulated error');
});
});
}
},
testProgressNotificationsForRealmConstructor() {
if (!isNodeProccess) {
return Promise.resolve();
}
const username = uuid();
const realmName = uuid();
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => {
return promisifiedLogin('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 realm = new Realm(config);
const progressCallback = (transferred, total) => {
resolve();
};
realm.syncSession.addProgressNotification('download', 'reportIndefinitely', progressCallback);
setTimeout(function() {
reject("Progress Notifications API failed to call progress callback for Realm constructor");
}, 5000);
});
});
});
},
testProgressNotificationsUnregisterForRealmConstructor() {
if (!isNodeProccess) {
return Promise.resolve();
}
const username = uuid();
const realmName = uuid();
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => {
return promisifiedLogin('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 realm = new Realm(config);
let unregisterFunc;
let writeDataFunc = () => {
realm.write(() => {
for (let i = 1; i <= 3; i++) {
realm.create('Dog', { name: `Lassy ${i}` });
}
});
}
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;
//unregister and write some new data.
if (syncFinished) {
failOnCall = true;
unregisterFunc();
//use second callback to wait for sync finished
realm.syncSession.addProgressNotification('upload', 'reportIndefinitely', (x, y) => {
if (x === y) {
resolve();
}
});
writeDataFunc();
}
};
realm.syncSession.addProgressNotification('upload', 'reportIndefinitely', progressCallback);
unregisterFunc = () => {
realm.syncSession.removeProgressNotification(progressCallback);
};
writeDataFunc();
});
});
});
},
testProgressNotificationsForRealmOpen() {
if (!isNodeProccess) {
return Promise.resolve();
}
const username = uuid();
const realmName = uuid();
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => {
return promisifiedLogin('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;
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);
});
});
});
},
testProgressNotificationsForRealmOpenAsync() {
if (!isNodeProccess) {
return Promise.resolve();
}
const username = uuid();
const realmName = uuid();
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => {
return promisifiedLogin('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;
Realm.openAsync(config,
(transferred, total) => {
progressCalled = true;
},
(error, realm) => {
if (error) {
reject(error);
return;
}
TestCase.assertTrue(progressCalled);
resolve();
});
setTimeout(function() {
reject("Progress Notifications API failed to call progress callback for Realm constructor");
}, 5000);
});
});
});
},
}

View File

@ -20,6 +20,7 @@
/* eslint-disable no-console */
'use strict';
const isNodeProccess = (typeof process === 'object' && process + '' === '[object process]');
const fs = require('fs');
const path = require('path');
@ -28,7 +29,11 @@ const Realm = require('realm');
const RealmTests = require('../js');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
const isDebuggerAttached = typeof v8debug === 'object';
let isDebuggerAttached = typeof v8debug === 'object';
if (!isDebuggerAttached && isNodeProccess) {
isDebuggerAttached = /--debug|--inspect/.test(process.execArgv.join(' '));
}
if (isDebuggerAttached) {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000000;
}