Troubleshooting

Start with sync.status.lastError for sync failures. For one rejected record, inspect its metadata with sync.debug.getRecordMeta(sync.todos, id).

Validation error

status.lastError.reason === "validation"

A local or server value does not match the schema. Check for:

  • Undeclared fields.
  • Values that cannot be stored as JSON.
  • A missing collection id.
  • Zod refinements that reject default values.

Network error

status.lastError.reason === "network"

The client could not reach the sync endpoint, the response was not ok, or no fetch implementation was available.

Dirty operations stay pending. The client schedules a retry while data remains dirty.

Authentication error

status.lastError.reason === "auth"

The sync endpoint returned 401 or 403. Refresh auth state, reauthenticate, or clear user-local data when switching accounts.

Conflict error

status.lastError.reason === "conflict"

The edit was based on an older version of the record. The current v1 behavior is to:

  • Keep the local edited value.
  • Record the conflict in metadata.
  • Stop retrying the rejected operation.

Use debug.getRecordMeta(collection, id) to inspect conflict details.

Pending ops are empty after a rejection

An operation rejected by the server is treated as handled, so it is removed from pending operations. This applies to:

  • Validation errors.
  • Access denials.
  • Conflicts.
  • Missing records.
  • Server-error rejections.

The local edited value may remain with lastError metadata, but the operation is no longer dirty.

A test sees no pending operation after an edit

Local proxy writes are batched. Use fake timers to advance 100 ms when needed, then call await sync.flush() before reading debug.getPendingOps().

Data appears under the wrong user

Construct the client with a stable per-user default adapter, such as storage: { namespace: 'my-app:user_1' }, then call await sync.hydrate(). The namespace scopes IndexedDB, local storage, session storage, and BroadcastChannel state.

Clear local data

Use:

await sync.clearLocalData()

This clears:

  • Synced records and account state.
  • Device state.
  • Session state.

It also notifies other tabs in the same namespace.

Keep secrets out of local state

Synced records, device, and session are stored in browser-managed persistence. Treat them as user data caches, not secure secret storage.