Ensure Lists and Results live-update in Chrome

Results needed to live-update during a transaction (not just as the end), and Lists needed to update on deletions as well.
This commit is contained in:
Scott Kyle 2015-10-28 10:21:32 -07:00
parent 7a02ff29a1
commit 0eb3b49970
6 changed files with 114 additions and 32 deletions

View File

@ -7,7 +7,6 @@ let propTypes = {};
[ [
'id', 'id',
'realm', 'realm',
'resize',
'type', 'type',
].forEach(function(name) { ].forEach(function(name) {
keys[name] = Symbol(); keys[name] = Symbol();

View File

@ -9,7 +9,6 @@ const util = require('./util');
const {keys, propTypes, objectTypes} = constants; const {keys, propTypes, objectTypes} = constants;
const listenersKey = Symbol(); const listenersKey = Symbol();
const resultsKey = Symbol();
// TODO: DATA // TODO: DATA
rpc.registerTypeConverter(propTypes.DATE, (_, info) => new Date(info.value)); rpc.registerTypeConverter(propTypes.DATE, (_, info) => new Date(info.value));
@ -40,7 +39,6 @@ class Realm {
this[keys.realm] = realmId; this[keys.realm] = realmId;
this[keys.type] = objectTypes.REALM; this[keys.type] = objectTypes.REALM;
this[listenersKey] = []; this[listenersKey] = [];
this[resultsKey] = [];
[ [
'path', 'path',
@ -60,7 +58,6 @@ class Realm {
this[listenersKey].push(callback); this[listenersKey].push(callback);
} }
removeListener(name, callback) { removeListener(name, callback) {
if (typeof callback != 'function') { if (typeof callback != 'function') {
throw new Error('Realm.addListener must be passed a function!'); throw new Error('Realm.addListener must be passed a function!');
@ -68,10 +65,11 @@ class Realm {
if (name != 'change') { if (name != 'change') {
throw new Error("Only 'change' notification is supported."); throw new Error("Only 'change' notification is supported.");
} }
var index = 0;
let index = 0;
while ((index = this[listenersKey].indexOf(callback, index)) != -1) { while ((index = this[listenersKey].indexOf(callback, index)) != -1) {
this[listenersKey].splice(index, 1); this[listenersKey].splice(index, 1);
}; }
} }
removeAllListeners(name) { removeAllListeners(name) {
@ -81,14 +79,6 @@ class Realm {
this[listenersKey] = []; this[listenersKey] = [];
} }
objects() {
let method = util.createMethod(objectTypes.REALM, 'objects');
let results = method.apply(this, arguments);
this[resultsKey].push(results);
return results;
}
write(callback) { write(callback) {
let realmId = this[keys.realm]; let realmId = this[keys.realm];
@ -105,27 +95,30 @@ class Realm {
callback(); callback();
} catch (e) { } catch (e) {
rpc.cancelTransaction(realmId); rpc.cancelTransaction(realmId);
util.fireMutationListeners(realmId);
throw e; throw e;
} }
rpc.commitTransaction(realmId); rpc.commitTransaction(realmId);
for (let results of this[resultsKey]) {
results[keys.resize]();
}
for (let callback of this[listenersKey]) { for (let callback of this[listenersKey]) {
callback(this, 'change'); callback(this, 'change');
} }
} }
} }
// Non-mutating methods:
util.createMethods(Realm.prototype, objectTypes.REALM, [ util.createMethods(Realm.prototype, objectTypes.REALM, [
'close', 'close',
'objects',
]);
// Mutating methods:
util.createMethods(Realm.prototype, objectTypes.REALM, [
'create', 'create',
'delete', 'delete',
'deleteAll', 'deleteAll',
]); ], true);
Object.defineProperties(Realm, { Object.defineProperties(Realm, {
Types: { Types: {

View File

@ -4,8 +4,11 @@ const constants = require('./constants');
const rpc = require('./rpc'); const rpc = require('./rpc');
const {keys} = constants; const {keys} = constants;
const mutationListeners = {};
module.exports = { module.exports = {
addMutationListener,
fireMutationListeners,
createList, createList,
createMethods, createMethods,
createMethod, createMethod,
@ -13,6 +16,20 @@ module.exports = {
setterForProperty, setterForProperty,
}; };
function addMutationListener(realmId, callback) {
let listeners = mutationListeners[realmId] || (mutationListeners[realmId] = new Set());
listeners.add(callback);
}
function fireMutationListeners(realmId) {
let listeners = mutationListeners[realmId];
if (listeners) {
for (let callback of listeners) {
callback();
}
}
}
function createList(prototype, realmId, info, mutable) { function createList(prototype, realmId, info, mutable) {
let list = Object.create(prototype); let list = Object.create(prototype);
let size = 0; let size = 0;
@ -26,7 +43,7 @@ function createList(prototype, realmId, info, mutable) {
}, },
}); });
list[keys.resize] = function(length) { let resize = function(length) {
if (length == null) { if (length == null) {
length = list.length; length = list.length;
} }
@ -66,24 +83,26 @@ function createList(prototype, realmId, info, mutable) {
list[keys.realm] = realmId; list[keys.realm] = realmId;
list[keys.id] = info.id; list[keys.id] = info.id;
list[keys.type] = info.type; list[keys.type] = info.type;
list[keys.resize](info.size);
resize(info.size);
addMutationListener(realmId, resize);
return list; return list;
} }
function createMethods(prototype, type, methodNames, resize) { function createMethods(prototype, type, methodNames, mutates) {
let props = {}; let props = {};
for (let name of methodNames) { for (let name of methodNames) {
props[name] = { props[name] = {
value: createMethod(type, name, resize), value: createMethod(type, name, mutates),
}; };
} }
Object.defineProperties(prototype, props); Object.defineProperties(prototype, props);
} }
function createMethod(type, name, resize) { function createMethod(type, name, mutates) {
return function() { return function() {
let realmId = this[keys.realm]; let realmId = this[keys.realm];
let id = this[keys.id]; let id = this[keys.id];
@ -97,8 +116,8 @@ function createMethod(type, name, resize) {
let result = rpc.callMethod(realmId, id, name, Array.from(arguments)); let result = rpc.callMethod(realmId, id, name, Array.from(arguments));
if (resize) { if (mutates) {
this[keys.resize](); fireMutationListeners(realmId);
} }
return result; return result;
@ -113,6 +132,13 @@ function getterForProperty(name) {
function setterForProperty(name) { function setterForProperty(name) {
return function(value) { return function(value) {
rpc.setProperty(this[keys.realm], this[keys.id], name, value); let realmId = this[keys.realm];
rpc.setProperty(realmId, this[keys.id], name, value);
// If this isn't a primitive value, then it might create a new object in the Realm.
if (value && typeof value == 'object') {
fireMutationListeners(realmId);
}
}; };
} }

View File

@ -2,7 +2,11 @@
"env": { "env": {
"commonjs": true "commonjs": true
}, },
"ecmaFeatures": {
"forOf": true
},
"rules": { "rules": {
"no-empty": 0,
"no-unused-vars": 0 "no-unused-vars": 0
} }
} }

View File

@ -270,7 +270,64 @@ module.exports = BaseTest.extend({
}); });
TestCase.assertThrows(function() { TestCase.assertThrows(function() {
obj.arrayCol.splice(0, 0, obj.objectCol); array.splice(0, 0, [1]);
}, 'can only splice in a write transaction'); }, 'can only splice in a write transaction');
}, },
testDeletions: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]);
array = obj.arrayCol;
});
try {
realm.write(function() {
realm.delete(array[0]);
TestCase.assertEqual(array.length, 1);
TestCase.assertEqual(array[0].doubleCol, 4);
throw new Error('Transaction FAIL');
});
} catch (e) {}
TestCase.assertEqual(array.length, 2);
TestCase.assertEqual(array[0].doubleCol, 3);
},
testLiveUpdatingResults: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var objects = realm.objects('TestObject');
var array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]);
array = obj.arrayCol;
});
TestCase.assertEqual(objects.length, 4);
try {
realm.write(function() {
array.push([5]);
TestCase.assertEqual(objects.length, 5);
array.unshift([2]);
TestCase.assertEqual(objects.length, 6);
array.splice(0, 0, [1]);
TestCase.assertEqual(objects.length, 7);
array.push(objects[0], objects[1]);
TestCase.assertEqual(objects.length, 7);
throw new Error('Transaction FAIL');
});
} catch (e) {}
TestCase.assertEqual(array.length, 2);
TestCase.assertEqual(objects.length, 4);
},
}); });

