Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: set objects in afterFind triggers #7311

Merged
merged 10 commits into from
Oct 9, 2021
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ ___
- Refactor: uniform issue templates across repos (Manuel Trezza) [#7528](https://github.com/parse-community/parse-server/pull/7528)
- ci: bump ci environment (Manuel Trezza) [#7539](https://github.com/parse-community/parse-server/pull/7539)
- CI now pushes docker images to Docker Hub (Corey Baker) [#7548](https://github.com/parse-community/parse-server/pull/7548)
- Allow afterFind and afterLiveQueryEvent to set unsaved pointers and keys (dblythy) [#7310](https://github.com/parse-community/parse-server/pull/7310)
- Allow setting descending sort to full text queries (dblythy) [#7496](https://github.com/parse-community/parse-server/pull/7496)
- Allow cloud string for ES modules (Daniel Blyth) [#7560](https://github.com/parse-community/parse-server/pull/7560)
- docs: Introduce deprecation ID for reference in comments and online search (Manuel Trezza) [#7562](https://github.com/parse-community/parse-server/pull/7562)
Expand Down
47 changes: 47 additions & 0 deletions spec/CloudCode.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2391,6 +2391,53 @@ describe('afterFind hooks', () => {
});
});

it('can set a pointer object in afterFind', async () => {
const obj = new Parse.Object('MyObject');
await obj.save();
Parse.Cloud.afterFind('MyObject', async ({ objects }) => {
const otherObject = new Parse.Object('Test');
otherObject.set('foo', 'bar');
await otherObject.save();
objects[0].set('Pointer', otherObject);
objects[0].set('xyz', 'yolo');
expect(objects[0].get('Pointer').get('foo')).toBe('bar');
});
const query = new Parse.Query('MyObject');
query.equalTo('objectId', obj.id);
const obj2 = await query.first();
expect(obj2.get('xyz')).toBe('yolo');
const pointer = obj2.get('Pointer');
expect(pointer.get('foo')).toBe('bar');
});

it('can set invalid object in afterFind', async () => {
const obj = new Parse.Object('MyObject');
await obj.save();
Parse.Cloud.afterFind('MyObject', () => [{}]);
const query = new Parse.Query('MyObject');
query.equalTo('objectId', obj.id);
const obj2 = await query.first();
expect(obj2).toBeDefined();
expect(obj2.toJSON()).toEqual({});
expect(obj2.id).toBeUndefined();
});

it('can return a unsaved object in afterFind', async () => {
const obj = new Parse.Object('MyObject');
await obj.save();
Parse.Cloud.afterFind('MyObject', async () => {
const otherObject = new Parse.Object('Test');
otherObject.set('foo', 'bar');
return [otherObject];
});
const query = new Parse.Query('MyObject');
const obj2 = await query.first();
expect(obj2.get('foo')).toEqual('bar');
expect(obj2.id).toBeUndefined();
await obj2.save();
expect(obj2.id).toBeDefined();
});

it('should have request headers', done => {
Parse.Cloud.afterFind('MyObject', req => {
expect(req.headers).toBeDefined();
Expand Down
38 changes: 38 additions & 0 deletions spec/ParseLiveQuery.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,44 @@ describe('ParseLiveQuery', function () {
await object.save();
});

it('can handle afterEvent set pointers', async done => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});

const object = new TestObject();
await object.save();

const secondObject = new Parse.Object('Test2');
secondObject.set('foo', 'bar');
await secondObject.save();

Parse.Cloud.afterLiveQueryEvent('TestObject', async ({ object }) => {
const query = new Parse.Query('Test2');
const obj = await query.first();
object.set('obj', obj);
});

const query = new Parse.Query(TestObject);
query.equalTo('objectId', object.id);
const subscription = await query.subscribe();
subscription.on('update', object => {
expect(object.get('obj')).toBeDefined();
expect(object.get('obj').get('foo')).toBe('bar');
done();
});
subscription.on('error', () => {
fail('error should not have been called.');
});
object.set({ foo: 'bar' });
await object.save();
});

it('can handle async afterEvent modification', async done => {
await reconfigureServer({
liveQuery: {
Expand Down
15 changes: 7 additions & 8 deletions src/LiveQuery/ParseLiveQueryServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ParsePubSub } from './ParsePubSub';
import SchemaController from '../Controllers/SchemaController';
import _ from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import { runLiveQueryEventHandlers, getTrigger, runTrigger } from '../triggers';
import { runLiveQueryEventHandlers, getTrigger, runTrigger, toJSONwithObjects } from '../triggers';
import { getAuthForSessionToken, Auth } from '../Auth';
import { getCacheController } from '../Controllers';
import LRU from 'lru-cache';
Expand Down Expand Up @@ -183,8 +183,7 @@ class ParseLiveQueryServer {
return;
}
if (res.object && typeof res.object.toJSON === 'function') {
deletedParseObject = res.object.toJSON();
deletedParseObject.className = className;
deletedParseObject = toJSONwithObjects(res.object, res.object.className || className);
}
if (
(deletedParseObject.className === '_User' ||
Expand Down Expand Up @@ -337,13 +336,13 @@ class ParseLiveQueryServer {
return;
}
if (res.object && typeof res.object.toJSON === 'function') {
currentParseObject = res.object.toJSON();
currentParseObject.className = res.object.className || className;
currentParseObject = toJSONwithObjects(res.object, res.object.className || className);
}

if (res.original && typeof res.original.toJSON === 'function') {
originalParseObject = res.original.toJSON();
originalParseObject.className = res.original.className || className;
originalParseObject = toJSONwithObjects(
res.original,
res.original.className || className
);
}
if (
(currentParseObject.className === '_User' ||
Expand Down
29 changes: 22 additions & 7 deletions src/triggers.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,27 @@ export function _unregisterAll() {
Object.keys(_triggerStore).forEach(appId => delete _triggerStore[appId]);
}

export function toJSONwithObjects(object, className) {
dblythy marked this conversation as resolved.
Show resolved Hide resolved
if (!object || !object.toJSON) {
return {};
dblythy marked this conversation as resolved.
Show resolved Hide resolved
}
const toJSON = object.toJSON();
const stateController = Parse.CoreManager.getObjectStateController();
const [pending] = stateController.getPendingOps(object._getStateIdentifier());
for (const key in pending) {
const val = object.get(key);
if (!val || !val._toFullJSON) {
toJSON[key] = val;
continue;
}
toJSON[key] = val._toFullJSON();
}
if (className) {
toJSON.className = className;
}
return toJSON;
}

export function getTrigger(className, triggerType, applicationId) {
if (!applicationId) {
throw 'Missing ApplicationID';
Expand Down Expand Up @@ -323,7 +344,7 @@ export function getResponseObject(request, resolve, reject) {
response = request.objects;
}
response = response.map(object => {
return object.toJSON();
return toJSONwithObjects(object);
});
return resolve(response);
}
Expand Down Expand Up @@ -451,12 +472,6 @@ export function maybeRunAfterFindTrigger(
const response = trigger(request);
if (response && typeof response.then === 'function') {
return response.then(results => {
if (!results) {
throw new Parse.Error(
Parse.Error.SCRIPT_FAILED,
'AfterFind expect results to be returned in the promise'
);
}
return results;
});
}
Expand Down