npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@surrealguard/svelte

v0.5.3

Published

Svelte 5 / SvelteKit bindings for SurrealGuard: typed live queries as runes, SSR hydration.

Downloads

1,114

Readme

@surrealguard/svelte

Svelte 5 / SvelteKit bindings for SurrealGuard. Typed queries as reactive primitives, with the query text written exactly once.

<script lang="ts">
  import { createLive } from "@surrealguard/svelte";
  import { livePeople } from "$lib/queries";

  const people = createLive(livePeople);
</script>

{#each people.data as person (person.id)}
  <li>{person.name} — {person.age}</li>
{/each}

people.data is Array<{ id: `person:${string}`; name: string; age: number }>, inferred from your schema. No $ prefix — reading a getter tracks.

Install

npm install @surrealguard/svelte @surrealguard/client surrealdb

Generate

SurrealGuard reads your schema and writes one module holding the typed client and the query registry. Point it at src/lib so $lib/… resolves:

npx surrealguard generate --out src/lib/surrealguard.generated.ts

Re-run it whenever the schema or a query changes — or leave --watch running, which regenerates on every change to a .surql file, a host file, or surrealguard.toml. Commit the generated module: it is what makes a fresh checkout type-check without a build step.

Setup

// src/lib/db.ts
import { createClient } from "$lib/surrealguard.generated";

export const db = createClient({ url: "ws://localhost:8000/rpc" });
// src/lib/queries.ts — the one place query text lives
import { defineQuery, defineLive } from "$lib/surrealguard.generated";

export const allPeople  = defineQuery("SELECT id, name, age FROM person");
export const addPerson  = defineQuery("CREATE person SET name = $name, joined = $joined");
export const livePeople = defineLive("SELECT id, name, age FROM person");
export const liveTeam   = defineLive("SELECT id, name FROM person WHERE team = $team");
<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { setClient } from "@surrealguard/svelte";
  import { db } from "$lib/db";

  setClient(db);
  let { children } = $props();
</script>

{@render children()}

createLive — a live query

<script lang="ts">
  import { createLive } from "@surrealguard/svelte";
  import { livePeople } from "$lib/queries";

  const people = createLive(livePeople);
</script>

{#if people.error}
  <p class="error">{people.error.message}</p>
{:else}
  <ul>{#each people.data as p (p.id)}<li>{p.name}</li>{/each}</ul>
{/if}

data is always an array and starts [], so markup never needs ?? []. N components sharing a query share one LIVE SELECT, and the last one to unmount issues the KILL.

Reactive parameters

Wrap the query in a function. A thunk re-runs when its dependencies change, so the query re-subscribes:

<script lang="ts">
  import { page } from "$app/state";
  import { createLive } from "@surrealguard/svelte";
  import { liveTeam } from "$lib/queries";

  // navigating to another team KILLs the old subscription and opens a new one
  const team = createLive(() => liveTeam.with({ team: page.params.team }));
</script>

This is the same rule @tanstack/svelte-query v6 and convex-svelte both arrived at: the argument must be wrapped in a function to preserve reactivity.

Conditional queries

"skip" says "not yet", and keeps the row type:

const team = createLive(() => (session ? liveTeam.with({ team }) : "skip"));

createQuery — a one-shot query

<script lang="ts">
  import { createQuery } from "@surrealguard/svelte";
  import { allPeople } from "$lib/queries";

  const roster = createQuery(allPeople);
</script>

{#if roster.loading}
  <Spinner />
{:else if roster.error}
  <p>{roster.error.message}</p>
{:else}
  <ul>{#each roster.data ?? [] as p (p.id)}<li>{p.name}</li>{/each}</ul>
{/if}

data is T | undefined here, because a one-shot query's result may be a scalar (RETURN count(…)) and there is nothing honest to default it to.

createMutation — a write, and what it invalidates

<script lang="ts">
  import { createMutation } from "@surrealguard/svelte";
  import { addPerson, allPeople, livePeople } from "$lib/queries";

  const add = createMutation(addPerson, { invalidates: [allPeople, livePeople] });
</script>

<button onclick={() => add.mutate({ name: "ada", joined: new Date() })}
        disabled={add.pending}>Add</button>
{#if add.error}<p class="error">{add.error.message}</p>{/if}

mutate is fire-and-forget (errors land on .error); mutateAsync returns the result and throws.

SSR — preload

// src/routes/+page.ts
import { preload } from "@surrealguard/svelte";
import { db } from "$lib/db";
import { livePeople } from "$lib/queries";

export async function load() {
  return { people: await preload(db, livePeople) };
}
<!-- src/routes/+page.svelte : the query text appears nowhere -->
<script lang="ts">
  import { createLive } from "@surrealguard/svelte";
  let { data } = $props();

  const people = createLive(() => data.people);
</script>

<ul>{#each people.data as p (p.id)}<li>{p.name}</li>{/each}</ul>

The payload carries its own key, text and params, so the component subscribes to exactly the query the server ran. It renders from the seed on the first paint and upgrades to live in place.

This is the flaw the package was rebuilt around. Before, +page.ts and +page.svelte each spelled the query out; change one and the key stopped matching, so the seed was silently discarded and the page refetched — no error, no warning, no type failure.

Wrap it in a thunk (() => data.people) if the route's data can change under you on a client-side navigation.

Values are JSON here

The reactive layer is Json<T>-shaped: a RecordId arrives as `person:${string}`, a datetime as an ISO string. That is what survives devalue and what JSON.stringify produces, so an SSR payload needs no special handling.

db.run(allPeople) outside the reactive layer gives the SDK's real values (RecordId, Date) instead. The two differ, deliberately: a React Server Component boundary rejects class instances and has no transport hook, so uniformity in the other direction is not available.

If you want SDK classes through load

devalue rejects class instances, so load cannot return a RecordId on its own. Register the transport hook:

// src/hooks.ts
export { transport } from "@surrealguard/svelte/transport";

It covers RecordId, DateTime, Duration, Uuid and Decimal. Spread it to add your own:

import { transport as surrealguard } from "@surrealguard/svelte/transport";
export const transport = { ...surrealguard, MyType: { encode, decode } };

Sharing a query from a .svelte.ts module

The primitives use createSubscriber, not $effect, so they work outside a component and tear down automatically:

// src/lib/people.svelte.ts
import { createLive } from "@surrealguard/svelte";
import { livePeople } from "$lib/queries";
import { db } from "$lib/db";

export const people = createLive(livePeople, { client: db });

(Pass { client } explicitly there — setContext is only readable during component initialisation.)

API

| Export | | | --- | --- | | setClient(db) / useClient(override?) | context | | createLive(source, options?) | live query; data is always an array | | createQuery(source, options?) | one-shot; data is T \| undefined | | createMutation(query, options?) | write + invalidation | | preload(db, query) | SSR payload that remembers its query | | dehydrate(db) / hydrate(db, state) | whole-cache transport | | transport (/transport) | SvelteKit hook for SDK value classes | | Source<Q> | Q \| (() => Q \| "skip") \| "skip" |

create* for reactive primitives, use* for context — TanStack Svelte v6's split.

Licence

MIT OR Apache-2.0