### Start Development Server Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/README.md Commands to start the development server for the SvelteKit frontend and the Convex backend. It demonstrates running both processes in parallel in separate terminals. ```sh # Terminal 1 - Convex backend pnpm convex dev # Terminal 2 - SvelteKit frontend pnpm dev ``` -------------------------------- ### Basic Development Server Start Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/README.md A simple command to start the development server for the SvelteKit application. The app will be accessible at http://localhost:5173. ```sh pnpm dev ``` -------------------------------- ### Shadcn Color Convention Example Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/svelte/overview.md Illustrates the use of Shadcn's color conventions (`background`, `foreground`) and CSS variables defined with the oklch color space function for theming. ```css --primary: oklch(0.205 0 0); --primary-foreground: oklch(0.985 0 0); /* Usage example */ .bg-primary { background-color: hsl(var(--primary)); } .text-primary-foreground { color: hsl(var(--primary-foreground)); } ``` -------------------------------- ### Install Autumn and Dependencies (Bash) Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/autumn.md Installs the necessary Autumn JavaScript libraries and development tools using pnpm. This is the first step in setting up Autumn within your project. ```bash pnpm add autumn-js @useautumn/convex pnpm add -D atmn ``` -------------------------------- ### Common Enhanced Image Patterns in Svelte Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/svelte/overview.md Provides examples of common patterns for using the `` component, including styling for avatars and hero images, and specifying appropriate `sizes` attributes for different use cases. ```svelte ``` -------------------------------- ### Setup and Use convex-svelte for Real-time Queries Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/convex.md This TypeScript snippet illustrates the setup and usage of the `convex-svelte` library. It shows how to initialize Convex in a Svelte application's root layout and how to use `useQuery` for reactive data fetching and `useConvexClient` for mutations and actions. It also demonstrates conditional queries using Svelte 5's `$derived`. ```typescript // +layout.svelte - Setup convex-svelte import { setupConvex } from 'convex-svelte'; import { PUBLIC_CONVEX_URL } from '$env/static/public'; setupConvex(PUBLIC_CONVEX_URL); // Component with reactive queries import { useQuery, useConvexClient } from 'convex-svelte'; import { api } from '../convex/_generated/api'; let userId = $state | null>(null); const client = useConvexClient(); // Conditional reactive queries using $derived for Svelte 5 let currentTaskQuery = $derived( userId ? useQuery(api.tasks.getCurrentTask, () => ({ userId: userId! })) : { data: null, isLoading: false } ); // Access reactive data let currentTask = $derived(currentTaskQuery.data); let loading = $derived(currentTaskQuery.isLoading); // Use client for mutations and actions async function handleComplete() { if (!userId || !currentTask) return; await client.mutation(api.tasks.completeTask, { userId, taskId: currentTask._id }); // UI automatically updates via reactive queries } async function handleSync() { if (!userId) return; await client.action(api.linear.triggerTaskSync, { userId }); } ``` -------------------------------- ### Autumn Billing Configuration and Setup (TypeScript) Source: https://context7.com/joachimchauvet/modernstack-saas/llms.txt Configures Autumn billing with Stripe integration. It defines features and products, including message quotas and pricing tiers. The setup involves identifying customers via an authentication component and setting up the Convex integration for Autumn. Dependencies include the 'atmn' library and '@useautumn/convex'. ```typescript // autumn.config.ts import { feature, product, featureItem, priceItem } from 'atmn'; // Define features export const messages = feature({ id: 'messages', name: 'messages', type: 'single_use' }); // Define products export const pro = product({ id: 'pro', name: 'Pro', items: [ featureItem({ feature_id: messages.id, included_usage: 100, interval: 'month' }), priceItem({ price: 20, interval: 'month' }) ] }); export const freePlan = product({ id: 'free_plan', name: 'Free Plan', items: [ featureItem({ feature_id: messages.id, included_usage: 5, interval: 'month' }) ] }); // src/convex/autumn.ts import { components } from './_generated/api'; import { Autumn } from '@useautumn/convex'; import { authComponent } from './auth'; export const autumn = new Autumn(components.autumn, { secretKey: process.env.AUTUMN_SECRET_KEY ?? '', identify: async (ctx: any) => { try { const user = await authComponent.getAuthUser(ctx); if (!user) return null; return { customerId: user._id, customerData: { name: user.name ?? '', email: user.email ?? '' } }; } catch { return null; } } }); export const { track, cancel, query, attach, check, checkout, usage, setupPayment, createCustomer, listProducts, billingPortal, createReferralCode, redeemReferralCode, createEntity, getEntity } = autumn.api(); ``` -------------------------------- ### SvelteKit Recommended Project Structure Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/svelte/overview.md Presents the standard directory structure for a SvelteKit project, including key folders like `src/lib`, `src/routes`, `static`, and configuration files. ```bash - src/ - lib/ - routes/ - app.html - static/ - svelte.config.js - vite.config.js ``` -------------------------------- ### Svelte 5 Component Props Interface Example Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/svelte/overview.md Shows the correct way to define component props using TypeScript interfaces with the $props rune in Svelte 5, contrasting it with the older, error-prone method. ```typescript // ✅ Correct - use $props with interface interface Props { value: string; error?: ErrorType | null; customPrompt?: string; onOptionChange?: (value: string) => void; } let { value, error = null, customPrompt = '', onOptionChange = () => {} }: Props = $props(); ``` -------------------------------- ### Null Return Type Example Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/convex.md Demonstrates how to use the `v.null()` validator for functions that are intended to return a null value, providing a specific example of a query function returning null. ```APIDOC ## Null Return Type Example ### Description Illustrates the correct way to specify and return a null value from a Convex function using the `v.null()` validator. It emphasizes using `null` explicitly instead of `undefined`. ### Method N/A (Convex function definition) ### Endpoint N/A ### Parameters #### Path Parameters N/A #### Query Parameters N/A #### Request Body N/A ### Request Example ```typescript import { query } from './_generated/server'; import { v } from 'convex/values'; export const exampleQuery = query({ args: {}, returns: v.null(), handler: async (ctx, args) => { console.log('This query returns a null value'); return null; } }); ``` ### Response #### Success Response (200) - **Return Value** (null) - The function explicitly returns null. #### Response Example ```json null ``` ``` -------------------------------- ### Define Products for Autumn (TypeScript) Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/autumn.md Defines products, features, and pricing tiers in `autumn.config.ts` using Autumn's configuration API. This example shows a 'messages' feature and 'free' and 'pro' plans with different included usage and pricing. ```typescript import { feature, product, featureItem, priceItem } from 'atmn'; // Define Features export const messages = feature({ id: 'messages', name: 'Messages', type: 'single_use' }); // Define Products export const free = product({ id: 'free_plan', name: 'Free Plan', items: [ featureItem({ feature_id: messages.id, included_usage: 5, interval: 'month' }) ] }); export const pro = product({ id: 'pro', name: 'Pro', items: [ featureItem({ feature_id: messages.id, included_usage: 100, interval: 'month' }), priceItem({ price: 20, interval: 'month' }) ] }); ``` -------------------------------- ### Per-Image Transformations with Enhanced Image Query Parameters Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/svelte/overview.md Explains how to apply transformations to individual images directly within the `src` attribute using query parameters with '@sveltejs/enhanced-img'. Examples include adjusting quality and generating multiple widths. ```svelte ``` -------------------------------- ### Define a Convex Action Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/convex.md Provides an example of defining a serverless action in Convex. Actions are asynchronous functions that run on the server and do not have direct access to the database context (`ctx.db`). This example demonstrates the basic structure of an action, including its arguments, return type, and handler function. It also shows how to use `"use node";` for Node.js built-in modules. ```typescript import { action } from './_generated/server'; export const exampleAction = action({ args: {}, returns: v.null(), handler: async (ctx, args) => { console.log('This action does not return anything'); return null; } }); ``` -------------------------------- ### Create Better Auth API Adapter for Convex Source: https://context7.com/joachimchauvet/modernstack-saas/llms.txt Generates a Convex API adapter for Better Auth, enabling CRUD operations for the defined authentication tables. It integrates with the existing authentication setup (`createAuth`) and relies on the schema defined in `./schema.ts`. ```typescript // src/convex/betterAuth/adapter.ts import { createApi } from '@convex-dev/better-auth'; import schema from './schema'; import { createAuth } from '../auth'; export const { create, findOne, findMany, updateOne, updateMany, deleteOne, deleteMany } = createApi(schema, createAuth); ``` -------------------------------- ### Svelte 5 Runes for Two-Way Bindable Props Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/svelte/overview.md Demonstrates the creation of two-way bindable props using the $bindable rune in conjunction with $props. ```typescript let { bindableProp = $bindable() } = $props(); ``` -------------------------------- ### Creating a Post (Server-side `form` handler) Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/svelte/remote_functions.md Example server-side handler for creating a new post using the `form` function. It includes user authentication, data validation, database insertion, and redirection. ```APIDOC ## Creating a Post (Server-side `form` handler) This is an example of a server-side `form` handler for creating a new post. ### Method `form` ### Endpoint (Implicitly handled by the SvelteKit routing based on file location) ### Parameters #### Request Body (FormData) - **title** (string) - Required - The title of the post. - **content** (string) - Required - The content of the post. ### Server Logic ```js // file: src/routes/blog/data.remote.js import * as v from 'valibot'; import { error, redirect } from '@sveltejs/kit'; import { query, form } from '$app/server'; import * as db from '$lib/server/database'; import * as auth from '$lib/server/auth'; export const createPost = form(async (data) => { // Check the user is logged in const user = await auth.getUser(); if (!user) error(401, 'Unauthorized'); const title = data.get('title'); const content = data.get('content'); // Check the data is valid if (typeof title !== 'string' || typeof content !== 'string') { error(400, 'Title and content are required'); } const slug = title.toLowerCase().replace(/ /g, '-'); // Insert into the database await db.sql` INSERT INTO post (slug, title, content) VALUES (${slug}, ${title}, ${content}) `; // Redirect to the newly created page redirect(303, `/blog/${slug}`); }); ``` ### Response #### Redirect (303) - Redirects to the newly created post's URL (e.g., `/blog/my-new-post`). #### Error Responses - **401 Unauthorized**: If the user is not logged in. - **400 Bad Request**: If the title or content are missing or invalid. ``` -------------------------------- ### Query File Metadata from Convex Storage Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/convex.md This example shows how to query file metadata from Convex's `_storage` system table using `ctx.db.system.get`. It defines a `FileMetadata` type and demonstrates fetching metadata for a given file ID. Avoid using deprecated methods like `ctx.storage.getMetadata`. ```typescript import { query } from "./_generated/server"; import { Id } from "./_generated/dataModel"; type FileMetadata = { _id: Id<"_storage">; _creationTime: number; contentType?: string; sha256: string; size: number; } export const exampleQuery = query({ args: { fileId: v.id("_storage") }, returns: v.null(); handler: async (ctx, args) => { const metadata: FileMetadata | null = await ctx.db.system.get(args.fileId); console.log(metadata); return null; }, }); ``` -------------------------------- ### Optimized Image Imports with '?enhanced' Query Parameter Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/svelte/overview.md Illustrates the correct way to import images for optimization using the '@sveltejs/enhanced-img' package. Importing with the '?enhanced' query parameter ensures that images are processed for performance benefits. ```typescript // ✅ Correct - use ?enhanced for optimization import heroImage from '$lib/assets/images/hero.png?enhanced'; import avatarImg from '$lib/assets/images/avatar.jpg?enhanced'; // ❌ Wrong - regular imports don't get optimized import heroImage from '$lib/assets/images/hero.png'; ``` -------------------------------- ### GET /api/remote/query (Example: getPost with Argument Validation) Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/svelte/remote_functions.md The `query` function can accept arguments, which should be validated using a schema library like Zod or Valibot to ensure type safety. This example fetches a single post based on its slug. ```APIDOC ## GET /api/remote/query (Example: getPost with Argument Validation) ### Description Fetches a specific resource (e.g., a blog post) from the server using an argument (e.g., post slug). Arguments passed to remote query functions are serialized and deserialized, and should be validated using a schema library to prevent potential errors and ensure data integrity. ### Method GET ### Endpoint /api/remote/query (Generated endpoint for the `getPost` function) ### Parameters #### Path Parameters N/A #### Query Parameters N/A #### Request Body N/A ### Request Example ```javascript /// file: src/routes/blog/data.remote.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; } // @filename: index.js // ---cut--- import * as v from 'valibot'; import { error } from '@sveltejs/kit'; import { query } from '$app/server'; import * as db from '$lib/server/database'; export const getPosts = query(async () => { /* ... */ }); export const getPost = query(v.string(), async (slug) => { const [post] = await db.sql` SELECT * FROM post WHERE slug = ${slug} `; if (!post) error(404, 'Not found'); return post; }); ``` ### Response #### Success Response (200) - **post** (Object) - The requested post object, containing details like title, content, etc. #### Response Example ```json { "title": "My First Post", "slug": "my-first-post", "content": "

