Repository: parse-community/Parse-SDK-JS
Pull request #1150 (2020-04-18 15:39:23)

add context to Parse.Object.save
Exposes the `context` object in `Parse.Object.save()` so that parameters can be made accessible in Cloud Code triggers `beforeSave`, `afterSave`.

Example:

```
const obj = new TestObject();
await obj.save(null, {context: {a: "a"}});

Parse.Cloud.beforeSave("TestObject", req => {
    console.log(req.context.a);
});

Parse.Cloud.afterSave("TestObject", req => {
    console.log(req.context.a);
});  
```
---
Don't merge until

- [ ] Concept agreed on in https://github.com/parse-community/parse-server/issues/6459
- [x] JS doc added in code for ‘context’

codecov[bot]: # [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1150?src=pr&el=h1) Report
> Merging [#1150](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1150?src=pr&el=desc) into [master](https://codecov.io/gh/parse-community/Parse-SDK-JS/commit/393b7233f657fe1fc3456650e7902dc059204bf7&el=desc) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1150/graphs/tree.svg?width=650&height=150&src=pr&token=8nCENM0QHj)](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1150?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master    #1150   +/-   ##
=======================================
  Coverage   92.30%   92.31%           
=======================================
  Files          54       54           
  Lines        5235     5241    +6     
  Branches     1169     1171    +2     
=======================================
+ Hits         4832     4838    +6     
  Misses        403      403           
```


| [Impacted Files](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1150?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/ParseObject.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1150/diff?src=pr&el=tree#diff-c3JjL1BhcnNlT2JqZWN0Lmpz) | `89.61% <100.00%> (+0.02%)` | :arrow_up: |
| [src/RESTController.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1150/diff?src=pr&el=tree#diff-c3JjL1JFU1RDb250cm9sbGVyLmpz) | `84.90% <100.00%> (+0.38%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1150?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1150?src=pr&el=footer). Last update [393b723...f5d787b](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1150?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

mtrezza: The context is transmitted as `_context` property in REST request.

- I did not find an existing similar use of internal properties in the REST request, but according to the [docs](https://docs.parseplatform.org/js/guide/#data-storage) a field name must start with a letter, so there cannot be a conflict with an object property:

> Key names must contain only numbers, letters, and underscore, and must start with a letter. 

So using `_context` as internal property in a REST request seems OK, it is also used for internal field names such as `_created_at` in the DB.
mtrezza: This unidirectionally exposes the internal trigger `context` to the client.
Should this be restricted to masterKey or to within Cloud Code?

@dplewis @acinader any thoughts?

Patch:
diff --git a/src/ParseObject.js b/src/ParseObject.js
index a10999f..5f78cb1 100644
--- a/src/ParseObject.js
+++ b/src/ParseObject.js
@@ -56,7 +56,8 @@ type SaveParams = {
 };
 
 type SaveOptions = FullOptions & {
-  cascadeSave?: boolean
+  cascadeSave?: boolean;
+  context?: AttributeMap;
 }
 
 // Mapping of class names to constructors, so we can populate objects from the
@@ -1179,6 +1180,7 @@ class ParseObject {
    *       <li>sessionToken: A valid session token, used for making a request on
    *       behalf of a specific user.
    *       <li>cascadeSave: If `false`, nested objects will not be saved (default is `true`).
+   *       <li>context: A dictionary that is accessible in Cloud Code `beforeSave` and `afterSave` triggers.
    *     </ul>
    *   </li>
    * </ul>
@@ -1192,6 +1194,7 @@ class ParseObject {
    *   <li>sessionToken: A valid session token, used for making a request on
    *       behalf of a specific user.
    *   <li>cascadeSave: If `false`, nested objects will not be saved (default is `true`).
+   *   <li>context: A dictionary that is accessible in Cloud Code `beforeSave` and `afterSave` triggers.
    * </ul>
    *
    * @return {Promise} A promise that is fulfilled when the save
@@ -1251,6 +1254,9 @@ class ParseObject {
     if (options.hasOwnProperty('installationId') && typeof options.installationId === 'string') {
       saveOptions.installationId = options.installationId;
     }
+    if (options.hasOwnProperty('context') && typeof options.context === 'object') {
+      saveOptions.context = options.context;
+    }
     const controller = CoreManager.getObjectController();
     const unsaved = options.cascadeSave !== false ? unsavedChildren(this) : null;
     return controller.save(unsaved, saveOptions).then(() => {
diff --git a/src/RESTController.js b/src/RESTController.js
index 1b3006a..82e3d31 100644
--- a/src/RESTController.js
+++ b/src/RESTController.js
@@ -21,6 +21,7 @@ export type RequestOptions = {
   batchSize?: number;
   include?: any;
   progress?: any;
+  context?: any;
 };
 
 export type FullOptions = {
@@ -222,6 +223,13 @@ const RESTController = {
       }
     }
 
+    // Add context
+    const context = options.context;
+    if (context !== undefined) {
+      payload._context = context;
+      delete options.context;
+    }
+
     if (method !== 'POST') {
       payload._method = method;
       method = 'POST';


Test patch:
diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js
index cc917da..e055a9d 100644
--- a/src/__tests__/ParseObject-test.js
+++ b/src/__tests__/ParseObject-test.js
@@ -1219,6 +1219,26 @@ describe('ParseObject', () => {
     });
   });
 
+  it('accepts context on save', async () => {
+    // Mock XHR
+    CoreManager.getRESTController()._setXHR(
+      mockXHR([{
+        status: 200,
+        response: { objectId: 'newattributes' }
+      }])
+    );
+    // Spy on REST controller
+    const controller = CoreManager.getRESTController();
+    jest.spyOn(controller, 'ajax');
+    // Save object
+    const context = {a: "a"};
+    const obj = new ParseObject('Item');
+    await obj.save(null, {context});
+    // Validate
+    const jsonBody = JSON.parse(controller.ajax.mock.calls[0][2]);
+    expect(jsonBody._context).toEqual(context);
+  });
+
   it('interpolates delete operations', (done) => {
     CoreManager.getRESTController()._setXHR(
       mockXHR([{