Skip to content

fix(idxdb-store): reset IndexedDB on client version downgrade - #355

Open
kutluhaneth46 wants to merge 4 commits into
0xMiden:mainfrom
kutluhaneth46:fix/idxdb-reset-store-on-version-downgrade-349
Open

fix(idxdb-store): reset IndexedDB on client version downgrade#355
kutluhaneth46 wants to merge 4 commits into
0xMiden:mainfrom
kutluhaneth46:fix/idxdb-reset-store-on-version-downgrade-349

Conversation

@kutluhaneth46

Copy link
Copy Markdown

Fixes #349. Reset IndexedDB on version downgrade (e.g. 0.16.0-rc.x to 0.15.9). Patch upgrades within same major.minor keep the store. README documents pinning @0.15.9 while latest is RC. 317 idxdb-store tests pass.

Made with Cursor

Co-authored-by: Cursor <cursoragent@cursor.com>
@Mustdzyl

Mustdzyl commented Sep 1, 2026

Copy link
Copy Markdown

Thanks for picking this up. One thing on the condition.

sameMajorMinor && semver.gt(...) also resets on a patch downgrade inside the same major.minor line, which was not part of what I reported:

incoming → stored before after
0.15.8 → 0.15.9 (patch downgrade) keep reset
0.15.9 → 0.15.8 (patch upgrade) keep keep
0.16.0 → 0.15.9 (minor upgrade) reset reset
0.15.9 → 0.16.0-rc.3 (the issue) keep reset

A patch release should not change the store schema, so wiping there costs the user their accounts and notes for no reason. Someone pinning back one patch would hit it.

sameMajorMinor on its own gives the right answer in all four rows, so the && semver.gt(...) half only adds that case:

if (sameMajorMinor) {
    await this.persistClientVersion(clientVersion);
    return;
}

Same fix for the downgrade I hit, one condition less, and the patch line keeps its data. Worth a test for 0.15.9 → 0.15.8 either way, since neither version of the condition has one.

sameMajorMinor alone preserves data for patch upgrades and pin-backs; major/minor crossings still reset. Adds 0.15.9 -> 0.15.8 regression coverage per review.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kutluhaneth46

Copy link
Copy Markdown
Author

Thanks @Mustdzyl — good catch. Dropped the semver.gt half so any same major.minor patch move (including 0.15.9 → 0.15.8) keeps the store; major/minor crossings and the 0.16.0-rc.3 → 0.15.9 case still reset. Added a regression test for the patch pin-back; schema.test.ts is 12/12 green locally.

@Mustdzyl

Mustdzyl commented Sep 3, 2026

Copy link
Copy Markdown

I pulled the actual packages to check the downgrade path, and I don't think this fires for the case in the test name.

0.15.9 declares Dexie versions up to 2. 0.16.0-rc.7 declares up to 5. So a user who ran the RC has the store at IndexedDB version 5, and coming back to 0.15.9 dexie.open() throws VersionError before ensureClientVersion ever runs:

await this.dexie.open();                        // throws here
await this.ensureClientVersion(clientVersion);  // never reached

There is no VersionError branch, and the only this.dexie.delete() sits inside the reset that the throw skips, so the store never gets wiped.

The test passes because both opens go through the schema on this branch, which declares 1..2 either way. What differs is the stored version string, not the IndexedDB version, so the version-5 store that the RC creates is never reproduced.

Would catching it around the open cover the real case?

try {
  await this.dexie.open();
} catch (err) {
  if (err?.name === "VersionError") { await this.dexie.delete(); await this.dexie.open(); }
  else throw err;
}

The README line probably wants softening until that holds, since it promises the reset is automatic.

…ersion

RC clients can advance Dexie past what 0.15.x declares, so open() throws VersionError and never reaches the client-version reset. Catch that, delete, reopen; soften the README claim accordingly.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kutluhaneth46

Copy link
Copy Markdown
Author

Thanks @Mustdzyl — you're right. With a real RC store at Dexie v5, dexie.open() throws VersionError before ensureClientVersion runs, so the wipe never fired.

Pushed a follow-up that:

  • catches VersionError around open(), deletes, and reopens (your suggested shape)
  • adds a unit test that drives that path explicitly (fake-indexeddb softens schema downgrades, so the test spies the first open to throw VersionError)
  • softens the README line so it no longer promises a blanket automatic reset without that path

schema.test.ts is 13/13 green locally.

@Mustdzyl

Mustdzyl commented Sep 4, 2026

Copy link
Copy Markdown

One more direction, in case it belongs in the same PR.

The VersionError catch covers the downgrade. The upgrade direction fails earlier and differently. On a clean origin I created a store with 0.15.9, then loaded 0.16.0-rc.7 on the same origin:

Failed to create client: storage error: failed to deserialize data from the store: invalid value: Invalid public key

That happens before any network call, so the client never starts. Dexie opens fine here because rc.7 declares a higher schema version and runs its own upgrade, so nothing throws VersionError and the new catch does not fire. What breaks is reading 0.15.9 era rows with the newer deserializer.

With the same origin cleared first, rc.7 gets past that and fails at the RPC instead, which is the version mismatch you would expect against the current testnet:

accept header validation failed: server rejected request (client version: 0.16.0-rc.4, genesis commitment: none)

So someone upgrading when testnet moves to 0.16 lands on the storage error rather than a recoverable reset. Should the catch widen to any store deserialization failure, or is that a separate migration concern?

Dexie upgrades can leave headers while settings (and the version key) are
gone. Persisting the new version alone lets newer deserializers read
incompatible rows; wipe first in that case.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kutluhaneth46

Copy link
Copy Markdown
Author

Thanks @Mustdzyl — good catch on the upgrade direction.

VersionError only covers schema downgrade (Dexie refuses to open). On upgrade Dexie opens fine, migrates, and the failure you hit is later WASM deserialize of legacy rows.

ensureClientVersion already wipes when the stored version crosses major/minor. The gap is when settings (and clientVersion) are missing after a Dexie upgrade while account headers remain: we used to just persist the new version and leave incompatible rows for the deserializer.

Pushed a follow-up that resets when latestAccountHeaders.count() > 0 and no stored client version is found, plus a regression test. schema.test.ts is 14/14 green locally.

Widening the open() catch to WASM deserialize failures still would not help there — that error surfaces after IndexedDB is already open, inside the Rust store read path. A client-level retry-on-deserialize would be a separate WebClient concern; happy to open a follow-up if you want that too.

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.

latest on npm is a release candidate the public testnet rejects

2 participants