Files
sgse-app/.agent/rules/convex-svelte-best-practices.md

2.9 KiB

trigger: glob globs: /*.svelte.ts,/*.svelte

Convex + Svelte Best Practices

This document outlines the mandatory rules and best practices for integrating Convex with Svelte in this project.

1. Imports

Always use the following import paths. Do NOT use $lib/convex or relative paths for generated files unless specifically required by a local override.

Correct Imports:

import { useQuery, useConvexClient } from 'convex-svelte';
import { api } from '@sgse-app/backend/convex/_generated/api';
import type { Id, Doc } from '@sgse-app/backend/convex/_generated/dataModel';

Incorrect Imports (Avoid):

import { convex } from '$lib/convex'; // Avoid direct client usage for queries
import { api } from '$lib/convex/_generated/api'; // Incorrect path
import { api } from '../convex/_generated/api'; // Relative path

2. Data Fetching

Use useQuery for Reactivity

Instead of manually fetching data inside onMount, use the useQuery hook. This ensures your data is reactive and automatically updates when the backend data changes.

Preferred Pattern:

<script lang="ts">
	import { useQuery } from 'convex-svelte';
	import { api } from '@sgse-app/backend/convex/_generated/api';

	const tasksQuery = useQuery(api.tasks.list, { status: 'pending' });
	const tasks = $derived(tasksQuery.data || []);
	const isLoading = $derived(tasksQuery.isLoading);
</script>

Avoid Pattern:

<script lang="ts">
	import { onMount } from 'svelte';
	import { convex } from '$lib/convex';

	let tasks = [];

	onMount(async () => {
		// This is not reactive!
		tasks = await convex.query(api.tasks.list, { status: 'pending' });
	});
</script>

Mutations

Use useConvexClient to access the client for mutations.

<script lang="ts">
	import { useConvexClient } from 'convex-svelte';
	import { api } from '@sgse-app/backend/convex/_generated/api';

	const client = useConvexClient();

	async function completeTask(id) {
		await client.mutation(api.tasks.complete, { id });
	}
</script>

3. Type Safety

No any

Strictly avoid using any. The Convex generated data model provides precise types for all your tables.

Use Generated Types

Use Doc<"tableName"> for full document objects and Id<"tableName"> for IDs.

Correct:

import type { Doc, Id } from '@sgse-app/backend/convex/_generated/dataModel';

let selectedTask: Doc<'tasks'> | null = $state(null);
let taskId: Id<'tasks'>;

Incorrect:

let selectedTask: any = $state(null);
let taskId: string;

Union Types for Enums

When dealing with status fields or other enums, define the specific union type instead of casting to any.

Correct:

async function updateStatus(newStatus: 'pending' | 'completed' | 'archived') {
	// ...
}

Incorrect:

async function updateStatus(newStatus: string) {
	// ...
	status: newStatus as any; // Avoid this
}