Repository navigation
Conversation
…lback - Add `onPersistenceError(error)`, wired to Loki's `autosaveCallback`: called once per save failure (not on every autosave retry), and again after a successful save. Raw IDB events are unwrapped, quota errors also go to `onQuotaExceededError`, and save failures are always logged. - Add `allowInMemoryFallback` (default `true`): with `false`, unusable IndexedDB (missing, failing to open, or throwing on access/open) fails setup, so `onSetUpError` is called, with the original error as `cause`. - Add `onInMemoryFallback(reason)`, called once per adapter. - On `versionchange` (database deleted or upgraded in another tab), stop saving instead of re-creating the database from memory, break the driver so changes (`batch`, local storage, `unsafeExecute`, `unsafeResetDatabase`) are rejected, and report it via `onPersistenceError` before calling the app's `onversionchange`. - Errors thrown by app callbacks on these paths are logged, so one failing callback can't skip the others or change the fallback policy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
unsafeResetDatabase() closes Loki and then deletes the database. close() only waits for a save when Loki is dirty, but an autosave clears the dirty flags as soon as it starts. If one was still running, close() emitted 'close' synchronously and IncrementalIndexedDBAdapter.deleteDatabase threw "another operation is already in progress", rejecting the reset. Drain Loki's throttled save queue after close() and before deleting. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* [LokiJS] Opt-in exclusiveLock: one tab at a time holds the database With exclusiveLock: true, the adapter takes an exclusive Web Lock per database before opening it. Other tabs wait until the holder is closed and get onDatabaseLockedElsewhere. If the lock is lost, the driver breaks (stops saving, rejects changes). Without navigator.locks, the database opens without a lock and a warning is logged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Stop a database that finishes loading after the driver broke If the exclusive lock is lost while Loki is loading (during setup or reset), the new instance is now stopped from saving and setup or reset fails before the schema is set up, so this tab can't overwrite the new lock holder's data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Log errors thrown by callbacks when the database lock is lost Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Don't delete the database after losing the lock, and match Loki's default name - unsafeResetDatabase() checks that the driver isn't broken right before deleting the database, after waiting for in-flight saves, so a tab that lost the lock meanwhile can't delete the new holder's database. - After a fatal error, callbacks waiting for a save that was in progress (close(), throttledSaveDrain()) are called with an error instead of never being called, so a reset can't hang. - The lock name falls back to Loki's default database name, loki.db. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test lock handoff and lock loss against IndexedDB Uses fake-indexeddb with the real IncrementalIndexedDBAdapter: a waiting tab gets all saved changes, a tab that lost the lock doesn't overwrite the new holder's changes, and a reset that lost the lock doesn't delete the new holder's database. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Call every waiting save callback after a fatal error, even if one throws Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
Hi @joker-777, thank you, the lock-stealing tests are really thorough! I maintain NitromelonDB, a maintained fork of WatermelonDB (New Architecture, Nitro Modules). Something that might interest you: on the web, our The fork was also rewritten in TypeScript (strict mode) to reduce the chance of bugs slipping through, so if you'd like to stay on LokiJS for now, a PR against our repo would be very welcome too. If you don't have the time or would rather not, no worries at all, I'm happy to port it myself. Just let me know. Thanks a lot! |
Yeah, watermelon is not being maintained. Nitromelon is the correct replacement, i have migrated two apps, no problems with nitromelon. |
Adds an opt-in
exclusiveLockoption toLokiJSAdapter, so only one browser tab at a time has the database open.Without it, every tab keeps its own in-memory copy of the database, and the last tab to save overwrites what the others saved. That includes undoing a reset done in another tab, for example on logout.
Builds on #1979 and #1980. It uses the driver-breaking and persistence error handling from #1979 and
drainSavesfrom #1980, so this branch contains both of those commits. Only the last commit, "[LokiJS] Opt-in exclusiveLock: one tab at a time holds the database", belongs to this PR. Once those two merge, the diff shrinks to it.Changes
exclusiveLockoption. Before opening IndexedDB, the adapter takes an exclusive Web Lock (navigator.locks, namedWatermelonDB:<dbName>), and holds it until the tab is closedonDatabaseLockedElsewherecallback is called once while they wait, so the app can tell the user to close the other tab. Setup doesn't fail here on purpose, because many apps reset the database inonSetUpErrorsteal: true, or from DevTools), saving stops and further changes are rejected, through the same path as a database deleted in another tab ([LokiJS] Report persistence failures and allow refusing in-memory fallback #1979). Setup or a reset that is in progress when the lock is lost doesn't continueclose()orunsafeResetDatabase()) are now called with an error instead of never being calledSetup.md) and TypeScript types updatedTests
src/adapters/lokijs/databaseLock.test.js, with a stubbednavigator.locks. It covers one tab at a time with the callback, waiting without it, one lock perdbName, a stolen lock breaking the driver, the missing-API fallback, and no lock requests unless the option is enabledfake-indexeddbwith the realIncrementalIndexedDBAdapter: handoff to a waiting tab, a tab that loses the lock with unsaved changes, and a reset that loses the lock while waiting for a saveVerification
sqlite-nodesuite was left out because better-sqlite3 doesn't build on the host's Node, and no SQLite code changed).flow checkis clean, and ESLint is clean on the changed filesnavigator.locksand IndexedDB:onDatabaseLockedElsewherefired once. After A was closed, B opened with all of A's saved recordsunsafeResetDatabase()while saving 40,000 records, and the lock was stolen right after. The reset rejected, the database was not deleted, and a new tab opened with all 40,000 recordslokiExtensions.js, where both add a function at the same place🤖 Generated with Claude Code