-
Notifications
You must be signed in to change notification settings - Fork 14
Thenable query #32
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
base: main
Are you sure you want to change the base?
Thenable query #32
Changes from 14 commits
8a1f7e8
5431649
9a4f5e7
cf22c15
42f4e04
35ac057
ba219cb
c39f26b
e198b77
5cd205b
e308b8d
b76ea52
7380e7d
ee0abfd
6234286
3650b0a
daf0091
dfc39ae
c000d2e
7cdedbb
7ea226f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| import { useConvexClient } from "$lib/client.svelte.js"; | ||
| import { type DefaultFunctionArgs, type FunctionReference } from "convex/server"; | ||
|
|
||
| export type ConvexQuery<T> = { | ||
| then: ( | ||
| onfulfilled: (value: T) => void, | ||
| onrejected: (reason: any) => void | ||
| ) => void; | ||
| current: T | undefined; | ||
| error: Error | undefined; | ||
| loading: boolean; | ||
| [Symbol.dispose]: () => void; | ||
| } | ||
|
|
||
|
|
||
| export type ConvexQueryOptions<Query extends FunctionReference<'query'>> = { | ||
| // Use this data and assume it is up to date (typically for SSR and hydration) | ||
| initialData?: Query['_returnType']; | ||
| }; | ||
|
|
||
| /** | ||
| * Subscribe to a Convex query and return a reactive query result object that can be awaited. | ||
| * | ||
| * @experimental API is experimental and could change. | ||
| * @param queryFunc - a FunctionRefernece like `api.dir1.dir2.filename.func`. | ||
| * @param args - The arguments to the query function. | ||
| * @param options - ConvexQueryOptions like `initialData`. | ||
| * @returns a thenable object. Also contains `current`, `error`, and `loading` properties. | ||
| */ | ||
| export function convexQuery< | ||
| Query extends FunctionReference<'query', 'public'>, | ||
| Args extends Query['_args'] | ||
| >( | ||
| queryFunc: Query, | ||
| args: Args, | ||
| options: ConvexQueryOptions<Query> = {} | ||
|
||
| ): ConvexQuery<Query['_returnType']> { | ||
| const client = useConvexClient(); | ||
|
|
||
| const state: { | ||
| current: Query['_returnType'] | undefined, | ||
| error: Error | undefined, | ||
| } = $state({ | ||
| current: options.initialData, | ||
| error: undefined, | ||
| }); | ||
|
|
||
| const loading: boolean = $derived( | ||
| state.current === undefined && state.error === undefined | ||
| ); | ||
|
|
||
| /* Get the value from Convex and subscribe to it */ | ||
| const unsubscribe = client.onUpdate( | ||
| queryFunc, | ||
| args, | ||
| (result) => { | ||
| state.current = result; | ||
| state.error = undefined; | ||
| }, | ||
| (err) => { | ||
| state.current = undefined; | ||
| state.error = err; | ||
| } | ||
| ); | ||
|
|
||
| /* Unsubscribe from the query when the parent component is destroyed */ | ||
| $effect(() => unsubscribe); | ||
|
|
||
| return { | ||
| get then() { | ||
| const value = state.current; | ||
| try { | ||
| return ( | ||
| resolve: (value: Query['_returnType']) => void, | ||
| reject: (reason: any) => void, | ||
| ) => { | ||
| /* If there is initial data then resolve immediately */ | ||
| if (value !== undefined) { | ||
| resolve(value); | ||
| return; | ||
| } | ||
| /* If the query is already in the cache, return the cached value */ | ||
| client.query(queryFunc, args).then((result) => { | ||
| resolve(value ?? result); | ||
| }).catch((err) => { | ||
| reject(err); | ||
| throw err; | ||
| }); | ||
| } | ||
| } catch (err) { | ||
| state.error = err as Error; | ||
| return ( | ||
| resolve: (value: Query['_returnType']) => void, | ||
| reject: (reason: any) => void, | ||
| ) => { | ||
| reject(err); | ||
| } | ||
| } | ||
| }, | ||
| get current() { return state.current; }, | ||
| get error() { return state.error; }, | ||
| get loading() { return loading; }, | ||
| [Symbol.dispose]: unsubscribe | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| // Reexport your entry components here | ||
|
|
||
| export { useConvexClient, setupConvex, useQuery, setConvexClientContext } from './client.svelte.js'; | ||
| export * as async from './async.svelte.js'; |
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For dev on this feature. Won't be included in final PR |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| <script lang="ts"> | ||
| import { api } from '$convex/_generated/api.js'; | ||
| import { convexQuery } from '$lib/async.svelte.js'; | ||
|
|
||
| let fail = $state(false); | ||
|
|
||
| const convexQueryResult = $derived(convexQuery(api.messages.firstMessage, { fail })); | ||
| </script> | ||
|
|
||
| <svelte:head> | ||
| <title>Home</title> | ||
| <meta name="description" content="Svelte demo app" /> | ||
| </svelte:head> | ||
|
|
||
| <section> | ||
| <h1>Welcome to SvelteKit with Convex</h1> | ||
| <a href="/tests">Tests</a> | ||
|
|
||
| <button onclick={() => fail = !fail}>{fail ? 'Fail' : 'Success'}</button> | ||
| <svelte:boundary> | ||
| <pre>Result: {JSON.stringify(await convexQueryResult, null, 2)}</pre> | ||
| <pre>Result: {JSON.stringify(await convexQuery(api.messages.firstMessage, { fail }), null, 2)}</pre> | ||
| {#snippet pending()} | ||
| <div>Loading...</div> | ||
| {/snippet} | ||
| {#snippet failed(error, retry)} | ||
| <div>Error: {error}</div> | ||
| <button onclick={retry}>Retry</button> | ||
| {/snippet} | ||
| </svelte:boundary> | ||
|
|
||
| {#if convexQueryResult.loading} | ||
| <div>Loading...</div> | ||
| {:else if convexQueryResult.error} | ||
| <div>Error: {convexQueryResult.error}</div> | ||
| {:else} | ||
| <pre>Result: {JSON.stringify(convexQueryResult.current, null, 2)}</pre> | ||
| {/if} | ||
| </section> | ||
|
|
||
| <style> | ||
| section { | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| flex: 0.6; | ||
| } | ||
|
|
||
| h1 { | ||
| width: 100%; | ||
| text-align: center; | ||
| } | ||
| </style> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <script lang="ts"> | ||
| import type { LayoutProps } from './$types'; | ||
|
|
||
| let { data, children }: LayoutProps = $props(); | ||
| </script> | ||
| <svelte:boundary onerror={(e) => { | ||
| console.error(e) | ||
| }}> | ||
| {@render children()} | ||
|
|
||
| {#snippet pending()} | ||
| <div>Loading...</div> | ||
| {/snippet} | ||
| {#snippet failed(error, reset)} | ||
| <p>Error: {error}</p> | ||
| <button onclick={reset}>oops! try again</button> | ||
| {/snippet} | ||
| </svelte:boundary> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is in for dev purposes for now, and won't be in the final PR