fix(dexie-cloud): route all blob writebacks through BlobSavingQueue to avoid PSD context loss#2302
fix(dexie-cloud): route all blob writebacks through BlobSavingQueue to avoid PSD context loss#2302liz709 wants to merge 9 commits into
Conversation
…o avoid PSD context loss After an async native fetch (blob download), Dexie's PSD zone is no longer active, so PSD.trans is undefined. The previous code called table.mutate() directly for rw transactions, which flowed through HooksMiddleware.mutate() — that reads PSD.trans and crashes: 'Cannot read properties of undefined (reading 'table')' This surfaced as '[dexie-cloud:blobResolve] Failed to resolve BlobRefs on <table>'. Fix: Remove the direct mutate path entirely. Always route blob writebacks through BlobSavingQueue.saveBlobs(), which uses setTimeout(fn, 0) to run in a fresh JS task outside any PSD context, then opens a proper Dexie rw transaction — always safe regardless of calling context. Fixes lazy blob mode crash reported when using Dexie hooks with dexie-cloud.
📝 WalkthroughWalkthroughCentralizes blob download lifecycle: middleware now delegates resolved-blob persistence to BlobDownloadTracker.enqueueSave; BlobDownloadTracker adds concurrency slots, owns BlobSavingQueue persistence, and exposes enqueueSave/drainPendingSaves/releaseRefs; eager downloader uses chunked bulkGet to trigger middleware-driven resolve/save. ChangesBlob download & persistence rework
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…loads of same blob id). No logical code change - but refactor to ease code review
This is not a logical change. Only to make code review less confusing and make sure subscribe/unsubscribe is done using globalEvents.storagemutated instead of the other tedious way for subscribe that was being used before.
Co-authored-by: Copilot <copilot@github.com>
…'t download it again - reuse the promise from the recent download and keep those promises alive until finally persisted. Needed rearchitecture due to new depencendy between BlobSavingQueue and BlobDownloadTracker. Used slightly differently from blobResolveMiddleware and eagerBlobDownloaded. Co-authored-by: Copilot <copilot@github.com>
… resolve ALL blobs in memory. This could starve RAM. Fix: We keep relying on blobResolveMiddleware to resolve the blobs, but we set a limit on each query to actually download the blobs in chunks. Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
addons/dexie-cloud/src/sync/eagerBlobDownloader.ts (1)
58-58: ⚡ Quick winThe infinite loop concern is not applicable with the current
MAX_CONCURRENTvalue.
MAX_CONCURRENTis hardcoded to 10 in BlobDownloadTracker.ts, makingCHUNK_SIZEequal to 9. The loop at line 104 will advance normally with no risk of infinite loop.However, if
MAX_CONCURRENTbecomes configurable in the future (as the comment suggests), consider adding a defensive floor to preventCHUNK_SIZEfrom becoming 0:-const CHUNK_SIZE = MAX_CONCURRENT - 1; +const CHUNK_SIZE = Math.max(1, MAX_CONCURRENT - 1);🤖 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 `@addons/dexie-cloud/src/sync/eagerBlobDownloader.ts` at line 58, CHUNK_SIZE currently sets to MAX_CONCURRENT - 1 which can become zero if MAX_CONCURRENT is ever configured to 1; change the CHUNK_SIZE declaration to enforce a defensive minimum (e.g. at least 1) so the loop in eagerBlobDownloader (the loop around line 104) can always advance; update the CHUNK_SIZE computation to use a floor such as taking the max between 1 and MAX_CONCURRENT - 1, and add a brief comment referencing MAX_CONCURRENT from BlobDownloadTracker to explain the defensive guard.
🤖 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 `@addons/dexie-cloud/src/sync/eagerBlobDownloader.ts`:
- Line 58: CHUNK_SIZE currently sets to MAX_CONCURRENT - 1 which can become zero
if MAX_CONCURRENT is ever configured to 1; change the CHUNK_SIZE declaration to
enforce a defensive minimum (e.g. at least 1) so the loop in eagerBlobDownloader
(the loop around line 104) can always advance; update the CHUNK_SIZE computation
to use a floor such as taking the max between 1 and MAX_CONCURRENT - 1, and add
a brief comment referencing MAX_CONCURRENT from BlobDownloadTracker to explain
the defensive guard.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 29729273-cf01-48d3-a54a-4c8fc4792f85
📒 Files selected for processing (6)
addons/dexie-cloud/src/middlewares/blobResolveMiddleware.tsaddons/dexie-cloud/src/sync/BlobDownloadTracker.tsaddons/dexie-cloud/src/sync/BlobSavingQueue.tsaddons/dexie-cloud/src/sync/eagerBlobDownloader.tsaddons/dexie-cloud/src/types/TXExpandos.tssrc/live-query/live-query.ts
Problem
When using Dexie hooks (e.g.
Table.hook('creating')) together with lazy blob mode, reads would crash with:Caused by:
Root cause: After an async native
fetch(blob download), Dexie's PSD zone is no longer active —PSD.transbecomesundefined. The previous code calledtable.mutate()directly for rw transactions, which flowed throughHooksMiddleware.mutate(). That readsPSD.transto get the current transaction and crashes.Fix
Remove the direct
table.mutate()path entirely. All blob writebacks now go throughBlobSavingQueue.saveBlobs(), which:setTimeout(fn, 0)to run in a fresh JS task, completely outside any PSD contextrwtransaction — always safe regardless of calling contextThe
BlobSavingQueuepath already existed for readonly transactions and was already correctly armored withdisableChangeTracking,disableAccessControl, anddisableBlobResolveflags.Verified
pnpm buildclean)Fixes lazy blob mode crash reported by @sarink in Discord.
Summary by CodeRabbit