docs: Create guidelines for integrating Convex with Svelte/SvelteKit.
This commit is contained in:
275
.agent/rules/convex-svelte-guidelines.md
Normal file
275
.agent/rules/convex-svelte-guidelines.md
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
---
|
||||||
|
trigger: glob
|
||||||
|
globs: **/*.svelte, **/*.ts, **/*.svelte.ts
|
||||||
|
---
|
||||||
|
|
||||||
|
# Convex + Svelte Guidelines
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
These guidelines describe how to write **Convex** backend code **and** consume it from a **Svelte** (SvelteKit) frontend. The syntax for Convex functions stays exactly the same, but the way you import and call them from the client differs from a React/Next.js project. Below you will find the adapted sections from the original Convex style guide with Svelte‑specific notes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Function Syntax (Backend)
|
||||||
|
|
||||||
|
> **No change** – keep the new Convex function syntax.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
query,
|
||||||
|
mutation,
|
||||||
|
action,
|
||||||
|
internalQuery,
|
||||||
|
internalMutation,
|
||||||
|
internalAction
|
||||||
|
} from './_generated/server';
|
||||||
|
import { v } from 'convex/values';
|
||||||
|
|
||||||
|
export const getUser = query({
|
||||||
|
args: { userId: v.id('users') },
|
||||||
|
returns: v.object({ name: v.string(), email: v.string() }),
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
const user = await ctx.db.get(args.userId);
|
||||||
|
if (!user) throw new Error('User not found');
|
||||||
|
return { name: user.name, email: user.email };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. HTTP Endpoints (Backend)
|
||||||
|
|
||||||
|
> **No change** – keep the same `convex/http.ts` file.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { httpRouter } from 'convex/server';
|
||||||
|
import { httpAction } from './_generated/server';
|
||||||
|
|
||||||
|
const http = httpRouter();
|
||||||
|
|
||||||
|
http.route({
|
||||||
|
path: '/api/echo',
|
||||||
|
method: 'POST',
|
||||||
|
handler: httpAction(async (ctx, req) => {
|
||||||
|
const body = await req.bytes();
|
||||||
|
return new Response(body, { status: 200 });
|
||||||
|
})
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Validators (Backend)
|
||||||
|
|
||||||
|
> **No change** – keep the same validators (`v.string()`, `v.id()`, etc.).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Function Registration (Backend)
|
||||||
|
|
||||||
|
> **No change** – use `query`, `mutation`, `action` for public functions and `internal*` for private ones.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Function Calling from **Svelte**
|
||||||
|
|
||||||
|
### 5.1 Install the Convex client
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm i convex @convex-dev/convex-svelte
|
||||||
|
```
|
||||||
|
|
||||||
|
> The `@convex-dev/convex-svelte` package provides a thin wrapper that works with Svelte stores.
|
||||||
|
|
||||||
|
### 5.2 Initialise the client (e.g. in `src/lib/convex.ts`)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createConvexClient } from '@convex-dev/convex-svelte';
|
||||||
|
|
||||||
|
export const convex = createConvexClient({
|
||||||
|
url: import.meta.env.VITE_CONVEX_URL // set in .env
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Using queries in a component
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<script lang="ts">
|
||||||
|
import { convex } from '$lib/convex';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { api } from '../convex/_generated/api';
|
||||||
|
|
||||||
|
let user: { name: string; email: string } | null = null;
|
||||||
|
let loading = true;
|
||||||
|
let error: string | null = null;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
user = await convex.query(api.users.getUser, { userId: 'some-id' });
|
||||||
|
} catch (e) {
|
||||||
|
error = (e as Error).message;
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if loading}
|
||||||
|
<p>Loading…</p>
|
||||||
|
{:else if error}
|
||||||
|
<p class="error">{error}</p>
|
||||||
|
{:else if user}
|
||||||
|
<h2>{user.name}</h2>
|
||||||
|
<p>{user.email}</p>
|
||||||
|
{/if}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Using mutations in a component
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<script lang="ts">
|
||||||
|
import { convex } from '$lib/convex';
|
||||||
|
import { api } from '../convex/_generated/api';
|
||||||
|
let name = '';
|
||||||
|
let creating = false;
|
||||||
|
let error: string | null = null;
|
||||||
|
|
||||||
|
async function createUser() {
|
||||||
|
creating = true;
|
||||||
|
error = null;
|
||||||
|
try {
|
||||||
|
const userId = await convex.mutation(api.users.createUser, { name });
|
||||||
|
console.log('Created user', userId);
|
||||||
|
} catch (e) {
|
||||||
|
error = (e as Error).message;
|
||||||
|
} finally {
|
||||||
|
creating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input bind:value={name} placeholder="Name" />
|
||||||
|
<button on:click={createUser} disabled={creating}>Create</button>
|
||||||
|
{#if error}<p class="error">{error}</p>{/if}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.5 Using **actions** (Node‑only) from Svelte
|
||||||
|
|
||||||
|
Actions run in a Node environment, so they cannot be called directly from the browser. Use a **mutation** that internally calls the action, or expose a HTTP endpoint that triggers the action.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Scheduler / Cron (Backend)
|
||||||
|
|
||||||
|
> Same as original guide – define `crons.ts` and export the default `crons` object.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. File Storage (Backend)
|
||||||
|
|
||||||
|
> Same as original guide – use `ctx.storage.getUrl()` and query `_storage` for metadata.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. TypeScript Helpers (Backend)
|
||||||
|
|
||||||
|
> Keep using `Id<'table'>` from `./_generated/dataModel`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Svelte‑Specific Tips
|
||||||
|
|
||||||
|
| Topic | Recommendation |
|
||||||
|
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| **Store‑based data** | If you need reactive data across many components, wrap `convex.query` in a Svelte store (`readable`, `writable`). |
|
||||||
|
| **Error handling** | Use `try / catch` around every client call; surface the error in the UI. |
|
||||||
|
| **SSR / SvelteKit** | Calls made in `load` functions run on the server; you can use `convex.query` there without worrying about the browser environment. |
|
||||||
|
| **Environment variables** | Prefix with `VITE_` for client‑side access (`import.meta.env.VITE_CONVEX_URL`). |
|
||||||
|
| **Testing** | Use the Convex mock client (`createMockConvexClient`) provided by `@convex-dev/convex-svelte` for unit tests. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Full Example (SvelteKit + Convex)
|
||||||
|
|
||||||
|
### 10.1 Backend (`convex/users.ts`)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { mutation, query } from './_generated/server';
|
||||||
|
import { v } from 'convex/values';
|
||||||
|
|
||||||
|
export const createUser = mutation({
|
||||||
|
args: { name: v.string() },
|
||||||
|
returns: v.id('users'),
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
return await ctx.db.insert('users', { name: args.name });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getUser = query({
|
||||||
|
args: { userId: v.id('users') },
|
||||||
|
returns: v.object({ name: v.string() }),
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
const user = await ctx.db.get(args.userId);
|
||||||
|
if (!user) throw new Error('Not found');
|
||||||
|
return { name: user.name };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 Frontend (`src/routes/+page.svelte`)
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<script lang="ts">
|
||||||
|
import { convex } from '$lib/convex';
|
||||||
|
import { api } from '$lib/convex/_generated/api';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
let name = '';
|
||||||
|
let createdId: string | null = null;
|
||||||
|
let loading = false;
|
||||||
|
let error: string | null = null;
|
||||||
|
|
||||||
|
async function create() {
|
||||||
|
loading = true;
|
||||||
|
error = null;
|
||||||
|
try {
|
||||||
|
createdId = await convex.mutation(api.users.createUser, { name });
|
||||||
|
} catch (e) {
|
||||||
|
error = (e as Error).message;
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input bind:value={name} placeholder="Your name" />
|
||||||
|
<button on:click={create} disabled={loading}>Create user</button>
|
||||||
|
{#if createdId}<p>Created user id: {createdId}</p>{/if}
|
||||||
|
{#if error}<p class="error">{error}</p>{/if}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Checklist for New Files
|
||||||
|
|
||||||
|
- ✅ All Convex functions use the **new syntax** (`query({ … })`).
|
||||||
|
- ✅ Every public function has **argument** and **return** validators.
|
||||||
|
- ✅ Svelte components import the generated `api` object from `convex/_generated/api`.
|
||||||
|
- ✅ All client calls use the `convex` instance from `$lib/convex`.
|
||||||
|
- ✅ Environment variable `VITE_CONVEX_URL` is defined in `.env`.
|
||||||
|
- ✅ Errors are caught and displayed in the UI.
|
||||||
|
- ✅ Types are imported from `convex/_generated/dataModel` when needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. References
|
||||||
|
|
||||||
|
- Convex Docs – [Functions](https://docs.convex.dev/functions)
|
||||||
|
- Convex Svelte SDK – [`@convex-dev/convex-svelte`](https://github.com/convex-dev/convex-svelte)
|
||||||
|
- SvelteKit Docs – [Loading Data](https://kit.svelte.dev/docs/loading)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Keep these guidelines alongside the existing `svelte-rules.md` so that contributors have a single source of truth for both frontend and backend conventions._
|
||||||
Reference in New Issue
Block a user