-
-
Notifications
You must be signed in to change notification settings - Fork 121
Description
Description
When using a mutating listener to reject/delete incoming synced data, the deletion doesn't propagate back to the originating client. However, explicit delRow calls after sync completes do propagate correctly.
Use Case
Server-side validation: A server wants to reject invalid data that clients attempt to sync. The pattern is to use a mutating listener that deletes rows with invalid values.
Client side validation satisfies the requirements for UX, but does not satisfy the security requirements - that needs to happen server side.
The ideal implementation would have the same validation run on both client and server. But in this case we omit the client validation to demonstrate that the server does not behave as expected.
Reproduction
import {createMergeableStore} from 'tinybase'
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local'
const clientStore = createMergeableStore()
const serverStore = createMergeableStore()
// Server rejects rows with name "Delete Me"
serverStore.addRowListener('jobs', null, (_store, _tableId, rowId) => {
const row = serverStore.getRow('jobs', rowId)
if (row.name === 'Delete Me') {
serverStore.delRow('jobs', rowId)
}
}, true) // mutator = true
const clientSync = await createLocalSynchronizer(clientStore)
const serverSync = await createLocalSynchronizer(serverStore)
await clientSync.startSync()
await serverSync.startSync()
clientStore.setRow('jobs', 'job-1', {name: 'Delete Me'})
await new Promise(r => setTimeout(r, 300))
console.log(serverStore.getRow('jobs', 'job-1')) // {} ✓ deleted
console.log(clientStore.getRow('jobs', 'job-1')) // {name: 'Delete Me'} ✗ still exists
Expected Behavior
The server's deletion should sync back to the client, removing the row from the client store.
Actual Behavior
- Server correctly deletes the row locally
- Client retains the original data
- The tombstone doesn't appear to propagate
Working Case
If I wait for sync to complete, then delete in a separate operation, it works:
clientStore.setRow('jobs', 'job-1', {name: 'Test'})
await waitForSync()
serverStore.delRow('jobs', 'job-1') // This DOES sync back correctly
Questions
- Is this expected behavior for mutating listeners during sync?
- Is there a recommended pattern for server-side validation that rejects incoming sync data?
Environment
- TinyBase 7.3.1
- Node.js (tested with createLocalSynchronizer)