Skip to content

Commit 68a3a87

Browse files
authored
fix: set objects in afterFind triggers (#7311)
1 parent 197fcbd commit 68a3a87

File tree

5 files changed

+115
-15
lines changed

5 files changed

+115
-15
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ ___
154154
- Refactor: uniform issue templates across repos (Manuel Trezza) [#7528](https://github.com/parse-community/parse-server/pull/7528)
155155
- ci: bump ci environment (Manuel Trezza) [#7539](https://github.com/parse-community/parse-server/pull/7539)
156156
- CI now pushes docker images to Docker Hub (Corey Baker) [#7548](https://github.com/parse-community/parse-server/pull/7548)
157+
- Allow afterFind and afterLiveQueryEvent to set unsaved pointers and keys (dblythy) [#7310](https://github.com/parse-community/parse-server/pull/7310)
157158
- Allow setting descending sort to full text queries (dblythy) [#7496](https://github.com/parse-community/parse-server/pull/7496)
158159
- Allow cloud string for ES modules (Daniel Blyth) [#7560](https://github.com/parse-community/parse-server/pull/7560)
159160
- docs: Introduce deprecation ID for reference in comments and online search (Manuel Trezza) [#7562](https://github.com/parse-community/parse-server/pull/7562)

Diff for: spec/CloudCode.spec.js

+47
Original file line numberDiff line numberDiff line change
@@ -2391,6 +2391,53 @@ describe('afterFind hooks', () => {
23912391
});
23922392
});
23932393

2394+
it('can set a pointer object in afterFind', async () => {
2395+
const obj = new Parse.Object('MyObject');
2396+
await obj.save();
2397+
Parse.Cloud.afterFind('MyObject', async ({ objects }) => {
2398+
const otherObject = new Parse.Object('Test');
2399+
otherObject.set('foo', 'bar');
2400+
await otherObject.save();
2401+
objects[0].set('Pointer', otherObject);
2402+
objects[0].set('xyz', 'yolo');
2403+
expect(objects[0].get('Pointer').get('foo')).toBe('bar');
2404+
});
2405+
const query = new Parse.Query('MyObject');
2406+
query.equalTo('objectId', obj.id);
2407+
const obj2 = await query.first();
2408+
expect(obj2.get('xyz')).toBe('yolo');
2409+
const pointer = obj2.get('Pointer');
2410+
expect(pointer.get('foo')).toBe('bar');
2411+
});
2412+
2413+
it('can set invalid object in afterFind', async () => {
2414+
const obj = new Parse.Object('MyObject');
2415+
await obj.save();
2416+
Parse.Cloud.afterFind('MyObject', () => [{}]);
2417+
const query = new Parse.Query('MyObject');
2418+
query.equalTo('objectId', obj.id);
2419+
const obj2 = await query.first();
2420+
expect(obj2).toBeDefined();
2421+
expect(obj2.toJSON()).toEqual({});
2422+
expect(obj2.id).toBeUndefined();
2423+
});
2424+
2425+
it('can return a unsaved object in afterFind', async () => {
2426+
const obj = new Parse.Object('MyObject');
2427+
await obj.save();
2428+
Parse.Cloud.afterFind('MyObject', async () => {
2429+
const otherObject = new Parse.Object('Test');
2430+
otherObject.set('foo', 'bar');
2431+
return [otherObject];
2432+
});
2433+
const query = new Parse.Query('MyObject');
2434+
const obj2 = await query.first();
2435+
expect(obj2.get('foo')).toEqual('bar');
2436+
expect(obj2.id).toBeUndefined();
2437+
await obj2.save();
2438+
expect(obj2.id).toBeDefined();
2439+
});
2440+
23942441
it('should have request headers', done => {
23952442
Parse.Cloud.afterFind('MyObject', req => {
23962443
expect(req.headers).toBeDefined();

Diff for: spec/ParseLiveQuery.spec.js

+38
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,44 @@ describe('ParseLiveQuery', function () {
358358
await object.save();
359359
});
360360

361+
it('can handle afterEvent set pointers', async done => {
362+
await reconfigureServer({
363+
liveQuery: {
364+
classNames: ['TestObject'],
365+
},
366+
startLiveQueryServer: true,
367+
verbose: false,
368+
silent: true,
369+
});
370+
371+
const object = new TestObject();
372+
await object.save();
373+
374+
const secondObject = new Parse.Object('Test2');
375+
secondObject.set('foo', 'bar');
376+
await secondObject.save();
377+
378+
Parse.Cloud.afterLiveQueryEvent('TestObject', async ({ object }) => {
379+
const query = new Parse.Query('Test2');
380+
const obj = await query.first();
381+
object.set('obj', obj);
382+
});
383+
384+
const query = new Parse.Query(TestObject);
385+
query.equalTo('objectId', object.id);
386+
const subscription = await query.subscribe();
387+
subscription.on('update', object => {
388+
expect(object.get('obj')).toBeDefined();
389+
expect(object.get('obj').get('foo')).toBe('bar');
390+
done();
391+
});
392+
subscription.on('error', () => {
393+
fail('error should not have been called.');
394+
});
395+
object.set({ foo: 'bar' });
396+
await object.save();
397+
});
398+
361399
it('can handle async afterEvent modification', async done => {
362400
await reconfigureServer({
363401
liveQuery: {

Diff for: src/LiveQuery/ParseLiveQueryServer.js

+7-8
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ParsePubSub } from './ParsePubSub';
1010
import SchemaController from '../Controllers/SchemaController';
1111
import _ from 'lodash';
1212
import { v4 as uuidv4 } from 'uuid';
13-
import { runLiveQueryEventHandlers, getTrigger, runTrigger } from '../triggers';
13+
import { runLiveQueryEventHandlers, getTrigger, runTrigger, toJSONwithObjects } from '../triggers';
1414
import { getAuthForSessionToken, Auth } from '../Auth';
1515
import { getCacheController } from '../Controllers';
1616
import LRU from 'lru-cache';
@@ -183,8 +183,7 @@ class ParseLiveQueryServer {
183183
return;
184184
}
185185
if (res.object && typeof res.object.toJSON === 'function') {
186-
deletedParseObject = res.object.toJSON();
187-
deletedParseObject.className = className;
186+
deletedParseObject = toJSONwithObjects(res.object, res.object.className || className);
188187
}
189188
if (
190189
(deletedParseObject.className === '_User' ||
@@ -337,13 +336,13 @@ class ParseLiveQueryServer {
337336
return;
338337
}
339338
if (res.object && typeof res.object.toJSON === 'function') {
340-
currentParseObject = res.object.toJSON();
341-
currentParseObject.className = res.object.className || className;
339+
currentParseObject = toJSONwithObjects(res.object, res.object.className || className);
342340
}
343-
344341
if (res.original && typeof res.original.toJSON === 'function') {
345-
originalParseObject = res.original.toJSON();
346-
originalParseObject.className = res.original.className || className;
342+
originalParseObject = toJSONwithObjects(
343+
res.original,
344+
res.original.className || className
345+
);
347346
}
348347
if (
349348
(currentParseObject.className === '_User' ||

Diff for: src/triggers.js

+22-7
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,27 @@ export function _unregisterAll() {
168168
Object.keys(_triggerStore).forEach(appId => delete _triggerStore[appId]);
169169
}
170170

171+
export function toJSONwithObjects(object, className) {
172+
if (!object || !object.toJSON) {
173+
return {};
174+
}
175+
const toJSON = object.toJSON();
176+
const stateController = Parse.CoreManager.getObjectStateController();
177+
const [pending] = stateController.getPendingOps(object._getStateIdentifier());
178+
for (const key in pending) {
179+
const val = object.get(key);
180+
if (!val || !val._toFullJSON) {
181+
toJSON[key] = val;
182+
continue;
183+
}
184+
toJSON[key] = val._toFullJSON();
185+
}
186+
if (className) {
187+
toJSON.className = className;
188+
}
189+
return toJSON;
190+
}
191+
171192
export function getTrigger(className, triggerType, applicationId) {
172193
if (!applicationId) {
173194
throw 'Missing ApplicationID';
@@ -323,7 +344,7 @@ export function getResponseObject(request, resolve, reject) {
323344
response = request.objects;
324345
}
325346
response = response.map(object => {
326-
return object.toJSON();
347+
return toJSONwithObjects(object);
327348
});
328349
return resolve(response);
329350
}
@@ -451,12 +472,6 @@ export function maybeRunAfterFindTrigger(
451472
const response = trigger(request);
452473
if (response && typeof response.then === 'function') {
453474
return response.then(results => {
454-
if (!results) {
455-
throw new Parse.Error(
456-
Parse.Error.SCRIPT_FAILED,
457-
'AfterFind expect results to be returned in the promise'
458-
);
459-
}
460475
return results;
461476
});
462477
}

0 commit comments

Comments
 (0)