This is the content of my first post.

" } ``` ### Error Response - **404** - If the post with the given slug is not found. ### Component Usage Example ```svelte

{post.title}

{@html post.content}
``` ``` -------------------------------- ### GET /api/remote/query (Example: getPosts) Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/svelte/remote_functions.md The `query` function is used to fetch dynamic data from the server. It can be awaited directly in Svelte components or accessed via `loading`, `error`, and `current` properties. ```APIDOC ## GET /api/remote/query (Example: getPosts) ### Description Fetches dynamic data, such as a list of blog posts, from the server. This function can be used with Svelte's `await` syntax for seamless data loading. ### Method GET ### Endpoint /api/remote/query (This is a generated endpoint; the actual path depends on the remote function definition) ### Parameters #### Path Parameters N/A #### Query Parameters N/A #### Request Body N/A ### Request Example ```javascript /// file: src/routes/blog/data.remote.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; } // @filename: index.js // ---cut--- import { query } from '$app/server'; import * as db from '$lib/server/database'; export const getPosts = query(async () => { const posts = await db.sql` SELECT title, slug FROM post ORDER BY published_at DESC `; return posts; }); ``` ### Response #### Success Response (200) - **posts** (Array) - An array of post objects, each containing 'title' and 'slug'. #### Response Example ```json [ { "title": "My First Post", "slug": "my-first-post" }, { "title": "Another Post", "slug": "another-post" } ] ``` ### Component Usage Example (Await) ```svelte

Recent posts

    {#each await getPosts() as { title, slug }}
  • {title}
  • {/each}
``` ### Component Usage Example (Loading/Error States) ```svelte {#if query.error}

oops!

{:else if query.loading}

loading...

{:else}
    {#each query.current as { title, slug }}
  • {title}
  • {/each}
{/if} ``` ``` -------------------------------- ### Build and Preview Production Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/README.md Commands to create a production build of the SvelteKit application and then preview the production build locally. This is typically done before deploying the application. ```sh pnpm build pnpm preview ``` -------------------------------- ### Push Products to Autumn Sandbox (Bash) Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/autumn.md Synchronizes the product definitions from your local `autumn.config.ts` file to the Autumn sandbox environment. This command makes your defined products available for testing. ```bash pnpm dlx atmn push ``` -------------------------------- ### Initialize Better Auth Client (SvelteKit/TypeScript) Source: https://context7.com/joachimchauvet/modernstack-saas/llms.txt Sets up the Better Auth client in a SvelteKit application, integrating Convex and admin plugins for authentication. It requires `better-auth/svelte`, `@convex-dev/better-auth/client/plugins`, and `better-auth/client/plugins`. The client can be used for sign-up, sign-in via email, and social sign-in. ```typescript import { createAuthClient } from 'better-auth/svelte'; import { convexClient } from '@convex-dev/better-auth/client/plugins'; import { adminClient } from 'better-auth/client/plugins'; export const authClient = createAuthClient({ plugins: [convexClient(), adminClient()] }); // Usage in Svelte component // src/lib/components/login-form.svelte async function handleSignUp(name: string, email: string, password: string) { await authClient.signUp.email( { name, email, password }, { onSuccess: () => { goto('/dashboard'); }, onError: (ctx) => { console.error(ctx.error.message); } } ); } async function handleSignIn(email: string, password: string) { await authClient.signIn.email( { email, password }, { onSuccess: () => { goto('/dashboard'); }, onError: (ctx) => { console.error(ctx.error.message); } } ); } async function handleGoogleOAuth() { await authClient.signIn.social({ provider: 'google', callbackURL: '/dashboard' }); } ``` -------------------------------- ### Initialize Autumn CLI (Bash) Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/autumn.md Initializes the Autumn command-line interface, which creates the `autumn.config.ts` file. This file is crucial for defining your products and their associated features and prices. ```bash pnpm dlx atmn init ``` -------------------------------- ### Array Validator Example Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/convex.md Shows an example of using the `v.array` validator to define an argument that accepts an array of mixed types (strings and numbers). ```APIDOC ## Array Validator Example ### Description Demonstrates how to use the `v.array` validator in Convex to define arguments that must be arrays. This example specifically allows an array containing a mix of strings and numbers. ### Method N/A (Convex function definition) ### Endpoint N/A ### Parameters #### Path Parameters N/A #### Query Parameters N/A #### Request Body - **simpleArray** (Array) - Required - An array that can contain strings or numbers. ### Request Example ```typescript import { mutation } from './_generated/server'; import { v } from 'convex/values'; export default mutation({ args: { simpleArray: v.array(v.union(v.string(), v.number())) }, handler: async (ctx, args) => { //... } }); ``` ### Response #### Success Response (200) N/A #### Response Example N/A ``` -------------------------------- ### SvelteKit Server Load: Billing Data Fetching Source: https://context7.com/joachimchauvet/modernstack-saas/llms.txt Implements a server-side load function for the billing page in a SvelteKit application. It fetches available products and customer subscription data using Convex actions. Dependencies include '@mmailaender/convex-better-auth-svelte/sveltekit' and Convex API definitions. ```typescript // src/routes/(app)/billing/+page.server.ts import { createConvexHttpClient } from '@mmailaender/convex-better-auth-svelte/sveltekit'; import { api } from '$convex/_generated/api'; import type { PageServerLoad } from './$types'; export const load: PageServerLoad = async ({ cookies }) => { const client = createConvexHttpClient({ cookies }); try { const [productsResult, customerResult] = await Promise.all([ client.action(api.billing.listProducts, {}), client.action(api.billing.getCustomer, {}) ]); return { products: productsResult?.data?.list || [], customerData: customerResult || null }; } catch (error) { console.error('Error loading billing data:', error); return { products: [], customerData: null }; } }; ``` -------------------------------- ### Define Record Type with TypeScript Example Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/convex.md Demonstrates how to define and use a Record type in TypeScript for mapping user IDs to usernames. It utilizes Convex's `Id` type and the `v.record` validator for type safety and efficient data handling. This example fetches user data and constructs a record mapping user IDs to their usernames. ```typescript import { query } from './_generated/server'; import { Doc, Id } from './_generated/dataModel'; export const exampleQuery = query({ args: { userIds: v.array(v.id('users')) }, returns: v.record(v.id('users'), v.string()), handler: async (ctx, args) => { const idToUsername: Record, string> = {}; for (const userId of args.userIds) { const user = await ctx.db.get(userId); if (user) { users[user._id] = user.username; } } return idToUsername; } }); ``` -------------------------------- ### Load Products from Convex Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/autumn.md Loads product information from the Convex backend using the `billing.listProducts` action. It expects products to be returned in `result.data.list` and handles potential errors during the fetch operation. Authentication is not required for this action. ```typescript import { useConvexClient } from 'convex-svelte'; import { api } from '$convex/_generated/api.js'; import { onMount } from 'svelte'; const client = useConvexClient(); let products = $state([]); onMount(async () => { try { const result = await client.action(api.billing.listProducts, {}); if (result?.data?.list) { products = result.data.list; } } catch (error) { console.error('Error loading products:', error); } }); ``` -------------------------------- ### Discriminated Union Schema Example Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/convex.md Illustrates defining a schema with validators that codify a discriminated union type, allowing for different object structures based on a 'kind' field. ```APIDOC ## Discriminated Union Schema Example ### Description Shows how to define a table schema in Convex using `v.union` to create a discriminated union type. This pattern is useful for representing different states or types of data within a single table, such as 'error' or 'success' results. ### Method N/A (Convex schema definition) ### Endpoint N/A ### Parameters N/A ### Request Example ```typescript import { defineSchema, defineTable } from 'convex/server'; import { v } from 'convex/values'; export default defineSchema({ results: defineTable( v.union( v.object({ kind: v.literal('error'), errorMessage: v.string() }), v.object({ kind: v.literal('success'), value: v.number() }) ) ) }); ``` ### Response #### Success Response (200) N/A #### Response Example N/A ``` -------------------------------- ### Initialize Autumn Client and Customer Identification (TypeScript) Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/autumn.md Sets up the Autumn client in `convex/autumn.ts`, defining how to identify customers. It uses the authenticated user's ID and details from Convex, ensuring proper customer mapping for billing. ```typescript import { components } from './_generated/api'; import { Autumn } from '@useautumn/convex'; import { authComponent } from './auth'; export const autumn = new Autumn(components.autumn, { secretKey: process.env.AUTUMN_SECRET_KEY ?? '', // eslint-disable-next-line @typescript-eslint/no-explicit-any identify: async (ctx: any) => { try { const user = await authComponent.getAuthUser(ctx); if (!user) return null; return { customerId: user._id, customerData: { name: user.name ?? '', email: user.email ?? '' } }; } catch { // User is not authenticated, return null return null; } } }); export const { track, cancel, query, attach, check, checkout, usage, setupPayment, createCustomer, listProducts, billingPortal, createReferralCode, redeemReferralCode, createEntity, getEntity } = autumn.api(); ``` -------------------------------- ### Svelte 5 Event Handler Syntax Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/svelte/overview.md Highlights the change in event handler syntax from Svelte 4 to Svelte 5, where `on:event` is replaced by `onEvent`. ```html // ✅ Svelte 5 ``` -------------------------------- ### Parse Product Data for Display Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/autumn.md Transforms the raw product data into a display-friendly format for UI elements. It maps product details, extracts pricing and feature information (specifically 'messages'), and formats them into `Plan` objects. ```typescript // Map products to display format let plans = $derived( Array.isArray(products) ? products.map((product) => { const priceItem = product.items?.find((item) => item.price); const featureItem = product.items?.find((item) => item.feature_id === 'messages'); return { id: product.id, name: product.name, price: priceItem?.price || 0, interval: priceItem?.interval || 'month', features: [`${featureItem?.included_usage || 0} messages per month`] }; }) : [] ); ``` -------------------------------- ### Svelte Billing UI: Checkout and Subscription Management Source: https://context7.com/joachimchauvet/modernstack-saas/llms.txt Provides the client-side user interface for billing management within a Svelte application. It allows users to initiate product checkouts and access the subscription management portal. It utilizes Convex Svelte hooks for interacting with backend actions. ```typescript // src/routes/(app)/billing/+page.svelte import { api } from '$convex/_generated/api.js'; import { useConvexClient } from 'convex-svelte'; const client = useConvexClient(); async function handleCheckout(productId: string) { try { const result = await client.action(api.billing.checkout, { productId }); if (result?.data?.url) { window.location.href = result.data.url; } } catch (error) { console.error('Checkout error:', error); } } async function handleManageSubscription() { try { const result = await client.action(api.billing.billingPortal, {}); if (result?.data?.url) { window.location.href = result.data.url; } } catch (error) { console.error('Billing portal error:', error); } } // Display current plan and available plans with checkout buttons let plans = $derived( products.map(product => ({ id: product.id, name: product.name, price: product.items?.find(item => item.price)?.price || 0, interval: product.items?.find(item => item.price)?.interval || 'month', features: [`${product.items?.find(item => item.feature_id)?.included_usage || 0} messages per month`], isCurrent: activeProductIds.includes(product.id) })) ); ``` -------------------------------- ### Convex Data Types and Validators Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/convex.md Provides a comprehensive overview of the valid Convex data types, their corresponding TypeScript/JavaScript types, example usage, and the specific validators used for arguments and schemas. ```APIDOC ## Convex Data Types and Validators ### Description This section details the fundamental data types supported by Convex, including their TypeScript/JavaScript equivalents, practical examples, and the necessary validators for use in function arguments and database schemas. It also includes important notes on type behavior and limitations. ### Method N/A ### Endpoint N/A ### Parameters N/A ### Request Example N/A ### Response #### Success Response (200) - **Type Table** (Table) - A table outlining Convex types, TS/JS types, usage, validators, and notes. #### Response Example ```markdown | Convex Type | TS/JS type | Example Usage | Validator for argument validation and schemas | Notes | | ----------- | ------------| -----------------------| -----------------------------------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Id | string | `doc._id` | `v.id(tableName)` | | | Null | null | `null` | `v.null()` | JavaScript's `undefined` is not a valid Convex value. Functions the return `undefined` or do not return will return `null` when called from a client. Use `null` instead. | | Int64 | bigint | `3n` | `v.int64()` | Int64s only support BigInts between -2^63 and 2^63-1. Convex supports `bigint`s in most modern browsers. | | Float64 | number | `3.1` | `v.number()` | Convex supports all IEEE-754 double-precision floating point numbers (such as NaNs). Inf and NaN are JSON serialized as strings. | | Boolean | boolean | `true` | `v.boolean()` | | String | string | `"abc"` | `v.string()` | Strings are stored as UTF-8 and must be valid Unicode sequences. Strings must be smaller than the 1MB total size limit when encoded as UTF-8. | | Bytes | ArrayBuffer | `new ArrayBuffer(8)` | `v.bytes()` | Convex supports first class bytestrings, passed in as `ArrayBuffer`s. Bytestrings must be smaller than the 1MB total size limit for Convex types. | | Array | Array] | `[1, 3.2, "abc"]` | `v.array(values)` | Arrays can have at most 8192 values. | | Object | Object | `{a: "abc"}` | `v.object({property: value})` | Convex only supports "plain old JavaScript objects" (objects that do not have a custom prototype). Objects can have at most 1024 entries. Field names must be nonempty and not start with "$" or "_". | | Record | Record | `{"a": "1", "b": "2"}` | `v.record(keys, values)` | Records are objects at runtime, but can have dynamic keys. Keys must be only ASCII characters, nonempty, and not start with "$" or "_". | ``` ``` -------------------------------- ### Define an HTTP Endpoint Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/convex.md This example shows how to define an HTTP endpoint in Convex using the `httpAction` decorator. It specifies the path, method, and handler for the endpoint, which in this case echoes the received request body. ```typescript import { httpRouter } from 'convex/server'; import { httpAction } from './_generated/server'; const http = httpRouter(); http.route({ path: '/echo', method: 'POST', handler: httpAction(async (ctx, req) => { const body = await req.bytes(); return new Response(body, { status: 200 }); }) }); ``` -------------------------------- ### Configure Better Auth Instance with Convex and Social Providers Source: https://context7.com/joachimchauvet/modernstack-saas/llms.txt This TypeScript code snippet demonstrates the configuration of a Better Auth instance, integrating it with Convex for backend services. It enables email/password authentication, password reset functionality via Resend, email change verification, and Google OAuth. ```typescript import { createClient } from '@convex-dev/better-auth'; import { convex } from '@convex-dev/better-auth/plugins'; import { betterAuth } from 'better-auth'; import { admin } from 'better-auth/plugins'; const authComponent = createClient(components.betterAuth, { local: { schema: authSchema } }); export const createAuth = (ctx: GenericCtx) => { return betterAuth({ baseURL: process.env.SITE_URL!, database: authComponent.adapter(ctx), user: { changeEmail: { enabled: true, sendChangeEmailVerification: async ({ user, newEmail, url }) => { await fetch('https://api.resend.com/emails', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.RESEND_API_KEY}` }, body: JSON.stringify({ from: process.env.RESET_EMAIL_FROM || 'App ', to: user.email, subject: 'Approve email change', html: `

Hello ${user.name},

Click to approve: Approve Email Change

` }) }); } } }, emailAndPassword: { enabled: true, requireEmailVerification: false, sendResetPassword: async ({ user, url }) => { await fetch('https://api.resend.com/emails', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.RESEND_API_KEY}` }, body: JSON.stringify({ from: process.env.RESET_EMAIL_FROM || 'App ', to: user.email, subject: 'Reset your password', html: `

Reset password: Reset Password

` }) }); } }, socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET! } }, plugins: [convex(), admin()] }); }; // Get current authenticated user export const getCurrentUser = query({ args: {}, handler: async (ctx) => { try { return await authComponent.getAuthUser(ctx); } catch { return null; } } }); ``` -------------------------------- ### HTTP Endpoint Syntax Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/convex.md Illustrates how to define HTTP endpoints within Convex using `httpRouter` and `httpAction`, including path definition, method, and handler implementation. ```APIDOC ## HTTP Endpoint Syntax ### Description Provides the structure for defining HTTP endpoints in Convex, which are managed in `convex/http.ts` and require the `httpAction` decorator. Endpoints are registered at the exact path specified. ### Method N/A (Convex HTTP endpoint definition) ### Endpoint N/A ### Parameters #### Path Parameters N/A #### Query Parameters N/A #### Request Body N/A ### Request Example ```typescript import { httpRouter } from 'convex/server'; import { httpAction } from './_generated/server'; const http = httpRouter(); http.route({ path: '/echo', method: 'POST', handler: httpAction(async (ctx, req) => { const body = await req.bytes(); return new Response(body, { status: 200 }); }) }); ``` ### Response #### Success Response (200) N/A (Response depends on the handler implementation) #### Response Example N/A ``` -------------------------------- ### Svelte 5 Runes for Derived Values Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/svelte/overview.md Illustrates how to compute derived reactive values using the $derived rune. This replaces the older reactive statements ($:) for computed properties. ```typescript let doubled = $derived(count * 2); let fullName = $derived(`${user.firstName} ${user.lastName}`); ``` -------------------------------- ### Pagination API Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/convex.md Documentation for implementing paginated queries in Convex, showing how to fetch data in incremental pages and detailing the structure of pagination options and results. ```APIDOC ## Pagination API ### Description Convex supports paginated queries, allowing you to efficiently retrieve large datasets in manageable chunks or pages. This is achieved by using the `.paginate()` method on a Convex query. ### Implementing Paginated Queries To implement pagination, your query handler should accept `paginationOpts` and use the `.paginate()` method on your database query. ### `paginationOptsValidator` Convex provides a validator for pagination options: - **`paginationOptsValidator`**: An object with the following properties: - **`numItems`** (`v.number()`): The maximum number of documents to return per page. - **`cursor`** (`v.union(v.string(), v.null())`): The cursor indicating the starting point for fetching the next page of documents. Can be `null` for the first page. ### `.paginate()` Method When a query uses `.paginate()`, it returns an object with the following structure: - **`page`** (Array): An array of documents fetched for the current page. - **`isDone`** (Boolean): `true` if this is the last page of documents, `false` otherwise. - **`continueCursor`** (String): A cursor string to use for fetching the next page. If `isDone` is `true`, this value might be null or undefined. ### Example of a Paginated Query ```typescript import { v } from 'convex/values'; import { query } from './_generated/server'; import { paginationOptsValidator } from 'convex/server'; // Import the validator export const listMessages = query({ // Accepts pagination options and an optional author filter args: { paginationOpts: paginationOptsValidator, author: v.optional(v.string()), // Example of an additional filter }, // The return type is implicitly the object returned by .paginate() returns: "any", // In a real app, you'd define a specific return type like `{ page: YourMessageType[], isDone: boolean, continueCursor: string | null }` handler: async (ctx, args) => { let dbQuery = ctx.db.query('messages'); // Assuming a 'messages' table // Apply optional author filter if (args.author) { dbQuery = dbQuery.filter((q) => q.eq(q.field('author'), args.author)); } // Order results and paginate const result = await dbQuery .order('desc') // Example ordering .paginate(args.paginationOpts); return result; }, }); ``` ### Calling a Paginated Query When calling a paginated query, you must provide the `paginationOpts`. ```typescript import { api } from '../convex/_generated/api'; async function fetchAllMessages(ctx: any) { let allMessages: any[] = []; let cursor: string | null = null; while (true) { const pageResult = await ctx.runQuery(api.messages.listMessages, { paginationOpts: { numItems: 10, // Fetch 10 items per page cursor: cursor, }, }); allMessages = allMessages.concat(pageResult.page); if (pageResult.isDone) { break; // Exit loop if it's the last page } cursor = pageResult.continueCursor; // Set cursor for the next iteration } return allMessages; } ``` ``` -------------------------------- ### Channel Management API Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/convex.md APIs for creating and managing chat channels. ```APIDOC ## POST /api/channels (createChannel) ### Description Creates a new chat channel with a given name. ### Method POST ### Endpoint /api/channels ### Parameters #### Request Body - **name** (string) - Required - The name of the channel. ### Request Example ```json { "name": "General Discussion" } ``` ### Response #### Success Response (200) - **channelId** (string) - The ID of the newly created channel. #### Response Example ```json { "channelId": "channelABC" } ``` ``` -------------------------------- ### Function References API Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/convex.md This section explains how to obtain and use function references to call other Convex functions, covering both public and internal functions and demonstrating Convex's file-based routing for references. ```APIDOC ## Function References API ### Description Function references act as pointers to registered Convex functions, enabling them to be called from other functions. Convex uses a file-based routing system to generate these references. ### Obtaining Function References - **Public Functions**: Use the `api` object, automatically generated in `convex/_generated/api.ts`. - **Internal Functions**: Use the `internal` object, also generated in `convex/_generated/api.ts`. ### File-Based Routing Convex maps the file structure within the `convex/` directory to the function references: - A public function `f` defined in `convex/example.ts` is referenced as `api.example.f`. - A private function `g` defined in `convex/example.ts` is referenced as `internal.example.g`. - Functions in nested directories follow the same pattern. For example, a public function `h` in `convex/messages/access.ts` is referenced as `api.messages.access.h`. ### Usage Example ```typescript import { query } from './_generated/server'; import { api } from '../convex/_generated/api'; // Adjust import path as needed // Example of calling an internal query function export const callInternal = query({ args: {}, returns: null, handler: async (ctx) => { // Assuming 'myInternalFunction' exists in 'convex/utils.ts' await ctx.runQuery(internal.utils.myInternalFunction); return null; } }); // Example of calling a public mutation function export const callPublicMutation = query({ args: { data: "any" }, returns: null, handler: async (ctx, args) => { // Assuming 'createItem' exists in 'convex/items.ts' await ctx.runMutation(api.items.createItem, { data: args.data }); return null; } }); ``` ``` -------------------------------- ### Svelte Basic State Declaration Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/svelte/reactivity_deep_dive.md Demonstrates how to declare a reactive variable using the `$state` rune in Svelte. This variable, when updated, will automatically trigger UI re-renders. The code shows a simple counter example. ```svelte

{count}

``` -------------------------------- ### Svelte 5 Removing Export Statements for Props Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/svelte/overview.md Explains the necessity of removing `export` statements for props when migrating to Svelte 5's $props rune to avoid redeclaration errors. ```typescript // ✅ Use props destructuring instead let { error = null, customPrompt = '' }: Props = $props(); ``` -------------------------------- ### User Management API Source: https://github.com/joachimchauvet/modernstack-saas/blob/dev/docs/convex.md Endpoints for creating and managing users within the application. ```APIDOC ## POST /api/users ### Description Creates a new user with the provided name. ### Method POST ### Endpoint /api/users ### Parameters #### Request Body - **name** (string) - Required - The name of the user to create. ### Request Example ```json { "name": "John Doe" } ``` ### Response #### Success Response (200) - **userId** (string) - The unique identifier for the newly created user. #### Response Example ```json { "userId": "user123abc" } ``` ```