From 6f65ec54bda422a3beba32b43beb46aaa4b83683 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 19 Jul 2023 13:25:04 +1000 Subject: [PATCH 1/6] feat: allow short circuit of beforeFind --- spec/CloudCode.spec.js | 55 ++++++++++++++++++++++++++++++++++++++++++ src/rest.js | 10 ++++++++ src/triggers.js | 10 ++++++++ 3 files changed, 75 insertions(+) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 90ab313826..c38199b3d0 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -201,6 +201,61 @@ describe('Cloud Code', () => { } }); + it('beforeFind can short circuit', async () => { + Parse.Cloud.beforeFind('beforeFind', () => { + return new Parse.Object('TestObject', { foo: 'bar' }); + }); + Parse.Cloud.afterFind('beforeFind', () => { + throw 'afterFind should not run'; + }); + const obj = new Parse.Object('beforeFind'); + await obj.save(); + const newObj = await new Parse.Query('beforeFind').first(); + expect(newObj.className).toBe('TestObject'); + expect(newObj.toJSON()).toEqual({ foo: 'bar' }); + }); + + it('beforeFind can short circuit arrays', async () => { + Parse.Cloud.beforeFind('beforeFind', () => { + return [new Parse.Object('TestObject', { foo: 'bar' })]; + }); + Parse.Cloud.afterFind('beforeFind', () => { + throw 'afterFind should not run'; + }); + const obj = new Parse.Object('beforeFind'); + await obj.save(); + const newObj = await new Parse.Query('beforeFind').first(); + expect(newObj.className).toBe('TestObject'); + expect(newObj.toJSON()).toEqual({ foo: 'bar' }); + }); + + it('beforeFind can short circuit get', async () => { + Parse.Cloud.beforeFind('beforeFind', () => { + return [new Parse.Object('TestObject', { foo: 'bar' })]; + }); + Parse.Cloud.afterFind('beforeFind', () => { + throw 'afterFind should not run'; + }); + const obj = new Parse.Object('beforeFind'); + await obj.save(); + const newObj = await new Parse.Query('beforeFind').get(obj.id); + expect(newObj.className).toBe('TestObject'); + expect(newObj.toJSON()).toEqual({ foo: 'bar' }); + }); + + it('beforeFind can short circuit empty array', async () => { + Parse.Cloud.beforeFind('beforeFind', () => { + return []; + }); + Parse.Cloud.afterFind('beforeFind', () => { + throw 'afterFind should not run'; + }); + 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 () { throw new Parse.Error(999, 'Nope'); diff --git a/src/rest.js b/src/rest.js index e1e53668a6..5f7266b0b7 100644 --- a/src/rest.js +++ b/src/rest.js @@ -39,6 +39,11 @@ function find(config, auth, className, restWhere, restOptions, clientSDK, contex .then(result => { restWhere = result.restWhere || restWhere; restOptions = result.restOptions || restOptions; + if (result?.objects) { + return { + results: result.objects.map(row => row._toFullJSON()), + }; + } const query = new RestQuery( config, auth, @@ -71,6 +76,11 @@ const get = (config, auth, className, objectId, restOptions, clientSDK, context) .then(result => { restWhere = result.restWhere || restWhere; restOptions = result.restOptions || restOptions; + if (result?.objects) { + return { + results: result.objects.map(row => row._toFullJSON()), + }; + } const query = new RestQuery( config, auth, diff --git a/src/triggers.js b/src/triggers.js index b5f11435df..42219aba03 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -586,9 +586,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 => { From 41bf1b74ec82cdb69e0157ef856571437af446df Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sun, 23 Jul 2023 15:23:13 +0200 Subject: [PATCH 2/6] improve test description Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- spec/CloudCode.spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index c38199b3d0..8465ab68d9 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -201,7 +201,7 @@ describe('Cloud Code', () => { } }); - it('beforeFind can short circuit', async () => { + it('beforeFind can return object without DB operation', async () => { Parse.Cloud.beforeFind('beforeFind', () => { return new Parse.Object('TestObject', { foo: 'bar' }); }); @@ -215,7 +215,7 @@ describe('Cloud Code', () => { expect(newObj.toJSON()).toEqual({ foo: 'bar' }); }); - it('beforeFind can short circuit arrays', async () => { + it('beforeFind can return array of objects without DB operation', async () => { Parse.Cloud.beforeFind('beforeFind', () => { return [new Parse.Object('TestObject', { foo: 'bar' })]; }); @@ -229,7 +229,7 @@ describe('Cloud Code', () => { expect(newObj.toJSON()).toEqual({ foo: 'bar' }); }); - it('beforeFind can short circuit get', async () => { + it('beforeFind can return object for get query without DB operation', async () => { Parse.Cloud.beforeFind('beforeFind', () => { return [new Parse.Object('TestObject', { foo: 'bar' })]; }); @@ -243,7 +243,7 @@ describe('Cloud Code', () => { expect(newObj.toJSON()).toEqual({ foo: 'bar' }); }); - it('beforeFind can short circuit empty array', async () => { + it('beforeFind can return empty array without DB operation', async () => { Parse.Cloud.beforeFind('beforeFind', () => { return []; }); From 21c9289fb0a6086cd038e3ad8d18e6f67eccf7fd Mon Sep 17 00:00:00 2001 From: dblythy Date: Sun, 23 Jul 2023 23:43:25 +1000 Subject: [PATCH 3/6] Update CloudCode.spec.js --- spec/CloudCode.spec.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 8465ab68d9..1ddf710a8e 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -208,11 +208,10 @@ describe('Cloud Code', () => { Parse.Cloud.afterFind('beforeFind', () => { throw 'afterFind should not run'; }); - const obj = new Parse.Object('beforeFind'); - await obj.save(); 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 () => { @@ -222,11 +221,10 @@ describe('Cloud Code', () => { Parse.Cloud.afterFind('beforeFind', () => { throw 'afterFind should not run'; }); - const obj = new Parse.Object('beforeFind'); - await obj.save(); 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 () => { @@ -236,11 +234,10 @@ describe('Cloud Code', () => { Parse.Cloud.afterFind('beforeFind', () => { throw 'afterFind should not run'; }); - const obj = new Parse.Object('beforeFind'); - await obj.save(); - const newObj = await new Parse.Query('beforeFind').get(obj.id); + 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 () => { From ca749293202d9e411ebbb9d21fe92d713b79d8b7 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 24 Jul 2023 22:26:22 +1000 Subject: [PATCH 4/6] wip --- spec/CloudCode.spec.js | 23 ++++---- src/rest.js | 123 ++++++++++++++++++++--------------------- src/triggers.js | 3 + 3 files changed, 77 insertions(+), 72 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 1ddf710a8e..7c1274a0f2 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -200,13 +200,14 @@ describe('Cloud Code', () => { done(); } }); - - it('beforeFind can return object without DB operation', async () => { + fit('beforeFind can return object without DB operation', async () => { + await reconfigureServer({ silent: false }); Parse.Cloud.beforeFind('beforeFind', () => { return new Parse.Object('TestObject', { foo: 'bar' }); }); - Parse.Cloud.afterFind('beforeFind', () => { - throw 'afterFind should not run'; + Parse.Cloud.afterFind('beforeFind', req => { + expect(req.objects).toBeDefined(); + expect(req.objects[0].className).toBe('TestObject'); }); const newObj = await new Parse.Query('beforeFind').first(); expect(newObj.className).toBe('TestObject'); @@ -214,12 +215,13 @@ describe('Cloud Code', () => { await newObj.save(); }); - it('beforeFind can return array of objects without DB operation', async () => { + fit('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', () => { - throw 'afterFind should not run'; + Parse.Cloud.afterFind('beforeFind', req => { + expect(req.objects).toBeDefined(); + expect(req.objects[0].className).toBe('TestObject'); }); const newObj = await new Parse.Query('beforeFind').first(); expect(newObj.className).toBe('TestObject'); @@ -227,12 +229,13 @@ describe('Cloud Code', () => { await newObj.save(); }); - it('beforeFind can return object for get query without DB operation', async () => { + fit('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', () => { - throw 'afterFind should not run'; + Parse.Cloud.afterFind('beforeFind', req => { + expect(req.objects).toBeDefined(); + expect(req.objects[0].className).toBe('TestObject'); }); const newObj = await new Parse.Query('beforeFind').get('objId'); expect(newObj.className).toBe('TestObject'); diff --git a/src/rest.js b/src/rest.js index 5f7266b0b7..4f11135d40 100644 --- a/src/rest.js +++ b/src/rest.js @@ -23,76 +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; - if (result?.objects) { - return { - results: result.objects.map(row => row._toFullJSON()), - }; - } - 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; - if (result?.objects) { - return { - results: result.objects.map(row => row._toFullJSON()), - }; - } - 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 42219aba03..fb867967cd 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -454,6 +454,9 @@ export function maybeRunAfterFindTrigger( ); request.objects = objects.map(object => { //setting the class name to transform into parse object + if (object instanceof Parse.Object) { + return object; + } object.className = className; return Parse.Object.fromJSON(object); }); From 207c3c04ca80ca31db6af0a0181431bd8fdcdcfe Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 24 Jul 2023 22:40:15 +1000 Subject: [PATCH 5/6] wip --- spec/CloudCode.spec.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 7c1274a0f2..720a91c42d 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -200,8 +200,7 @@ describe('Cloud Code', () => { done(); } }); - fit('beforeFind can return object without DB operation', async () => { - await reconfigureServer({ silent: false }); + it('beforeFind can return object without DB operation', async () => { Parse.Cloud.beforeFind('beforeFind', () => { return new Parse.Object('TestObject', { foo: 'bar' }); }); @@ -215,7 +214,7 @@ describe('Cloud Code', () => { await newObj.save(); }); - fit('beforeFind can return array of objects without DB operation', async () => { + it('beforeFind can return array of objects without DB operation', async () => { Parse.Cloud.beforeFind('beforeFind', () => { return [new Parse.Object('TestObject', { foo: 'bar' })]; }); @@ -229,7 +228,7 @@ describe('Cloud Code', () => { await newObj.save(); }); - fit('beforeFind can return object for get query without DB operation', async () => { + it('beforeFind can return object for get query without DB operation', async () => { Parse.Cloud.beforeFind('beforeFind', () => { return [new Parse.Object('TestObject', { foo: 'bar' })]; }); From bc68816045614487a034141f2dc7607e3bd55832 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 25 Jul 2023 18:38:16 +1000 Subject: [PATCH 6/6] wip --- spec/CloudCode.spec.js | 10 +++++----- src/triggers.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 720a91c42d..db8e81dab7 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -206,7 +206,7 @@ describe('Cloud Code', () => { }); Parse.Cloud.afterFind('beforeFind', req => { expect(req.objects).toBeDefined(); - expect(req.objects[0].className).toBe('TestObject'); + expect(req.objects[0].get('foo')).toBe('bar'); }); const newObj = await new Parse.Query('beforeFind').first(); expect(newObj.className).toBe('TestObject'); @@ -220,7 +220,7 @@ describe('Cloud Code', () => { }); Parse.Cloud.afterFind('beforeFind', req => { expect(req.objects).toBeDefined(); - expect(req.objects[0].className).toBe('TestObject'); + expect(req.objects[0].get('foo')).toBe('bar'); }); const newObj = await new Parse.Query('beforeFind').first(); expect(newObj.className).toBe('TestObject'); @@ -234,7 +234,7 @@ describe('Cloud Code', () => { }); Parse.Cloud.afterFind('beforeFind', req => { expect(req.objects).toBeDefined(); - expect(req.objects[0].className).toBe('TestObject'); + expect(req.objects[0].get('foo')).toBe('bar'); }); const newObj = await new Parse.Query('beforeFind').get('objId'); expect(newObj.className).toBe('TestObject'); @@ -246,8 +246,8 @@ describe('Cloud Code', () => { Parse.Cloud.beforeFind('beforeFind', () => { return []; }); - Parse.Cloud.afterFind('beforeFind', () => { - throw 'afterFind should not run'; + Parse.Cloud.afterFind('beforeFind', req => { + expect(req.objects.length).toBe(0); }); const obj = new Parse.Object('beforeFind'); await obj.save(); diff --git a/src/triggers.js b/src/triggers.js index fb867967cd..a7e997e3a1 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -454,10 +454,10 @@ 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; } - object.className = className; return Parse.Object.fromJSON(object); }); return Promise.resolve()