Nozbe / WatermelonDB

🍉 Reactive & asynchronous database for powerful React and React Native apps ⚡️
https://watermelondb.dev
MIT License
10.49k stars 589 forks source link

Do not synchronize record or table #1027

Open osvaldokalvaitir opened 3 years ago

osvaldokalvaitir commented 3 years ago

I am facing two synchronization problems.

1 - I have a table that serves to store application data. I can't use Watermelon's LocalStorage. How can I prevent this table from being synchronized.

2 - I have an order page that cannot be sent to the server until the user has entered all the information. The user can close the application and continue with the current order, it happens that until he finishes I cannot send it to the server. The solution I would have at the moment would be to make a 'new_order' table to create and then move to the 'order', but I would have to create a relationship for two tables, which I don't think is right, the best would be if I had a status so that I could ignore this record.

I found this problem in the issue, but I don't know what the solution was: https://github.com/Nozbe/WatermelonDB/issues/206

For the time being in item 2, I'm resolving by placing order._raw._status = 'draft' I'm doing tests now, I don't know if it's going to break something now or in the future.

sidferreira commented 3 years ago

I believe if we had a way to overwrite

const createdQuery = Q.where(columnName('_status'), 'created')
const updatedQuery = Q.where(columnName('_status'), 'updated')

for fetchLocal is a start...

osvaldokalvaitir commented 3 years ago

The test did not work with order._raw._status = 'draft'.

After a while, the status automatically changes to 'created'.

Until I have a good solution, I will duplicate the order table and I will prevent this table from being synchronized.

osvaldokalvaitir commented 3 years ago

@sidferreira thanks for dedicating your time to my problem =)

KrisLau commented 2 years ago

2 - I have an order page that cannot be sent to the server until the user has entered all the information. The user can close the application and continue with the current order, it happens that until he finishes I cannot send it to the server. The solution I would have at the moment would be to make a 'new_order' table to create and then move to the 'order', but I would have to create a relationship for two tables, which I don't think is right, the best would be if I had a status so that I could ignore this record.

I have something similar to this right now in my application. The user has go through a couple pages before the creation process is complete. I basically just either send the partially completed data through the navigation params or I save the data to AsyncStorage and fetch it later when needed and clear it after the record is created

osvaldokalvaitir commented 2 years ago

2 - I have an order page that cannot be sent to the server until the user has entered all the information. The user can close the application and continue with the current order, it happens that until he finishes I cannot send it to the server. The solution I would have at the moment would be to make a 'new_order' table to create and then move to the 'order', but I would have to create a relationship for two tables, which I don't think is right, the best would be if I had a status so that I could ignore this record.

I have something similar to this right now in my application. The user has go through a couple pages before the creation process is complete. I basically just either send the partially completed data through the navigation params or I save the data to AsyncStorage and fetch it later when needed and clear it after the record is created

As there is no way to inform which table I want to synchronize, I created a table that holds temporary data and then sends it to the table I want. And in the synchronization function I delete the temporary table so that it doesn't go in synchronization.

KennanChan commented 1 year ago

I have something similar in my application. I build an adapter layer on top of wdb synchronize. An adapter is designed to transform database changes into a new one, something like this:

(imagine an interceptor in backend development)

interface DatabaseAdapter {
    toLocal(changes: SyncDatabaseChangeSet, database: Database): Promise<SyncDatabaseChangeSet>;
    toRemote(changes: SyncDatabaseChangeSet, database: Database): Promise<SyncDatabaseChangeSet>;
}

Then I have a registry to hold all the adapters:

interface AdapterRegistry {
    register(adapter: DatabaseAdapter): void;
}
class AdapterRegistry implements AdapterRegistry, DatabaseAdapter {
    private registry: DatabaseAdapter[] = []
    register(adapter: DatabaseAdapter): void {
        this.registry.push(adapter);
    }

    toLocal(changes: SyncDatabaseChangeSet, database: Database): Promise<SyncDatabaseChangeSet> {
        return this.registry.reduce((promise, adapter) => {
            return promise.then(changes => adapter.toLocal(changes, database));
        }, Promise.resolve(changes));
    }

    toRemote(changes: SyncDatabaseChangeSet, database: Database): Promise<SyncDatabaseChangeSet> {
        return this.registry.reduce((promise, adapter) => {
            return promise.then(changes => adapter.toRemote(changes, database));
        }, Promise.resolve(changes));
    }
}

After pulling changes from backend, I then pass the changes to AdapterRegistry and return the result to wdb. Before pushing changes to backend, I pass the changes to AdapterRegistry and push the result to backend.

export const adapterRegistry = new AdapterRegistry();

export function sync(options) {
    const database = options.database;
    return synchronize({
            ...options,
        pullChanges: async (...args) => {
            const pullResult = await options.pullChanges(...args);
            return {
                ...pullResult,
                changes: await adapterRegistry.toLocal(pullResult.changes, database);
            }
        },
        pushChanges: async (pushArgs) => {
                    pushArgs.changes = await adapterRegistry.toRemote(pushArgs.changes, database);
            await options.pushChanges(pushArgs);
        }
    })
}

As to this thread, I assume a DatabaseAdapter like this will be OK:

class IgnoreTablesDatabaseAdapter implements DatabaseAdapter {
    private ignoredTableNames: string[]

    constructor(ignoredTableNames: string[]) {
        this.ignoredTableNames = ignoreTableNames;
    }

    async toLocal(changes: SyncDatabaseChangeSet, database: Database): Promise<SyncDatabaseChangeSet> {
        return changes;
    }

    async toRemote(changes: SyncDatabaseChangeSet, database: Database): Promise<SyncDatabaseChangeSet> {
        return Object.keys(changes)
            .filter(tableName => !this.ignoredTableNames.includes(tableName))
            .reduce((result, tableName) => {
                result[tableName] = changes[tableName];
                return result;
        }, {})
    }
}
Alarees commented 6 months ago

The test did not work with order._raw._status = 'draft'

@osvaldokalvaitir i did try your way out, with a little bit of changes.

i used order._raw._status = 'synced' instead of order._raw._status = 'draft'