Skip to content

Sending unsaved Parse Objects to Client #7004

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

Open
fastrde opened this issue Nov 11, 2020 · 13 comments
Open

Sending unsaved Parse Objects to Client #7004

fastrde opened this issue Nov 11, 2020 · 13 comments
Labels
type:feature New feature or improvement of existing feature

Comments

@fastrde
Copy link
Contributor

fastrde commented Nov 11, 2020

Is your feature request related to a problem? Please describe.
I use Parse Server as App-Backend and store some informations in the Parse DB. Other informations are received live from third party backends via cloud-code. To unify the communication between the Parse Backend and the App I tried to generate temporary Parse Objects to send these Objects to the App but I aren't allowed to store them in the Parse DB. When I try to send the object to the App I got the error "Cannot create a pointer to an unsaved ParseObject"

Describe the solution you'd like
Send the Objects to the App (perhaps with a "temp_xyz" objectId like the local objectId)

@mtrezza
Copy link
Member

mtrezza commented Nov 11, 2020

Thanks for suggesting.

A Parse Object can be considered proprietary to the Parse ecosystem. It does not only have the key-value components that you set but also a number of properties and mechanisms in the background. In other words, a Parse Object is a complex container with much more "under the hood", defined for specific usage.

If you want to transmit 3rd party data or any other data that is not a Parse Object by definition, why don't you define a custom container and parse it in the app?

@fastrde
Copy link
Contributor Author

fastrde commented Nov 11, 2020

Thank you for your lightning fast answer.
That's the way I'm doing it right now. I'm refactoring the backend at the moment and I wondered why I can't send temporary Parse Objects, so i didn't have to maintain the logic in the app and can streamline all communication between parse server and the app, so that only Parse Objects are send and not some custom data structures.

But it's probably much more effort to implement something like this in the server and sdks than parsing the handful custom structures directly in the app.

@mtrezza
Copy link
Member

mtrezza commented Nov 11, 2020

it's probably much more effort to implement something like this in the server and sdks

It's not so much the effort but the viability of such a change. If we can demonstrate a use case and the value / viability of such a change, we can go to the next step and determine its feasibility.

@fastrde
Copy link
Contributor Author

fastrde commented Nov 12, 2020

This is my use case.

We talked about (1) in the picture. There I want to use Parse.Objects that are stored or not stored in the Parse-DB, so all the app-communication is handled the same way. The "fix connector" is the part of the orange module that handles the communication with the app and in an ideal world it get's Parse.Objects in and sends (perhaps temporary) Parse.Objects to the app. (1) Is fully under my control. I can send custom structures and parse them (as mentioned) later in the app.

(2) and (3) are both adapters that implement the same functionality and are interchangeable. For example at side A the Information is directly stored in the Parse-DB, is queried from adapter1 send the the connector and is forwarded to the app.
At side B the same data is stored externally in a third party application, queried from adapter2 transformed to temporary parse objects and forwarded to the connector for delivery.

At (4) I want to have Parse.Objects as well. The Interface is defined by me, but when I go there for custom structures I have to convert everything that adapter1 read from the Parse-DB to this custom structure, because which adapter is used is not in my responsibility.

In some cases I would have Parse.Objects with ClassNames that didn't (and shouldn't) exist in the Parse-DB. The interchangeability of the adapters make the temporary objects a nice (and in my opinion really helpful) feature.

In the meanwhile I dig a bit in the code and found Parse.Object._toFullJSON what looks like the on-the-wire Parse Object representation. On the client side (JS-SDK) the JSON is parsed to a valid Parse.Object with an objectId of undefined. When I call save I got the (correct) error that the user is not allowed to access non-existsted class: "NoDBObject".
What I didn't try yet is pinning the object locally (and only locally). When this works i can build a PoC of my use case and look for side effects.

parse
green: Parse-Server
orange: one (cloud code)-module which implements one function (there are many)
yellow: defined interface between adapter and connector

@davimacedo
Copy link
Member

