dexie / Dexie.js

A Minimalistic Wrapper for IndexedDB
https://dexie.org
Apache License 2.0
11.66k stars 641 forks source link

Dexie Cloud currentUser observable not refreshing with SvelteKit #1864

Open dusty-phillips opened 10 months ago

dusty-phillips commented 10 months ago

In yet another episode of "Dusty's Repro Is Not At all Minimal": https://github.com/dusty-phillips/dexie-cloud-svelte-repro (Sorry about that).

This repo is actually a standard svelte-kit install with loads of base files. I've added Dexie and dexie-cloud-addon (latest betas) as dependencies.

The repro itself actually is pretty minimal in +page.svelte.

The basic problem: If you log into dexie-cloud and refresh the page, the db.cloud.currentUser observable never signals that the user has previously logged in. This appears to be an issue with Dexie cloud's internal state, because if you do click 'Log In' again, it magically updates the state without triggering the OTP flow.

I wondered if this was SSR related, but disabling SSR with export const ssr = false doesn't seem to change the behaviour.

tylermercer commented 6 months ago

I'm also seeing this.

In addition to the magic-update that Dusty mentioned when you log in, for me it magically updates to logged in upon navigation to a page that accesses Dexie (e.g. via a liveQuery on the page). But it does not magically update if I navigate to a page that doesn't access Dexie.

(@dusty-phillips thanks for excellent your write-up on Dexie Cloud, by the way!)

tylermercer commented 6 months ago

Did some poking around in the source this morning. Found these lines that seem to have the specific purpose of preventing this problem. I'm not sure why they aren't accomplishing that though.

When I get a chance (i.e. probably tomorrow), I'll try debugging the package locally and see what currentUser is after those lines.

tylermercer commented 6 months ago

I've found a solution. The commit which added those lines says they are only run when db.open is called. [The docs on db.open](https://dexie.org/docs/Dexie/Dexie.open()) say that it's called either explicitly (which I'm not doing) or implicitly on first query. (That would explain the behavior I was seeing where it suddenly updates to logged in once a Dexie query is made.)

So one solution is to await db.open before using currentUser. I did this in my root layout's loader (mostly since my root layout was where I was seeing the problem):

import { browser } from '$app/environment';
import db from '$lib/db';
import type { LayoutLoad } from './$types';

export const load: LayoutLoad = async () => {
    if (browser) {
        await db.open();
    }
    return {
        db,
    };
};

Then in my root layout Svelte component:

<script lang="ts">
    import type { LayoutData } from './$types';

    export let data: LayoutData;

    const db = data.db;
    let user = db.cloud.currentUser;
</script>

<slot />
<footer>
    {#if $user.isLoggedIn}
        Logged in as {$user.name}.
    {:else}
        <button on:click={() => db.cloud.login()}>Log in</button>
    {/if}
</footer>
tylermercer commented 6 months ago

Actually, since db.currentUser is an observable, it might be sufficient to just call db.open in an onMount hook, without awaiting it. 🤔 Awaiting it feels more "correct", though.

dfahlander commented 6 months ago

Thanks @tylermercer for these findings. In all my samples I have had at least one liveQuery going on somewhere, and they will implicitly open the db, so I have missed this scenario. We should probably let our observables in db.cloud trigger a db.open when subscribed to, the same way as liveQueries do. But doing db.open() somewhere in the application start fixes it for now.

tylermercer commented 6 months ago

Thanks @tylermercer for these findings.

@dfahlander thank you for the clean git history that made it easy to track things down!

We should probably let our observables in db.cloud trigger a db.open when subscribed to, the same way as liveQueries do.

That sounds like a good solution. The behavior would be more intuitive.

But doing db.open() somewhere in the application start fixes it for now.

Can we put a note about the need for this in the docs? I can open a PR for it tomorrow.

dusty-phillips commented 6 months ago

Ah, good research! I've been using Dexie successfully with Svelte for a couple months and I was tempted to close this because it didn't seem to be an issue anymore. My production app opens the DB on mount, so I was getting the correct behaviour for reasons I didn't understand.

phil-w commented 4 months ago

...it might be sufficient to just call db.open in an onMount hook, without awaiting it. 🤔 Awaiting it feels more "correct", though.

I have this problem in Sveltekit and came here because this approach seemed to fix it. However there is some condition which affects my system probably on the first sign in or sign out of a new build. There,

I'm not sure how to step into that, but it seems like I at least running on Chrome based browsers need no await, it's fire and forget...

let count = 1
console.log(count++)
// await db.open()     // Never returns, on 1st hit
db.open()
console.log(count++) // Never gets here if await the open.
let usr = db.cloud.currentUser

Update

I got it going by wrapping the db.open() call in a timeout, so when it hangs I just kill it off anyway. It returns fast (when it's not needed), but I can't find a test which detects that before calling it, and if I await it in one scenario, in the other. The two scenarios being: (1) user not signed in; (2) user signed in.

That seems to work.