How to pass data for sync function using watermelondb

1.2k views Asked by At

Good day everyone, I am working with watermelondb and I have the code below, but I don't know how to actually use it. I am new in watermelondb and I don't know how to pass data as props to the pullChanges and pushChanges objects. How do I pass necessary data like changes and lastPulledAt from the database into the sync function when I call it. And I need more explanation on the migrationsEnabledAtVersion: 1 too. Thanks in advance for your gracious answers.

import { synchronize } from '@nozbe/watermelondb/sync'

async function mySync() {
  await synchronize({
    database,
    pullChanges: async ({ lastPulledAt, schemaVersion, migration }) => {
      const urlParams = `last_pulled_at=${lastPulledAt}&schema_version=${schemaVersion}&migration=${encodeURIComponent(JSON.stringify(migration))}`
      const response = await fetch(`https://my.backend/sync?${urlParams}`)
      if (!response.ok) {
        throw new Error(await response.text())
      }

      const { changes, timestamp } = await response.json()
      return { changes, timestamp }
    },
    pushChanges: async ({ changes, lastPulledAt }) => {
      const response = await fetch(`https://my.backend/sync?last_pulled_at=${lastPulledAt}`, {
        method: 'POST',
        body: JSON.stringify(changes)
      })
      if (!response.ok) {
        throw new Error(await response.text())
      }
    },
    migrationsEnabledAtVersion: 1,
  })
}

2

There are 2 answers

0
Guilherme Maier On

Watermelondb's documentation is terrible and its link to typescript even worse. I spent almost a week to get 100% synchronization with a simple table, now I'm having the same problems to solve the synchronization with associations. Well, the object you need to return in pullChanges is of the following form:

return {
    changes: {
        //person is the name of the table in the models
        person: {
            created: [
                {
                    // in created you need to send null in the id, if you don't send the id it doesn't work
                    id: null,
                    // other fields of your schema, not model
                }
            ],
            updated: [
                {
                    // the fields of your schema, not model
                }
            ],
            deleted: [
                // is a string[] consisting of the watermelondb id of the records that were deleted in the remote database
            ],
        }
    },
    timestamp: new Date().getTime() / 1000
}

In my case, the remote database is not a watermelondb, it's a mySQL, and I don't have an endpoint in my API that returns everything in the watermelon format. For each table I do a search with deletedAt, updatedAt or createdAt > lastPulledAt and do the necessary filtering and preparations so that the data from the remote database is in the schema format of the local database.

In pushChanges I do the reverse data preparation process by calling the appropriate creation, update or deletion endpoints for each of the tables.

It's costly and annoying to do, but in the end it works fine, the biggest problem is watermelon's documentation which is terrible.

0
Takudzwa Neil Bvungidzire On
    export async function sync(id: Number, callback: (success: boolean) => void) {
  const user_id = id;
  const lastPulledAtTime = await AsyncStorage.getItem('LAST_SYNC_TIME')
  synchronize({
    database,
    pullChanges: async ({ lastPulledAt }) => {
      const response = await fetch(SYNC_API_URL_PULL, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${BEARER_TOKEN}`,
        },
        body: JSON.stringify({ lastPulledAtTime, user_id }),
      });

      if (!response.ok) {
        throw new Error(await response.text());
      }

      const { changes, timestamp } = await response.json();
      return { changes, timestamp };
    },
    pushChanges: async ({ changes, lastPulledAt }) => {
      try {
        const formattedChanges = {
          persons: {
            created: [],
            updated: [],
            deleted: [],
          },
          contacts: {
            created: [],
            updated: [],
            deleted: [],
          },
        };
    
        const hasChanges = await hasUnsyncedChanges({ database });
        if (hasChanges) {
          const persons = await database.get('persons').query(Q.where('_status', 'updated'));
          const contacts = await database.get('contacts').query(Q.where('_status', 'updated'));
    
          persons.forEach(serializedRecord => {
            const { _status, id, ...fields } = serializedRecord._raw;
    
            if (_status === 'updated') {
              const updatedFields = {
                firstName: fields.first_name,
                lastName: fields.last_name,
                country: fields.country,
                subdivision: fields.subdivision,
                city: fields.city,
                gender: fields.gender,
              };
              formattedChanges.persons.updated.push(updatedFields);
            }
          });
    
          contacts.forEach(serializedRecord => {
            const { _status, id, ...fields } = serializedRecord._raw;
    
            if (_status === 'updated') {
              const updatedFields = {
                channel: fields.channel,
                description: fields.description,
                data: fields.data,
              };
              formattedChanges.contacts.updated.push(updatedFields);
            }
          });
        }
        const response = await fetch(`${SYNC_API_URL_PUSH}`, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${BEARER_TOKEN}`,
          },
          body: JSON.stringify({ changes: formattedChanges, user_id }),
        });
    
        if (!response.ok) {
          throw new Error(await response.text());
        }
    
        callback(true); // Synchronization succeeded
      } catch (error) {
        console.error('Error pushing changes:', error.message);
        callback(false); // Synchronization failed
      }
    },
    
    
  }).catch((error) => {
    console.error('Error syncing:', error.message);
    callback(false); // Synchronization failed
  });
}

this is how you can do it by looking if there are any unsynched changes and extracting the changed fields and formating them to be able to suit your backend. Not the most ideal but its a start