Skip to content

Fix OPFS sync metadata recovery#570

Open
kilbot wants to merge 1 commit into
mainfrom
fix/opfs-sync-count-recovery
Open

Fix OPFS sync metadata recovery#570
kilbot wants to merge 1 commit into
mainfrom
fix/opfs-sync-count-recovery

Conversation

@kilbot

@kilbot kilbot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Recover from OPFS/RxDB JSON corruption in sync metadata count streams, matching the demo-site templates count failure path.
  • Drop only the affected local sync metadata collection and reload once; the next load rebuilds sync state from the remote endpoint.
  • Add regression coverage for the production-shaped requestRemote/SyntaxError count error.

Design decisions (preserve through rebases)

  • Recovery is scoped to sync metadata collections — these contain reconciliation state, not user-authored business records, and can be rebuilt from the remote endpoint.
  • Recovery is guarded by a per-collection session key — prevents reload loops if OPFS remains corrupted after one reset attempt.

Test plan

  • On a browser profile with the reported demo-site OPFS error, load the app and confirm it resets the corrupted sync metadata once and reloads instead of surfacing the could not requestRemote error.
  • After reload, open receipt/template-backed flows and confirm templates sync from the server again.
  • Update product stock on the demo site and confirm the mutation flow is no longer interrupted by the templates sync count error.
  • pnpm --filter @wcpos/query test --runInBand
  • pnpm --filter @wcpos/query typecheck
  • pnpm --filter @wcpos/query lint (0 errors; existing hook dependency warnings remain)

🤖 Generated with Codex

Summary by CodeRabbit

  • Bug Fixes

    • Improved resilience during collection synchronization with automatic recovery for corrupted local sync metadata.
    • Enhanced remote document count computation with error handling to prevent synchronization failures.
  • Tests

    • Added test coverage for sync metadata recovery scenarios.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR improves sync collection replication resilience by introducing recovery logic for corrupted local metadata. A new recovery function conditionally removes affected collections and reloads the app when storage errors occur, integrated into the remote count stream to allow graceful degradation when queries fail.

Changes

Sync Collection Recovery

Layer / File(s) Summary
Sync Recovery Function
packages/query/src/logs-storage-recovery.ts
Adds recoverSyncCollectionStorage with a session-keyed constant to prevent repeated recovery attempts; conditionally removes a synced collection on recoverable errors and triggers reload.
Remote Count Resilience
packages/query/src/collection-replication-state.ts
Updates RXJS imports and integrates recoverSyncCollectionStorage into the remoteCount$ stream via catchError; on recovery success, emits 0 to unblock downstream computation; on failure, re-throws the error.
Recovery Tests
packages/query/tests/logs-recovery.test.ts
Adds test case verifying recovery behavior: detects corrupted requestRemote errors, removes collection once, sets sessionStorage flag, and triggers single reload.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • wcpos/monorepo#549: Introduces foundational recoverLogsCollectionStorage and isRecoverableLogsStorageError that the main PR extends with sync-specific recovery.

Poem

🐰 A sync collection stumbles, metadata breaks,
But now we catch the fall with grace—
Recovery blooms, one try per page,
And remoteCount flows with zero's embrace.
Resilience in RXJS streams, oh what joy! 🌱

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix OPFS sync metadata recovery' directly describes the main objective of the PR—recovering from OPFS sync metadata corruption—and is a clear, specific reference to the primary change across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/opfs-sync-count-recovery

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8e371f5b8f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


const sessionKey = `${SYNC_RECOVERY_SESSION_KEY_PREFIX}${collection.name}`;
const sessionStorage = getSessionStorage();
if (sessionStorage?.getItem(sessionKey) === '1') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope the recovery guard to the fast-store DB

When the same collection name is recovered once, this session key blocks recovery for every other store database in the tab because it only includes collection.name; createFastStoreDB() creates distinct fast_store_v5_${store.localID} databases, so switching stores after recovering (for example) templates in one store will make a later templates OPFS corruption in another store return false and surface the count error instead of resetting. Include the collection's database name (or another store/database identifier) in the guard key so the one-shot reload protection is per corrupted DB, not global per collection name.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/query/tests/logs-recovery.test.ts (1)

67-86: ⚡ Quick win

