Skip to content

Commit

Permalink
feat: Add ParseQuery.watch to trigger LiveQuery only on update of s…
Browse files Browse the repository at this point in the history
…pecific fields (#8028)
  • Loading branch information
dblythy authored Jan 16, 2023
1 parent 62b3426 commit fc92faa
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 0 deletions.
43 changes: 43 additions & 0 deletions spec/ParseLiveQuery.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,49 @@ describe('ParseLiveQuery', function () {
await object.save();
});

xit('can handle live query with fields - enable upon JS SDK support', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['Test'],
},
startLiveQueryServer: true,
});
const query = new Parse.Query('Test');
query.watch('yolo');
const subscription = await query.subscribe();
const spy = {
create(obj) {
if (!obj.get('yolo')) {
fail('create should not have been called');
}
},
update(object, original) {
if (object.get('yolo') === original.get('yolo')) {
fail('create should not have been called');
}
},
};
const createSpy = spyOn(spy, 'create').and.callThrough();
const updateSpy = spyOn(spy, 'update').and.callThrough();
subscription.on('create', spy.create);
subscription.on('update', spy.update);
const obj = new Parse.Object('Test');
obj.set('foo', 'bar');
await obj.save();
obj.set('foo', 'xyz');
obj.set('yolo', 'xyz');
await obj.save();
const obj2 = new Parse.Object('Test');
obj2.set('foo', 'bar');
obj2.set('yolo', 'bar');
await obj2.save();
obj2.set('foo', 'bart');
await obj2.save();
await new Promise(resolve => setTimeout(resolve, 2000));
expect(createSpy).toHaveBeenCalledTimes(1);
expect(updateSpy).toHaveBeenCalledTimes(1);
});

it('can handle afterEvent set pointers', async done => {
await reconfigureServer({
liveQuery: {
Expand Down
55 changes: 55 additions & 0 deletions spec/ParseLiveQueryServer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,61 @@ describe('ParseLiveQueryServer', function () {
done();
});

it('can handle create command with watch', async () => {
jasmine.restoreLibrary('../lib/LiveQuery/Client', 'Client');
const Client = require('../lib/LiveQuery/Client').Client;
const parseLiveQueryServer = new ParseLiveQueryServer({});
// Make mock request message
const message = generateMockMessage();

const clientId = 1;
const parseWebSocket = {
clientId,
send: jasmine.createSpy('send'),
};
const client = new Client(clientId, parseWebSocket);
spyOn(client, 'pushCreate').and.callThrough();
parseLiveQueryServer.clients.set(clientId, client);

// Add mock subscription
const requestId = 2;
const query = {
className: testClassName,
where: {
key: 'value',
},
watch: ['yolo'],
};
await addMockSubscription(parseLiveQueryServer, clientId, requestId, parseWebSocket, query);
// Mock _matchesSubscription to return matching
parseLiveQueryServer._matchesSubscription = function (parseObject) {
if (!parseObject) {
return false;
}
return true;
};
parseLiveQueryServer._matchesACL = function () {
return Promise.resolve(true);
};

parseLiveQueryServer._onAfterSave(message);

// Make sure we send create command to client
await timeout();

expect(client.pushCreate).not.toHaveBeenCalled();

message.currentParseObject.set('yolo', 'test');
parseLiveQueryServer._onAfterSave(message);

await timeout();

const args = parseWebSocket.send.calls.mostRecent().args;
const toSend = JSON.parse(args[0]);
expect(toSend.object).toBeDefined();
expect(toSend.original).toBeUndefined();
});

it('can match subscription for null or undefined parse object', function () {
const parseLiveQueryServer = new ParseLiveQueryServer({});
// Make mock subscription
Expand Down
19 changes: 19 additions & 0 deletions src/LiveQuery/ParseLiveQueryServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { getCacheController, getDatabaseController } from '../Controllers';
import LRU from 'lru-cache';
import UserRouter from '../Routers/UsersRouter';
import DatabaseController from '../Controllers/DatabaseController';
import { isDeepStrictEqual } from 'util';

class ParseLiveQueryServer {
clients: Map;
Expand Down Expand Up @@ -329,6 +330,10 @@ class ParseLiveQueryServer {
} else {
return null;
}
const watchFieldsChanged = this._checkWatchFields(client, requestId, message);
if (!watchFieldsChanged && (type === 'update' || type === 'create')) {
return;
}
res = {
event: type,
sessionToken: client.sessionToken,
Expand Down Expand Up @@ -707,6 +712,17 @@ class ParseLiveQueryServer {
return auth;
}

_checkWatchFields(client: any, requestId: any, message: any) {
const subscriptionInfo = client.getSubscriptionInfo(requestId);
const watch = subscriptionInfo?.watch;
if (!watch) {
return true;
}
const object = message.currentParseObject;
const original = message.originalParseObject;
return watch.some(field => !isDeepStrictEqual(object.get(field), original?.get(field)));
}

async _matchesACL(acl: any, client: any, requestId: number): Promise<boolean> {
// Return true directly if ACL isn't present, ACL is public read, or client has master key
if (!acl || acl.getPublicReadAccess() || client.hasMasterKey) {
Expand Down Expand Up @@ -888,6 +904,9 @@ class ParseLiveQueryServer {
if (request.query.fields) {
subscriptionInfo.fields = request.query.fields;
}
if (request.query.watch) {
subscriptionInfo.watch = request.query.watch;
}
if (request.sessionToken) {
subscriptionInfo.sessionToken = request.sessionToken;
}
Expand Down

0 comments on commit fc92faa

Please sign in to comment.