Skip to content

Commit fc92faa

Browse files
authored
feat: Add ParseQuery.watch to trigger LiveQuery only on update of specific fields (parse-community#8028)
1 parent 62b3426 commit fc92faa

File tree

3 files changed

+117
-0
lines changed

3 files changed

+117
-0
lines changed

Diff for: spec/ParseLiveQuery.spec.js

+43
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,49 @@ describe('ParseLiveQuery', function () {
394394
await object.save();
395395
});
396396

397+
xit('can handle live query with fields - enable upon JS SDK support', async () => {
398+
await reconfigureServer({
399+
liveQuery: {
400+
classNames: ['Test'],
401+
},
402+
startLiveQueryServer: true,
403+
});
404+
const query = new Parse.Query('Test');
405+
query.watch('yolo');
406+
const subscription = await query.subscribe();
407+
const spy = {
408+
create(obj) {
409+
if (!obj.get('yolo')) {
410+
fail('create should not have been called');
411+
}
412+
},
413+
update(object, original) {
414+
if (object.get('yolo') === original.get('yolo')) {
415+
fail('create should not have been called');
416+
}
417+
},
418+
};
419+
const createSpy = spyOn(spy, 'create').and.callThrough();
420+
const updateSpy = spyOn(spy, 'update').and.callThrough();
421+
subscription.on('create', spy.create);
422+
subscription.on('update', spy.update);
423+
const obj = new Parse.Object('Test');
424+
obj.set('foo', 'bar');
425+
await obj.save();
426+
obj.set('foo', 'xyz');
427+
obj.set('yolo', 'xyz');
428+
await obj.save();
429+
const obj2 = new Parse.Object('Test');
430+
obj2.set('foo', 'bar');
431+
obj2.set('yolo', 'bar');
432+
await obj2.save();
433+
obj2.set('foo', 'bart');
434+
await obj2.save();
435+
await new Promise(resolve => setTimeout(resolve, 2000));
436+
expect(createSpy).toHaveBeenCalledTimes(1);
437+
expect(updateSpy).toHaveBeenCalledTimes(1);
438+
});
439+
397440
it('can handle afterEvent set pointers', async done => {
398441
await reconfigureServer({
399442
liveQuery: {

Diff for: spec/ParseLiveQueryServer.spec.js

+55
Original file line numberDiff line numberDiff line change
@@ -1087,6 +1087,61 @@ describe('ParseLiveQueryServer', function () {
10871087
done();
10881088
});
10891089

1090+
it('can handle create command with watch', async () => {
1091+
jasmine.restoreLibrary('../lib/LiveQuery/Client', 'Client');
1092+
const Client = require('../lib/LiveQuery/Client').Client;
1093+
const parseLiveQueryServer = new ParseLiveQueryServer({});
1094+
// Make mock request message
1095+
const message = generateMockMessage();
1096+
1097+
const clientId = 1;
1098+
const parseWebSocket = {
1099+
clientId,
1100+
send: jasmine.createSpy('send'),
1101+
};
1102+
const client = new Client(clientId, parseWebSocket);
1103+
spyOn(client, 'pushCreate').and.callThrough();
1104+
parseLiveQueryServer.clients.set(clientId, client);
1105+
1106+
// Add mock subscription
1107+
const requestId = 2;
1108+
const query = {
1109+
className: testClassName,
1110+
where: {
1111+
key: 'value',
1112+
},
1113+
watch: ['yolo'],
1114+
};
1115+
await addMockSubscription(parseLiveQueryServer, clientId, requestId, parseWebSocket, query);
1116+
// Mock _matchesSubscription to return matching
1117+
parseLiveQueryServer._matchesSubscription = function (parseObject) {
1118+
if (!parseObject) {
1119+
return false;
1120+
}
1121+
return true;
1122+
};
1123+
parseLiveQueryServer._matchesACL = function () {
1124+
return Promise.resolve(true);
1125+
};
1126+
1127+
parseLiveQueryServer._onAfterSave(message);
1128+
1129+
// Make sure we send create command to client
1130+
await timeout();
1131+
1132+
expect(client.pushCreate).not.toHaveBeenCalled();
1133+
1134+
message.currentParseObject.set('yolo', 'test');
1135+
parseLiveQueryServer._onAfterSave(message);
1136+
1137+
await timeout();
1138+
1139+
const args = parseWebSocket.send.calls.mostRecent().args;
1140+
const toSend = JSON.parse(args[0]);
1141+
expect(toSend.object).toBeDefined();
1142+
expect(toSend.original).toBeUndefined();
1143+
});
1144+
10901145
it('can match subscription for null or undefined parse object', function () {
10911146
const parseLiveQueryServer = new ParseLiveQueryServer({});
10921147
// Make mock subscription

Diff for: src/LiveQuery/ParseLiveQueryServer.js

+19
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { getCacheController, getDatabaseController } from '../Controllers';
2222
import LRU from 'lru-cache';
2323
import UserRouter from '../Routers/UsersRouter';
2424
import DatabaseController from '../Controllers/DatabaseController';
25+
import { isDeepStrictEqual } from 'util';
2526

2627
class ParseLiveQueryServer {
2728
clients: Map;
@@ -329,6 +330,10 @@ class ParseLiveQueryServer {
329330
} else {
330331
return null;
331332
}
333+
const watchFieldsChanged = this._checkWatchFields(client, requestId, message);
334+
if (!watchFieldsChanged && (type === 'update' || type === 'create')) {
335+
return;
336+
}
332337
res = {
333338
event: type,
334339
sessionToken: client.sessionToken,
@@ -707,6 +712,17 @@ class ParseLiveQueryServer {
707712
return auth;
708713
}
709714

715+
_checkWatchFields(client: any, requestId: any, message: any) {
716+
const subscriptionInfo = client.getSubscriptionInfo(requestId);
717+
const watch = subscriptionInfo?.watch;
718+
if (!watch) {
719+
return true;
720+
}
721+
const object = message.currentParseObject;
722+
const original = message.originalParseObject;
723+
return watch.some(field => !isDeepStrictEqual(object.get(field), original?.get(field)));
724+
}
725+
710726
async _matchesACL(acl: any, client: any, requestId: number): Promise<boolean> {
711727
// Return true directly if ACL isn't present, ACL is public read, or client has master key
712728
if (!acl || acl.getPublicReadAccess() || client.hasMasterKey) {
@@ -888,6 +904,9 @@ class ParseLiveQueryServer {
888904
if (request.query.fields) {
889905
subscriptionInfo.fields = request.query.fields;
890906
}
907+
if (request.query.watch) {
908+
subscriptionInfo.watch = request.query.watch;
909+
}
891910
if (request.sessionToken) {
892911
subscriptionInfo.sessionToken = request.sessionToken;
893912
}

0 commit comments

Comments
 (0)