Skip to content

Add awaitPersisted() method to wait for record sync #661

@KyleAMathews

Description

@KyleAMathews

Feature Request

Add a collection.awaitPersisted(id) method that returns a promise resolving when all active mutations for a given record have been persisted.

Motivation

Currently, to wait for a record to sync, you need to manually track the transaction:

const tx = collection.update(todoId, draft => { draft.completed = true })
await tx.isPersisted.promise

This requires holding onto the transaction reference. For fire-and-forget mutations or when multiple components might update the same record, it would be cleaner to await persistence by record ID:

collection.update(todoId, draft => { draft.completed = true })
// Later, anywhere in the app...
await collection.awaitPersisted(todoId)

Proposed API

collection.awaitPersisted(id: TKey): Promise<void>

Behavior:

  • Finds all active transactions affecting the given key
  • Waits for them to complete via Promise.all()
  • Rechecks for new mutations after completion (new txs might have started while waiting)
  • Loops until no active mutations remain
  • Resolves immediately if no active mutations exist for that key

Implementation

async awaitPersisted(key: TKey): Promise<void> {
  while (true) {
    const info = this.getOptimisticInfo(key)
    if (!info?.isOptimistic) return
    
    await Promise.all(info.mutations.map(m => m.transaction.isPersisted.promise))
    // Recheck - new mutations might have arrived while waiting
  }
}

The loop is necessary because new mutations can be added while waiting for current ones to complete.

Use Cases

  1. Navigation after save: Wait for sync before navigating away
  2. Sequential operations: Ensure first update completes before starting dependent operation
  3. Loading states: Show spinner until all pending changes for a record are persisted
  4. Testing: Easier to wait for specific records to sync in tests

Example Usage

// Simple case
await collection.awaitPersisted(todoId)

// Navigation
async function handleSaveAndClose() {
  collection.update(todoId, draft => { draft.text = newText })
  await collection.awaitPersisted(todoId)
  navigate('/todos')
}

// Testing
test('updates are persisted', async () => {
  collection.update('todo-1', draft => { draft.completed = true })
  await collection.awaitPersisted('todo-1')
  expect(mockApi.updateTodo).toHaveBeenCalled()
})

Implementation Notes

Builds on the existing getOptimisticInfo() infrastructure from #660 which already scans active transactions for a given key.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions