diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 90ab313826..db8e81dab7 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -200,6 +200,60 @@ describe('Cloud Code', () => { done(); } }); + it('beforeFind can return object without DB operation', async () => { + Parse.Cloud.beforeFind('beforeFind', () => { + return new Parse.Object('TestObject', { foo: 'bar' }); + }); + Parse.Cloud.afterFind('beforeFind', req => { + expect(req.objects).toBeDefined(); + expect(req.objects[0].get('foo')).toBe('bar'); + }); + const newObj = await new Parse.Query('beforeFind').first(); + expect(newObj.className).toBe('TestObject'); + expect(newObj.toJSON()).toEqual({ foo: 'bar' }); + await newObj.save(); + }); + + it('beforeFind can return array of objects without DB operation', async () => { + Parse.Cloud.beforeFind('beforeFind', () => { + return [new Parse.Object('TestObject', { foo: 'bar' })]; + }); + Parse.Cloud.afterFind('beforeFind', req => { + expect(req.objects).toBeDefined(); + expect(req.objects[0].get('foo')).toBe('bar'); + }); + const newObj = await new Parse.Query('beforeFind').first(); + expect(newObj.className).toBe('TestObject'); + expect(newObj.toJSON()).toEqual({ foo: 'bar' }); + await newObj.save(); + }); + + it('beforeFind can return object for get query without DB operation', async () => { + Parse.Cloud.beforeFind('beforeFind', () => { + return [new Parse.Object('TestObject', { foo: 'bar' })]; + }); + Parse.Cloud.afterFind('beforeFind', req => { + expect(req.objects).toBeDefined(); + expect(req.objects[0].get('foo')).toBe('bar'); + }); + const newObj = await new Parse.Query('beforeFind').get('objId'); + expect(newObj.className).toBe('TestObject'); + expect(newObj.toJSON()).toEqual({ foo: 'bar' }); + await newObj.save(); + }); + + it('beforeFind can return empty array without DB operation', async () => { + Parse.Cloud.beforeFind('beforeFind', () => { + return []; + }); + Parse.Cloud.afterFind('beforeFind', req => { + expect(req.objects.length).toBe(0); + }); + const obj = new Parse.Object('beforeFind'); + await obj.save(); + const newObj = await new Parse.Query('beforeFind').first(); + expect(newObj).toBeUndefined(); + }); it('beforeSave rejection with custom error code', function (done) { Parse.Cloud.beforeSave('BeforeSaveFailWithErrorCode', function () { diff --git a/src/rest.js b/src/rest.js index e1e53668a6..4f11135d40 100644 --- a/src/rest.js +++ b/src/rest.js @@ -23,66 +23,75 @@ function checkLiveQuery(className, config) { return config.liveQueryController && config.liveQueryController.hasLiveQuery(className); } -// Returns a promise for an object with optional keys 'results' and 'count'. -function find(config, auth, className, restWhere, restOptions, clientSDK, context) { - enforceRoleSecurity('find', className, auth); - return triggers - .maybeRunQueryTrigger( - triggers.Types.beforeFind, +async function runFindTriggers( + config, + auth, + className, + restWhere, + restOptions, + clientSDK, + context, + isGet +) { + const result = await triggers.maybeRunQueryTrigger( + triggers.Types.beforeFind, + className, + restWhere, + restOptions, + config, + auth, + context, + isGet + ); + restWhere = result.restWhere || restWhere; + restOptions = result.restOptions || restOptions; + if (result?.objects) { + const objects = result.objects; + await triggers.maybeRunAfterFindTrigger( + triggers.Types.afterFind, + auth, className, - restWhere, - restOptions, + objects, config, - auth, + restWhere, context - ) - .then(result => { - restWhere = result.restWhere || restWhere; - restOptions = result.restOptions || restOptions; - const query = new RestQuery( - config, - auth, - className, - restWhere, - restOptions, - clientSDK, - true, - context - ); - return query.execute(); - }); + ); + return { + results: objects.map(row => row._toFullJSON()), + }; + } + const query = new RestQuery( + config, + auth, + className, + restWhere, + restOptions, + clientSDK, + true, + context + ); + return await query.execute(); } +// Returns a promise for an object with optional keys 'results' and 'count'. +const find = (config, auth, className, restWhere, restOptions, clientSDK, context) => { + enforceRoleSecurity('find', className, auth); + return runFindTriggers(config, auth, className, restWhere, restOptions, clientSDK, context); +}; + // get is just like find but only queries an objectId. const get = (config, auth, className, objectId, restOptions, clientSDK, context) => { - var restWhere = { objectId }; enforceRoleSecurity('get', className, auth); - return triggers - .maybeRunQueryTrigger( - triggers.Types.beforeFind, - className, - restWhere, - restOptions, - config, - auth, - context, - true - ) - .then(result => { - restWhere = result.restWhere || restWhere; - restOptions = result.restOptions || restOptions; - const query = new RestQuery( - config, - auth, - className, - restWhere, - restOptions, - clientSDK, - true, - context - ); - return query.execute(); - }); + return runFindTriggers( + config, + auth, + className, + { objectId }, + restOptions, + clientSDK, + context, + true + ); }; // Returns a promise that doesn't resolve to any useful value. diff --git a/src/triggers.js b/src/triggers.js index b5f11435df..a7e997e3a1 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -455,6 +455,9 @@ export function maybeRunAfterFindTrigger( request.objects = objects.map(object => { //setting the class name to transform into parse object object.className = className; + if (object instanceof Parse.Object) { + return object; + } return Parse.Object.fromJSON(object); }); return Promise.resolve() @@ -586,9 +589,19 @@ export function maybeRunQueryTrigger( restOptions = restOptions || {}; restOptions.subqueryReadPreference = requestObject.subqueryReadPreference; } + let objects = undefined; + if (result instanceof Parse.Object) { + objects = [result]; + } else if ( + Array.isArray(result) && + (!result.length || result.some(obj => obj instanceof Parse.Object)) + ) { + objects = result; + } return { restWhere, restOptions, + objects, }; }, err => {