In the first message you said that you got Cannot create a pointer to an unsaved ParseObject error. Could you share the code so we can try to advise? Also, you can consider creating a custom storage adapter according to your needs.

@fastrde
Copy link
Contributor Author

fastrde commented Nov 12, 2020

I get the error on the Server before sending (an unsaved) ParseObject via cloud code. That codesnippet should explain the situation.

Parse.Cloud.define("notworkingfunction", async (request) => {
  const data = await getSomeDataFromThirdParty();
  const notSavedObject = new Parse.Object("NotExistingClass");
  notSavedObject.set(data);
  return notSavedObject; // throws
});

before the Server sends the Object it tries to call toPoiner() on the object. In this function the error is thrown.

  if (!this.id) {
   throw new Error('Cannot create a pointer to an unsaved ParseObject');
  }

@mtrezza
Copy link
Member

mtrezza commented Nov 12, 2020

In principle I don't see an issue with composing a Parse Object in Cloud Code, sending it to a client, continue composing it there and saving it from the client side. Seems like a simple use case.

The question is what background mechanism require a Parse Object to be saved before being sent between server and client.

As a workaround, did you try to set a dummy object ID?

@fastrde
Copy link
Contributor Author

fastrde commented Nov 12, 2020

Setting a dummy objectId works, because that prevents the if (!this.id) check to throw, but setting the id explicitly implies to the client, that this is an id that exists on the server. What this means to the client for calls to Parse.Object and Parse.Query methods is unclear to me at the moment. When I send the object with an objectId of undefined the Object is handled by the client in the same way a locally created object would be handled. I think that should be the preferred way to handle Objects that doesn't exist on the server.

That function is called in the Parse-JS-SDK to get the id when an Object with an objectId = undefined is received from the server and pinned. As you can see a localId is generated before the object is pinned.

 _getId(): string {
    if (typeof this.id === 'string') {
      return this.id;
    }
    if (typeof this._localId === 'string') {
      return this._localId;
    }
    const localId = 'local' + uuidv4();
    this._localId = localId;
    return localId;
  }

@mtrezza
Copy link
Member

mtrezza commented Nov 12, 2020

When I send the object with an objectId of undefined the Object is handled by the client in the same way a locally created object would be handled. I think that this should be the preferred way to handle Objects that don't exist on the server.

I agree, without having looked into any further implications of sending an object without ID. Maybe the blame line can give you further hints as to why there is this check of ID being set.

In any case, feel free to do some further investigation and if nothing speaks against it, create a PR to remove this ID check. The PR should include test cases for handling objects without ID.

@davimacedo
Copy link
Member

If you return the JSON, it should work. Give a try with:

Parse.Cloud.define("notworkingfunction", async (request) => {
  const data = await getSomeDataFromThirdParty();
  const notSavedObject = new Parse.Object("NotExistingClass");
  notSavedObject.set(data);
  return notSavedObject._toFullJSON();
});

@fastrde
Copy link
Contributor Author

fastrde commented Nov 17, 2020

I didn't get the test environment up and running...
Test the changes on the client side is no problem, but i am struggling to test the changes on the server side. To test the changes the parse-server have to depend on my local Parse-JS-SDK directory. I linked my local parse installation with npm link --save ../Parse-JS-SDK and build a docker image docker build --tag parse-server-dev . in the parse-server directory, but in the image is always parse 2.17.0 from npm.
Have you a hint what am I missing?

@davimacedo
Copy link
Member

Have you run npm run build in both projects?

@fastrde
Copy link
Contributor Author

fastrde commented Nov 18, 2020

@davimacedo thank you, but yes I did. It's possibly the way the Docker-Container is build. npm ci is using the package-lock.json file to determine which versions to install. I added a COPY command to copy my parse node_module into the Docker-Container and override the default one. Feels hacky but works for the moment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type:feature New feature or improvement of existing feature
Projects
None yet
Development

No branches or pull requests

3 participants