Consider adding a test for sync collection removal failure.

The test suite includes a case for logs collection removal failure (line 115), which verifies the session flag is not set when remove() throws. Adding an equivalent test for recoverSyncCollectionStorage would ensure consistent error handling and prevent regressions.

Suggested test case
it('does not set the session flag when sync collection removal fails', async () => {
	const removeError = new Error('remove failed');
	const remove = jest.fn(async () => {
		throw removeError;
	});
	const syncCollection = { name: 'templates', remove };
	const reload = jest.fn();

	await expect(
		recoverSyncCollectionStorage(
			syncCollection,
			new Error(
				'could not requestRemote: {"methodName":"count","error":{"name":"SyntaxError","message":"Unexpected token"}}'
			),
			{ reload }
		)
	).rejects.toThrow(removeError);

	expect(remove).toHaveBeenCalledTimes(1);
	expect(
		globalThis.sessionStorage.getItem('wcpos_sync_storage_recovery_attempted_templates')
	).toBeNull();
	expect(reload).not.toHaveBeenCalled();
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/query/tests/logs-recovery.test.ts` around lines 67 - 86, Add a new
test in packages/query/tests/logs-recovery.test.ts that mirrors the logs
removal-failure case but targets recoverSyncCollectionStorage: create a remove
mock that throws a specific Error (e.g., removeError), construct syncCollection
= { name: 'templates', remove } and a jest fn reload, then call
recoverSyncCollectionStorage(...) wrapped in await
expect(...).rejects.toThrow(removeError); assert remove was called once, that
globalThis.sessionStorage.getItem('wcpos_sync_storage_recovery_attempted_templates')
is null (i.e., no session flag set), and that reload was not called; name the
test like "does not set the session flag when sync collection removal fails".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/query/tests/logs-recovery.test.ts`:
- Around line 67-86: Add a new test in
packages/query/tests/logs-recovery.test.ts that mirrors the logs removal-failure
case but targets recoverSyncCollectionStorage: create a remove mock that throws
a specific Error (e.g., removeError), construct syncCollection = { name:
'templates', remove } and a jest fn reload, then call
recoverSyncCollectionStorage(...) wrapped in await
expect(...).rejects.toThrow(removeError); assert remove was called once, that
globalThis.sessionStorage.getItem('wcpos_sync_storage_recovery_attempted_templates')
is null (i.e., no session flag set), and that reload was not called; name the
test like "does not set the session flag when sync collection removal fails".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2edc6c7b-9778-4216-b196-bace2ae7f455

📥 Commits

Reviewing files that changed from the base of the PR and between 0893756 and 8e371f5.

📒 Files selected for processing (3)
  • packages/query/src/collection-replication-state.ts
  • packages/query/src/logs-storage-recovery.ts
  • packages/query/tests/logs-recovery.test.ts

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

📊 Test Coverage Report

Package Statements Branches Functions Lines
@wcpos/core 🔴 22.0% 🔴 25.6% 🔴 21.3% 🔴 21.5%
@wcpos/components 🔴 24.6% 🔴 27.3% 🔴 16.8% 🔴 24.3%
@wcpos/database 🟡 60.2% 🔴 55.0% 🟡 62.7% 🔴 59.7%
@wcpos/hooks 🔴 52.4% 🔴 50.1% 🔴 45.0% 🔴 52.4%
@wcpos/utils 🔴 55.9% 🟡 61.5% 🟡 71.4% 🔴 53.8%
@wcpos/query 🟢 82.6% 🟡 75.0% 🟡 76.1% 🟢 82.8%
Average 🔴 49.6% 🔴 49.1% 🔴 48.9% 🔴 49.1%
Coverage Legend
  • 🟢 Good (≥80%)
  • 🟡 Moderate (60-79%)
  • 🔴 Needs improvement (<60%)
  • ⚪ No coverage data
--- 🤖 Updated by GitHub Actions • [View full report](https://github.com/wcpos/monorepo/actions/runs/27219934185)

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployment Summary

Item Status
Preview URL https://wcpos--jqs5al1ph4.expo.app
E2E Tests ✅ Passed
Commit 3f32e13

🔗 Quick Links


🤖 Updated by GitHub Actions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant