Skip to content

rainxh11/simurgh

Repository files navigation

NPM MIT License

qwik-simurgh for Qwik

An asynchronous state manager for Qwik - similar to the excellent libraries of Tanstack Query.

Features

Installation

pnpm add qwik-simurgh
yarn add qwik-simurgh
npm install qwik-simurgh

Integration:

  • To use qwik-simurgh you need to use the SimurghProvider context provider + the store that will hold the cached queries:
// src/routes/layout.tsx
import {component$, Slot} from "@builder.io/qwik";
import type {RequestHandler} from "@builder.io/qwik-city";
import {InMemoryCacheStore, SimurghProvider} from "qwik-simurgh";

export const onGet: RequestHandler = async ({cacheControl}) => {
    /* ... */
};

export default component$(() => {
    return (
        <SimurghProvider store$={() => new InMemoryCacheStore()}>
            <Slot/>
        </SimurghProvider>
    );
});

Example Usage:

Simple query with 30 seconds cache:

import {$, component$, useSignal} from "@builder.io/qwik";
import {useQuery} from "qwik-simurgh";

export default component$(() => {
    const search = useSignal<string | undefined>("");
    const {data, isLoading, isSuccess, isError, errors} = useQuery<string, string, any>({
        queryKey: [search],
        queryFn$: $(() =>
            fetch("https://fakestoreapi.com/products/" + search.value)
                .then((res) => res.text())),
        select$: $((res: any) => res),
        refetchOnWindowFocus: false,
        staleTime: 30 * 1000
    });
    return <div class="m-12 flex flex-col gap-1">
        <h1 class="text-3xl">🐦 Simurgh </h1>
        <input class="rounded-md border-2 border-blue-400 px-2 py-1 hover:border-blue-700"
               value={search.value}
               onInput$={(e) => (search.value = e.target?.value)}
        />
        {isLoading.value && <div>Loading data...</div>}
        {isSuccess.value && <div> Data :{data.value}</div>}
    </div>
});

Example

Automatic refetching when search value changes; fetches from cache after the initial request until for the duration of the cache window

Other cool features made possible due to Qwik:

  • 🚥 Execute queries, mutations on a separate worker thread freeing up the main thread using worker$()
    • Example: calculate a SHA512 hash of some value on a separate worker thread
import {component$, useSignal} from "@builder.io/qwik";
import {useQuery} from "qwik-simurgh";
import {worker$} from "@builder.io/qwik-worker";
import {computeHash} from "@/hasher"

export default component$(() => {
    const text = useSignal<string>(/* ... */)
    const {data} = useQuery<string, string, any>({
        queryKey: ["hash", text],
        queryFn$: worker$(async () => await computeHash(text.value, "SHA512")),
    })
    return <>{/* ... */}</>
})
  • 🚥 Or in server using server$()
    • Example: call a database in the server directly
import {component$} from "@builder.io/qwik";
import {server$} from "@builder.io/qwik-city";
import {useQuery} from "qwik-simurgh";
import {db} from "@/db"

export default component$(() => {
    const {data} = useQuery<string, string, any>({
        queryKey: ["products"],
        queryFn$: server$(async () => await db.findAll()),
    })
    return <>{/* ... */}</>
})
  • 🚥 Use routeLoader$() with useQuery
    • Example: use route preloaded data based on URL query parameters and passing it to initialData
import {$, component$, useSignal} from "@builder.io/qwik";
import {routeLoader$, useLocation} from "@builder.io/qwik-city";
import {useQuery} from "qwik-simurgh";

const useProduct = routeLoader$<Product>(async (req) => {
    const product = await fetch("https://fakestoreapid.com/products/" + req.query.get("productId"))
    return await product.json() as Product
});

export default component$(() => {
    const preloadedProduct = useProduct()
    const location = useLocation()
    const selectedProductId = useSignal<string>(location.url.searchParams.get("productId"))

    const {data} = useQuery<string, string, any>({
        queryKey: ["product", selectedProductId],
        initialQueryKey: ["product", location.url.searchParams.get("productId")],
        initialData: preloadedProduct.value,
        queryFn$: $(() => fetch("https://fakestoreapi.com/products/" + location.url.searchParams.get("productId"))
            .then(res => res.json())),
    })
    return <>{/* ... */}</>
})

Make sure to set initialQueryKey to a value that matches queryKey initial state, useQuery will skip calling the query function altogether & uses the preloaded route data directly.

more documentations coming soon...

This project is still under development with little to none proper testing, so expect many bugs🐞 while using it.