feat(#2): autoAdapter — localStorage primary, IndexedDB fallback on size/quota#9
Merged
Merged
Conversation
…ize/quota Closes #2. Wraps localStorage and IndexedDB so users don't have to pre-pick storage by upfront-unknown size. Small payloads stay in fast synchronous localStorage; once a key's serialized size crosses the threshold (default 1 MB) the adapter routes that key's writes to IndexedDB and cleans up the stale localStorage entry. Reads are primary-first so v0.1 → v0.2 upgrades keep working without explicit migration. useFormDraft({ storage: autoAdapter({ thresholdBytes: 500_000, onMigration: (key, reason) => console.log(`migrated`, key, reason), }), // ... }); Also recovers from QuotaExceededError (other libs filling localStorage) by falling back to IndexedDB and reporting `reason: 'quota'`. 7 rounds of code review + adversarial audit in parallel each round. Bugs caught and fixed before merge: R1: Two concurrent same-key writes interleaved into "both succeeded / read returns null" data loss → per-key serialization queue. R2: clear() raced with writes started DURING its own pending-await → installed a clearing barrier that runSerialized chains on. R3: Two concurrent clear() calls — fast clear2 installs barrier, writes chain on it, slow clear1's still-pending adapter.clear() then wipes them → clear chains on prev clearing too. Also moved pending snapshot to the sync prefix to avoid deadlock (clear awaits write awaits clear). R4: read() interleaved with clear could see a mid-wipe state → read awaits clearing barrier. R5: onMigration fired even when primary.remove failed (telemetry lied) → only fires on full success. R6: write(undefined) threw a cryptic TypeError → guard with a formdraft-tagged TypeError. Plus tightened quota detection to name+code only (no /quota/i regex false positives), guarded primary===fallback at construction, and documented concurrent-read+clear ordering as undefined. 26 autoAdapter unit tests, 131 total in the package. Bundle 4.7 KB brotli (8 KB CI gate).
01a63e5 to
60c3999
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2.
Summary
autoAdapter({ thresholdBytes?, primary?, fallback?, onMigration? })storage adapter exported from main entry.thresholdBytes(default 1 MB / UTF-16 code units) go to localStorage; larger writes route the key to IndexedDB and clean up the stale localStorage entry.QuotaExceededErrorby falling back to IndexedDB transparently and reportingreason: 'quota'viaonMigration.Vetting
7 rounds of code review + adversarial audit dispatched in parallel each round. Real bugs caught before merge:
clear()racing with writes started during its own awaitclear()calls — fast one installs barrier, slow one's lateadapter.clearwipes data that chained on the new barrierread()mid-clear returning inconsistent partial-wipe dataonMigrationfiring even whenprimary.removefailed (telemetry lied)write(undefined)Plus tightened quota detection to name+code only (removed loose
/quota/iregex), guardedprimary === fallbackat construction, documented concurrent-read+clear ordering as undefined.Test plan
npm test— 131 tests pass (26 autoAdapter + 105 existing)npx size-limit— 4.7 KB brotli (8 KB gate)npm run typecheck— cleanonMigrationcallback firing withreason: 'size'