Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there a plan to make liveQuery more compatible with Svelte5 runes #2075

Open
newholder opened this issue Sep 23, 2024 · 7 comments
Open

Comments

@newholder
Copy link

newholder commented Sep 23, 2024

With the introduction of runes, store is kinda obsolete. Is there any plan to support runes once svelte5 released?

I use it like this in svelte5

let _friends = liveQuery(async () => {
      return await db.friends
        .where("age")
        .between(18, 65)
        .toArray();
    });

let friends = $derived($_friends)
@dfahlander
Copy link
Collaborator

Thanks for bringing it up. Also asked on stackoverflow: https://stackoverflow.com/questions/78089371/dexie-livequery-with-svelte-5-runes

Need more knowledge about Svelte 5 and runes before I could do anything about it but a PR would be much welcome. Could we introduce some kind of svelte helper library that delivers runes for live queries? A bit like we have for react?

@molarmanful
Copy link

The recently-added fromStore alongside the wrapper from #1907 worked for me:

import { liveQuery } from 'dexie'
import { fromStore } from 'svelte/store'

export const liveQ = q => fromStore({
  subscribe: (run, invalidate) =>
    liveQuery(q).subscribe(run, invalidate).unsubscribe,
})

// usage

const friends = liveQ(async () => await db.friends.where('age').between(18, 65).toArray())

$inspect(friends.current)

@ericdudley
Copy link

Hello, I am also trying to use Dexie.js in a Svelte5 project with runes. I started by following the existing tutorial Get started with Dexie in Svelte tutorial. However, I got stuck when trying to make a liveQuery reactive to props/state.

I was able to get it working by combining $derived.by with a noop reference to the state I want to react to (in this case namePrefix). Is this the correct way of parameterizing a live query? Re-creating the liveQuery whenever the parameter changes?

<script lang="ts">
	import { db } from '$lib/store';
	import { liveQuery } from 'dexie';

	let { namePrefix }: { namePrefix: string } = $props();

	let friends = $derived.by(() => {
		// noop just to make it reactive
		namePrefix;

		return liveQuery(() => db.friends
				.where('name')
				.startsWithIgnoreCase(namePrefix ?? '')
				.toArray()
		);
	});
</script>

@dfahlander
Copy link
Collaborator

Hello, I am also trying to use Dexie.js in a Svelte5 project with runes. I started by following the existing tutorial Get started with Dexie in Svelte tutorial. However, I got stuck when trying to make a liveQuery reactive to props/state.

I was able to get it working by combining $derived.by with a noop reference to the state I want to react to (in this case namePrefix). Is this the correct way of parameterizing a live query? Re-creating the liveQuery whenever the parameter changes?

<script lang="ts">
	import { db } from '$lib/store';
	import { liveQuery } from 'dexie';

	let { namePrefix }: { namePrefix: string } = $props();

	let friends = $derived.by(() => {
		// noop just to make it reactive
		namePrefix;

		return liveQuery(() => db.friends
				.where('name')
				.startsWithIgnoreCase(namePrefix ?? '')
				.toArray()
		);
	});
</script>

It's correct to recreate the query whenever a closure it depends on changes. In the background the old query will be unsubscribed and a new query subscribed and the result will stay until the new query emits it's initial value.

@dfahlander
Copy link
Collaborator

dfahlander commented Oct 31, 2024

We'd need some svelte-specific library to consume live queries more slick with runes. See also my suggested helper in #2089 - it emits errors and loading status but it still need the noops for some reason (or does it). Wouldn't the svelte compiler see the closures passed to the callback? If that's the case, a plain usage of liveQuery with svelte 5 would be:

<script lang="ts">
  import { db } from '$lib/store';
  import { liveQuery } from 'dexie';

  let { namePrefix }: { namePrefix: string } = $props();

  const friends = $derived(
    liveQuery(
      () => db.friends
        .where('name')
        .startsWith(namePrefix)
        .toArray()
    )
  );
</script>

{#if $friends}
  <ul>
    {#each $friends as friend (friend.id)}
      <li>{friend.name}, {friend.age}</li>
    {/each}
  </ul>
{/if}

Could anyone confirm whether the above code would work? It would be more direct than what's suggested so far.

If errors and loading status is requested, then look at how it could be done in #2089. An updated sample using that helper would then be:

<script lang="ts">
  import { db } from '$lib/store';
  import { svelteLiveQuery } from '$lib/svelteLiveQuery'; // or where to put it...
  import { liveQuery } from 'dexie';

  let { namePrefix }: { namePrefix: string } = $props();

  const friendResult = $derived(
    svelteLiveQuery(
      () => db.friends
        .where('name')
        .startsWith(namePrefix)
        .toArray()
    )
  );
</script>

<!-- isLoading -->
{#if $friendResult.isLoading}
   <p>Loading...</p>
{/if}

<!-- error -->
{#if $friendResult.error}
   <p>Error: $friendResult.error</p>
{/if}

<!-- result -->
{#if $friendResult.value}
<ul>
  {#each $friendResult.value as friend (friend.id)}
    <li>{friend.name}, {friend.age}</li>
  {/each}
</ul>
{/if}

@dusty-phillips
Copy link
Contributor

dusty-phillips commented Nov 6, 2024

I've also been struggling with various incantations of this. It works acceptably in certain toy app scenarios, but it's a frustrating nightmare in a production app.

I think I've got a working solution now, thanks in large part to standing on the shoulders of @braebo, who posted a solution here (see the "fixed" route). The QueryRune works correctly for simple cases.

One gotcha is that it doesn't behave as expected with queries that have derived parameters.So for example, this doesn't work:

  const draftQuery = $derived(new QueryRune(liveQuery(() => getSelectedDraft(book?.id))))

(getSelectedDraft is just a promise that calls into Dexie)

This is not reactive because according to the Svelte docs:

Anything read synchronously inside the $derived expression (or $derived.by function body) is considered a dependency of the derived state.

(emphasis mine)

Because liveQuery is executing asynchronously, anything passed into it is not considered reactive. This is a slightly tidier version of the $derived.by with a no-op David suggested.

The least untidy solution I've found is to bring back the concept of ye olde deps array. 😭 I've created a function like this:

export function liveRune<T>(
  querier: () => T | Promise<T>,
  ..._dependencies: any[]
): QueryRune<T> | { current: undefined } {
  return new QueryRune(liveQuery(querier))
}

I can call it like this:

  const draftQuery = $derived(liveRune(() => getSelectedDraft(book?.id), book?.id))

The function doesn't do anything with the second book?.id but because $derived sees it as a synchronous parameter, it treats it reactively things update properly.

I've actually extended this function to short circuit if any of the deps are undefined, so cascading loads can behave sanely when waiting for data from a parent query (book, in this example):

export function liveRune<T>(
  querier: () => T | Promise<T>,
  ...dependencies: any[]
): QueryRune<T> | { current: undefined } {
  if (!dependencies.every((x) => x)) {
    return { current: undefined }
  }

  return new QueryRune(liveQuery(querier))
}

Svelte's generally done an excellent job of inferring dependencies, so I'm pretty bummed out that it isn't able to do the right thing without an explicit dependency. But it's the best I've found so far.

@braebo
Copy link

braebo commented Nov 6, 2024

@dusty-phillips thanks for sharing your findings! I'm not sure if this helps, but I've got derived working reliably here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants