Parse.Query with .include('PointerClass') on Electron returns old Data

This particular application uses the ‘parse/node’ module and does not allow client side SDK usage.

I have an API endpoint which updates a class “SecondaryData”.
The user selects a singular primary “PrimaryData” which has a pointer to “SecondaryData” and the API updates the “SecondaryData” only and directly via the id. The client then refreshes a Parse.Query(‘PrimaryData’) with a .includes(‘SecondaryData’)

The problem is, the includes contains stale data. It works fine in node. The data remains stale until the application is restarted. (You can make the request as many times as you want) — If you update the ‘PrimaryData’ object, the includes no longer has stale data.

I have investigated the problem a bit in RESTController.js ParseQuery.js and ParseObject.js.
I can’t quite understand, but in fromJSON it is provided the correct data, and then somewhere inside of fromJSON the data is replaced with the stale data. I’m not exactly sure where this stale data is coming from. While using vscode debugger it seems strange, but when the id is overwritten

if (otherAttributes.objectId) {
  o.id = otherAttributes.objectId;
}

All of the old data just seems to “pop in”.

Any thoughts on the inner workings of Parse’s local object cache? would be appreciated.

Thanks!

Could you please share the code that you are using to fetch and update the data? A reproducible example of the issue would help a lot.

Hey David, thanks for the response. I will try to create a reproducible example project. Might take me a bit.

Hi David,
Please see

setup is npm install and then npm run prestart for a mongodb instance or you can use your own mongo instance. Then use vscode to run node development and subsequently electron

Click each of the buttons in sequence. — you’ll see that “Test2” is not returned for electron

I believe if you could share here only the parse operations that you are doing, it would be much easier for the community to help you.

I’m not sure what you mean by “share the parse operations” — could you share an example or is this good enough?

Create

      const o = new Parse.Object('TestClass',req.body.attr)
      const serial = (await randomBytes(40)).toString('hex')
      o.set('SerialNo', serial)
      await o.save(null, {useMasterKey:true});

“Update”

    // oTestClass, Action passed as a parameter
    const qTestClassSub = await new Parse.Query(TestClassSub)
    qTestClassSub.equalTo('SerialNo', serialNo)

    let oSub = await qTestClassSub.first({ sessionToken, useMasterKey })
    if (!oSub) {
      oSub = new TestClassSub()
      oSub.set('SerialNo', serialNo)
    }

    // Action: string = "Test" or "Test2" depending on which
    // static function is called on the class
    oSub.set('Action', Action) 

    await oSub.save(null, {
      sessionToken,
      useMasterKey,
    })
    if (!oTestClass.get('TestClassSub')) {
      // Then need to set pointer data
      oTestClass.set('TestClassSub', oSub)
      await oTestClass.save(null, { sessionToken, useMasterKey })
    }

“Retreive”

      const {id} = req.params;
      const q = new Parse.Query('TestClass')
      q.equalTo("objectId",id)
      q.include('TestClassSub');
      const o = await q.first({useMasterKey:true})

“Update2” (Same as Update1, different “Action”)

“Retrieve2”

Same as Retrieve

Could you share how Action is initialized? Does it belong to a custom class? Or is it a regular Parse.Object?

It’s a string. “Test” and “Test2” were the test cases I used.
oTestClass is initialized from a JSON object provided by the browser client.

      o = new TestClass(o,{sessionToken:undefined, useMasterKey:true});
      // fn in this case is calls a static function on TestClass
      await TestClassSub[fn](o,{sessionToken:undefined,useMasterKey:true});
     // o becomes oTestClass in the static function

Sorry. I meant to say TestClassSub. Can you share how it is initialized? Is it a custom class? Can you share the classe definition?

It is indeed a custom class, that extends Parse.Object.

Initialization of TestClassSub

    const qTestClassSub = await new Parse.Query(TestClassSub)
    qTestClassSub.equalTo('SerialNo', serialNo)
    let oSub = await qTestClassSub.first({ sessionToken, useMasterKey })
    if (!oSub) {
      oSub = new TestClassSub()
      oSub.set('SerialNo', serialNo)
    }

Class Definition

class TestClassSub extends Parse.Object {
  constructor(attr, options = {}) {
    super('TestClassSub', attr, options)
    this.useMasterKey = options.useMasterKey
    this.sessionToken = options.sessionToken
  }
  static async _SetState(oTestClass,{Test=false,Action = ''}){
    const {sessionToken, useMasterKey } = this;
    const serialNo = oTestClass.get('SerialNo')
    if(serialNo===undefined){
      throw new Error('TestClass has no SerialNo')
    }
    const qTestClassSub = await new Parse.Query(TestClassSub)
    qTestClassSub.equalTo('SerialNo', serialNo)

    let oSub = await qTestClassSub.first({ sessionToken, useMasterKey })
    if (!oSub) {
      oSub = new TestClassSub()
      oSub.set('SerialNo', serialNo)
    }
    oSub.set('Action', Action)

    await oSub.save(null, {
      sessionToken,
      useMasterKey,
    })
    if (!oTestClass.get('TestClassSub')) {
      // Then need to set pointer data
      oTestClass.set('TestClassSub', oSub)
      try {
        await oTestClass.save(null, { sessionToken, useMasterKey })
      } catch (err) {
        console.error(
          'TestClass does not have TestClassSub Pointer, but it failed to save.',
          err
        )
      }
    }

  }
  static async Test(oTestClass, { sessionToken, useMasterKey }){
    await this._SetState(
      oTestClass,
      {
        Test: true,
        Action: 'Test',
      },
      { sessionToken, useMasterKey }
    )
  }
  static async Test2(oTestClass, { sessionToken, useMasterKey }){
    await this._SetState(
      oTestClass,
      {
        Test2: true,
        Action: 'Test2',
      },
      { sessionToken, useMasterKey }
    )
  }
}

Does it work if you use the regular Parse.Object class?

I did not test if it worked with a regular Parse.Object.

My fix ended up being to fetch() the instantiated object (which was from JSON) before modifying it.

I do still find it strange that you don’t need to fetch it in node.js vs electron.