View File

@ -216,11 +216,13 @@ module.exports = BaseTest.extend({
}, },
testLinkTypesPropertySetters: function() { testLinkTypesPropertySetters: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]}); var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var objects = realm.objects('TestObject');
var obj = null; var obj = null;
realm.write(function() { realm.write(function() {
obj = realm.create('LinkTypesObject', [[1], null, [[3]]]); obj = realm.create('LinkTypesObject', [[1], null, [[3]]]);
}); });
TestCase.assertEqual(realm.objects('TestObject').length, 2); TestCase.assertEqual(objects.length, 2);
TestCase.assertThrows(function() { TestCase.assertThrows(function() {
obj.objectCol1 = obj.objectCol; obj.objectCol1 = obj.objectCol;
@ -232,7 +234,7 @@ module.exports = BaseTest.extend({
}); });
TestCase.assertEqual(obj.objectCol1.doubleCol, 1); TestCase.assertEqual(obj.objectCol1.doubleCol, 1);
//TestCase.assertEqual(obj.objectCol, obj.objectCol1); //TestCase.assertEqual(obj.objectCol, obj.objectCol1);
TestCase.assertEqual(realm.objects('TestObject').length, 2); TestCase.assertEqual(objects.length, 2);
realm.write(function() { realm.write(function() {
obj.objectCol = null; obj.objectCol = null;
@ -246,12 +248,13 @@ module.exports = BaseTest.extend({
obj.objectCol = { doubleCol: 1 }; obj.objectCol = { doubleCol: 1 };
}); });
TestCase.assertEqual(obj.objectCol.doubleCol, 1); TestCase.assertEqual(obj.objectCol.doubleCol, 1);
TestCase.assertEqual(realm.objects('TestObject').length, 3); TestCase.assertEqual(objects.length, 3);
// set array property // set array property
realm.write(function() { realm.write(function() {
obj.arrayCol = [obj.arrayCol[0], obj.objectCol, realm.create('TestObject', [2])]; obj.arrayCol = [obj.arrayCol[0], obj.objectCol, realm.create('TestObject', [2])];
}); });
TestCase.assertEqual(objects.length, 4);
TestCase.assertEqual(obj.arrayCol.length, 3); TestCase.assertEqual(obj.arrayCol.length, 3);
TestCase.assertEqual(obj.arrayCol[0].doubleCol, 3); TestCase.assertEqual(obj.arrayCol[0].doubleCol, 3);
TestCase.assertEqual(obj.arrayCol[1].doubleCol, 1); TestCase.assertEqual(obj.arrayCol[1].doubleCol, 1);