Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG-Unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@

### New features

- [LokiJS] New `onPersistenceError` option: called when saving the database to IndexedDB fails (e.g. quota exceeded, aborted transaction), once per failure. Quota exceeded save errors are now also passed to `onQuotaExceededError`
- [LokiJS] New `allowInMemoryFallback` option (default: `true`). Pass `false` to make setup fail (and `onSetUpError` be called) instead of silently falling back to an in-memory database when IndexedDB is unavailable
- [LokiJS] New `onInMemoryFallback` option: called when the database falls back to being in-memory only
- New `Collection.observeRecord(id)`: a failsafe version of `findAndObserve(id)` that emits `null` while the record does not exist (instead of erroring), emits the record once it is created, and emits `null` (instead of completing) when it is deleted. `@experimentalFailsafe` relations now use it, so their `observe()` recovers when a missing related record is created later

### Fixes

- [LokiJS] Multitab sync issue fix
- [LokiJS] When IndexedDB database is deleted in another tab (`onversionchange`), the database is no longer re-created from this tab's memory on next autosave. Instead, the adapter stops saving, rejects further changes, and calls `onPersistenceError`
- [LokiJS] Failures to save the database to IndexedDB are now always logged
- [LokiJS] When accessing or opening IndexedDB throws synchronously (e.g. `SecurityError` when storage access is denied), the adapter now falls back to an in-memory database like for other IndexedDB open failures (or fails setup with `allowInMemoryFallback: false`), instead of always failing setup
- [Android] Added linker flag for building with 16kB page alignment
- [TS] make catchError visible to typescript
- [LokiJS] Fixed `unsafeResetDatabase()` failing with "another operation is already in progress" when it's called while an autosave is in progress
Expand Down
22 changes: 22 additions & 0 deletions docs-website/docs/docs/Setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ const adapter = new LokiJSAdapter({
onSetUpError: (error) => {
// Database failed to load -- offer the user to reload the app or log out
},
onPersistenceError: (error) => {
// Saving to IndexedDB failed (e.g. disk full, database deleted in another tab). Changes made
// since the last successful save only exist in memory and will be lost if the page is closed.
// Called once per failure, not on every autosave retry
},
// (true by default) Pass `false` to make setup fail (and call `onSetUpError`) instead of silently
// falling back to an in-memory database when IndexedDB is unavailable (e.g. some private modes)
allowInMemoryFallback: true,
onInMemoryFallback: (reason) => {
// IndexedDB is unavailable, so data will be lost when the page is closed -- warn the user
},
extraIncrementalIDBOptions: {
onDidOverwrite: () => {
// Called when this adapter is forced to overwrite contents of IndexedDB.
Expand All @@ -99,6 +110,7 @@ const adapter = new LokiJSAdapter({
onversionchange: () => {
// database was deleted in another browser tab (user logged out), so we must make sure we delete
// it in this tab as well - usually best to just refresh the page
// (from this point, this tab no longer saves to IndexedDB and rejects further changes)
if (checkIfUserIsLoggedIn()) {
window.location.reload()
}
Expand All @@ -109,6 +121,16 @@ const adapter = new LokiJSAdapter({
// The rest is the same!
```

:::note
On the web, `database.write()` resolves once changes are applied in memory. LokiJS saves them to IndexedDB shortly after (autosave), so a failure to save is only reported via `onPersistenceError`.

Multiple tabs of the same app share one IndexedDB database, but each tab keeps its own in-memory copy:

- When another tab deletes the database (e.g. on logout), this tab stops saving to IndexedDB, rejects further changes (including `unsafeExecute`), and calls `onPersistenceError` and `onversionchange`. Reload the page to continue.
- When two tabs make changes concurrently, the last tab to save overwrites changes saved by the other one (`onDidOverwrite` is called). WatermelonDB does not coordinate writers across tabs.
- `onPersistenceError`, `onInMemoryFallback`, `onQuotaExceededError` and `extraIncrementalIDBOptions` callbacks only work with `useWebWorker: false`.
:::

* * *

## Next steps
Expand Down
17 changes: 17 additions & 0 deletions src/adapters/lokijs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ export type LokiAdapterOptions = $Exact<{
// This means that app can't save more data or that it will fall back to using in-memory database only
// Note that this only works when `useWebWorker: false`
onQuotaExceededError?: (error: Error) => void
// Called when saving the database to IndexedDB fails (e.g. quota exceeded, transaction aborted,
// database closed). Changes made since the last successful save are only kept in memory until
// a later save succeeds, and will be lost if the page is closed. Note that `database.write()`
// resolving does not mean that changes were saved to disk. Called once per failure, not on every
// retry, and called again if saving fails after it succeeded in the meantime. Quota exceeded
// errors are also passed to `onQuotaExceededError`.
// Note that this only works when `useWebWorker: false`
onPersistenceError?: (error: Error) => void
// (true by default) If IndexedDB is not available or fails to open (e.g. in some browsers' private
// mode), the database falls back to being in-memory only and data is lost when the page is closed.
// Pass `false` to make setup fail instead (`onSetUpError` will be called)
allowInMemoryFallback?: boolean
// Called when the database falls back to being in-memory only (see `allowInMemoryFallback`)
// Note that this only works when `useWebWorker: false`
onInMemoryFallback?: (reason: Error) => void
// extra options passed to Loki constructor
extraLokiOptions?: $Exact<{
autosave?: boolean
Expand All @@ -47,6 +62,8 @@ export type LokiAdapterOptions = $Exact<{
// You might use it as an opportunity to alert user to the potential loss of data
onDidOverwrite?: () => void
// Called when internal IndexedDB version changed (most likely the database was deleted in another browser tab)
// When this happens, the database stops being saved (so that it's not re-created from memory),
// further changes are rejected, and `onPersistenceError` is called.
// Pass a callback to force log out in this copy of the app as well
// (Due to a race condition, it's usually best to just reload the web app)
// Note that this only works when not using web workers
Expand Down
17 changes: 17 additions & 0 deletions src/adapters/lokijs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@ export type LokiAdapterOptions = $Exact<{
// This means that app can't save more data or that it will fall back to using in-memory database only
// Note that this only works when `useWebWorker: false`
onQuotaExceededError?: (error: Error) => void,
// Called when saving the database to IndexedDB fails (e.g. quota exceeded, transaction aborted,
// database closed). Changes made since the last successful save are only kept in memory until
// a later save succeeds, and will be lost if the page is closed. Note that `database.write()`
// resolving does not mean that changes were saved to disk. Called once per failure, not on every
// retry, and called again if saving fails after it succeeded in the meantime. Quota exceeded
// errors are also passed to `onQuotaExceededError`.
// Note that this only works when `useWebWorker: false`
onPersistenceError?: (error: Error) => void,
// (true by default) If IndexedDB is not available or fails to open (e.g. in some browsers' private
// mode), the database falls back to being in-memory only and data is lost when the page is closed.
// Pass `false` to make setup fail instead (`onSetUpError` will be called)
allowInMemoryFallback?: boolean,
// Called when the database falls back to being in-memory only (see `allowInMemoryFallback`)
// Note that this only works when `useWebWorker: false`
onInMemoryFallback?: (reason: Error) => void,
// extra options passed to Loki constructor
extraLokiOptions?: $Exact<{
autosave?: boolean,
Expand All @@ -51,6 +66,8 @@ export type LokiAdapterOptions = $Exact<{
// You might use it as an opportunity to alert user to the potential loss of data
onDidOverwrite?: () => void,
// Called when internal IndexedDB version changed (most likely the database was deleted in another browser tab)
// When this happens, the database stops being saved (so that it's not re-created from memory),
// further changes are rejected, and `onPersistenceError` is called.
// Pass a callback to force log out in this copy of the app as well
// (Due to a race condition, it's usually best to just reload the web app)
// Note that this only works when not using web workers
Expand Down
Loading
Loading