Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    wiesson

    svelte-remote-functions

    wiesson/svelte-remote-functions
    Coding
    3
    1 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Guide for SvelteKit Remote Functions...

    SKILL.md

    SvelteKit Remote Functions

    Type-safe client-server communication for SvelteKit applications.

    When to Use This Skill

    Use this skill by default for all SvelteKit projects. It covers:

    • Type-safe data fetching with query
    • Forms with progressive enhancement using form
    • Imperative server actions with command
    • Data invalidation and cache refresh patterns
    • Optimistic updates for better UX

    File Structure

    Remote functions are defined in .remote.js or .remote.ts files.

    Core Concepts

    Four Function Types

    1. query - Read dynamic data (most common for data fetching)
    2. form - Write data via forms with progressive enhancement
    3. command - Write data imperatively from event handlers
    4. prerender - Static data, prerendered at build time (less common)

    Key Features

    • Type-safe - Full TypeScript support across client-server boundary
    • Validated - Use Standard Schema libraries (Zod, Valibot, Arktype) for argument validation
    • Serialization - Automatic handling of Date, Map, Set via devalue
    • Progressive enhancement - Forms work without JavaScript
    • Optimistic updates - Update UI immediately, revert on error
    • Single-flight mutations - Efficient query refresh patterns
    • N+1 prevention - Batch queries with query.batch()
    • Isolated form instances - Multiple forms with form.for(id)

    Quick Start Patterns

    Data Fetching (Query)

    // data.remote.ts
    import { query } from '$app/server';
    import { z } from 'zod';
    
    export const getPosts = query(async () => {
      return await db.getAllPosts();
    });
    
    export const getPost = query(z.string(), async (slug) => {
      return await db.getPost(slug);
    });
    
    // Batch queries to prevent N+1 problems
    export const getPostsBatch = query.batch(
      z.string(),
      async (slugs) => {
        const posts = await db.getPostsBySlug(slugs);
        return slugs.map(slug => posts.find(p => p.slug === slug));
      }
    );
    
    <!-- +page.svelte -->
    <script>
      import { getPosts } from './data.remote';
    </script>
    
    {#each await getPosts() as post}
      <div>{post.title}</div>
    {/each}
    

    Or using properties:

    <script>
      import { getPosts } from './data.remote';
      const posts = getPosts();
    </script>
    
    {#if posts.loading}
      Loading...
    {:else if posts.error}
      Error!
    {:else}
      {#each posts.current as post}
        <div>{post.title}</div>
      {/each}
    {/if}
    

    Form Submission

    // data.remote.ts
    import { form } from '$app/server';
    import { redirect } from '@sveltejs/kit';
    import { z } from 'zod';
    
    export const createPost = form(async (data) => {
      const title = data.get('title');
      // Sensitive fields (prefixed with _) are not sent back to client
      const password = data.get('_password');
    
      await db.insert(title);
    
      // Refresh queries that changed
      getPosts().refresh();
    
      redirect(303, '/posts');
    });
    
    // With validation schema
    export const createUser = form(
      z.object({
        email: z.string().email(),
        username: z.string().min(3),
        _password: z.string().min(8) // Sensitive field
      }),
      async (data) => {
        await db.createUser(data);
        redirect(303, '/login');
      }
    );
    
    <!-- +page.svelte -->
    <form {...createPost}>
      <input name="title" />
      <input name="_password" type="password" />
      <button>Create</button>
    </form>
    
    <!-- Multiple isolated form instances -->
    {#each items as item}
      <form {...deleteItem.for(item.id)}>
        <button>Delete {item.name}</button>
      </form>
    {/each}
    

    Imperative Actions (Command)

    // likes.remote.ts
    import { command, query } from '$app/server';
    import { z } from 'zod';
    
    export const getLikes = query(z.string(), async (id) => {
      return await db.getLikes(id);
    });
    
    export const addLike = command(z.string(), async (id) => {
      await db.incrementLikes(id);
      getLikes(id).refresh();
    });
    
    <!-- +page.svelte -->
    <script>
      import { getLikes, addLike } from './likes.remote';
      let { item } = $props();
      
      const likes = getLikes(item.id);
    </script>
    
    <button onclick={() => addLike(item.id)}>
      Like ({await likes})
    </button>
    

    Invalidation & Refresh Patterns

    Manual Refresh

    <script>
      import { getPosts } from './data.remote';
      const posts = getPosts();
    </script>
    
    <button onclick={() => posts.refresh()}>
      Refresh
    </button>
    

    Automatic Refresh After Mutations

    Default: All queries refresh after form/command (inefficient).

    Better: Specify which queries to refresh (single-flight mutation).

    Server-side Refresh

    export const createPost = form(async (data) => {
      await db.insert(data);
      getPosts().refresh(); // ← Only refresh affected queries
      redirect(303, '/posts');
    });
    

    Client-side Refresh

    <form {...createPost.enhance(async ({ submit }) => {
      await submit().updates(getPosts()); // ← Specify queries
    })}>
    </form>
    
    await addLike(id).updates(getLikes(id));
    

    Optimistic Updates

    <form {...addTodo.enhance(async ({ data, submit }) => {
      await submit().updates(
        getTodos().withOverride((todos) => [
          ...todos,
          { text: data.get('text') }
        ])
      );
    })}>
    </form>
    

    The override applies immediately and reverts on error.

    Detailed Documentation

    For complete implementation details, read the reference files:

    • references/quick-reference.md - Start here for syntax and common patterns
    • references/query.md - Complete query function documentation
    • references/form.md - Form functions with progressive enhancement
    • references/command.md - Imperative command functions
    • references/invalidation.md - Data refresh and optimistic update patterns

    Common Workflows

    Creating a Resource

    1. Define query for fetching list and form for creation
    2. In form handler, call query().refresh() before redirect
    3. User sees updated list after redirect

    Updating with Optimistic UI

    1. Define query for data and command for update
    2. Call command().updates(query().withOverride(...))
    3. UI updates immediately, reverts on error

    Form with Custom Logic

    1. Use form.enhance() to customize submission
    2. Access form data and submit function
    3. Call submit().updates() for targeted refresh
    4. Handle success/error states

    Common Mistakes to Avoid

    ❌ Query Without Validation Schema

    WRONG - Missing Zod schema:

    // ❌ DON'T: Arguments without validation
    export const getPost = query(async (slug) => {
      return await db.getPost(slug);
    });
    

    CORRECT:

    // ✅ DO: Always validate arguments
    import { z } from 'zod';
    
    export const getPost = query(z.string(), async (slug) => {
      return await db.getPost(slug);
    });
    

    ❌ Invalid Empty Object Syntax

    WRONG - Empty object parameter:

    // ❌ DON'T: Use empty object syntax
    export const getPosts = query(async ({}) => {
      return await db.getAllPosts();
    });
    

    CORRECT:

    // ✅ DO: Omit parameters entirely
    export const getPosts = query(async () => {
      return await db.getAllPosts();
    });
    

    ❌ Missing Arguments When Calling

    WRONG - Forgetting required arguments:

    // ❌ DON'T: Call without required arguments
    const post = getPost(); // Missing slug parameter!
    

    CORRECT:

    // ✅ DO: Always pass required arguments
    const post = getPost(params.slug);
    

    ❌ Using Event as Parameter

    WRONG - Event as second parameter:

    // ❌ DON'T: Use event as function parameter
    export const getUser = query(z.string(), async (id, event) => {
      const session = event.cookies.get('session');
      return await db.getUser(id);
    });
    
    // ❌ Also wrong for commands
    export const updateUser = command(z.string(), async (id, event) => {
      const userId = event.locals.user.id;
      await db.update(id, userId);
    });
    

    CORRECT:

    // ✅ DO: Use getRequestEvent()
    import { getRequestEvent } from '$app/server';
    
    export const getUser = query(z.string(), async (id) => {
      const { cookies } = getRequestEvent();
      const session = cookies.get('session');
      return await db.getUser(id);
    });
    
    // ✅ For commands too
    export const updateUser = command(z.string(), async (id) => {
      const { locals } = getRequestEvent();
      const userId = locals.user.id;
      await db.update(id, userId);
    });
    

    ⚠️ Critical Syntax Rules

    Query function signatures:

    • ✅ No arguments: query(async () => { })
    • ✅ With arguments: query(z.schema(), async (arg) => { })
    • ❌ NEVER use: query(async ({}) => { })
    • ❌ NEVER skip validation: query(async (arg) => { }) without schema
    • ❌ NEVER use event parameter: query(z.schema(), async (arg, event) => { })

    When calling remote functions:

    • ✅ No args: getPosts()
    • ✅ With args: getPost('slug-value')
    • ❌ NEVER forget required arguments

    Form and Command:

    • Forms can omit schema if using FormData
    • Commands should always validate arguments with Zod
    • Both follow same validation patterns as queries

    Accessing request context:

    • ✅ DO: import { getRequestEvent } from '$app/server' then use getRequestEvent()
    • ❌ DON'T: Use event as a function parameter

    Validation

    Always validate arguments using Standard Schema (Zod recommended):

    import { z } from 'zod';
    
    // String
    query(z.string(), async (id) => { })
    
    // Number
    query(z.number(), async (count) => { })
    
    // Object
    query(z.object({
      id: z.string(),
      name: z.string()
    }), async (data) => { })
    
    // Optional
    query(z.string().optional(), async (id) => { })
    
    // Sensitive fields (underscore prefix)
    form(z.object({
      username: z.string(),
      _password: z.string().min(8),
      _apiKey: z.string().optional()
    }), async (data) => {
      // _password and _apiKey not sent back to client
    })
    
    // Complex schemas
    query(z.object({
      filters: z.object({
        status: z.enum(['active', 'archived']),
        limit: z.number().min(1).max(100)
      })
    }), async (params) => { })
    

    Custom Validation Error Handling

    Customize how validation errors are returned (e.g., for security):

    // hooks.server.ts
    export function handleValidationError({ event, issues }) {
      // Hide validation details from client
      return {
        message: 'Invalid request',
        code: 'VALIDATION_ERROR'
      };
    
      // Or return detailed errors
      // return { issues };
    }
    

    Best Practices

    1. Prefer query over prerender for dynamic data
    2. Prefer form over command for better progressive enhancement
    3. Use single-flight mutations instead of refreshing all queries
    4. Validate all arguments with Standard Schema
    5. Use optimistic updates for better perceived performance
    6. Server-side refresh when possible for simpler code
    7. Use query.batch() when fetching multiple items to prevent N+1 queries
    8. Use form.for(id) when rendering multiple forms for isolated state
    9. Prefix sensitive fields with underscore (_password, _apiKey) to prevent sending back to client

    Important Notes

    • Commands cannot be called during render
    • Queries are cached: getX() === getX()
    • Data serialized with devalue (supports Date, Map, Set)
    • Inside remote functions, RequestEvent differs (no params/route.id, url.pathname is always /)
    • Sensitive fields (underscore-prefixed) are never sent back to the client after form submission
    • Use handleValidationError hook in hooks.server.ts to customize validation error responses
    Repository
    wiesson/svelte-remote-functions
    Files