Next.js Supabase V3 (#463)

Version 3 of the kit:
- Radix UI replaced with Base UI (using the Shadcn UI patterns)
- next-intl replaces react-i18next
- enhanceAction deprecated; usage moved to next-safe-action
- main layout now wrapped with [locale] path segment
- Teams only mode
- Layout updates
- Zod v4
- Next.js 16.2
- Typescript 6
- All other dependencies updated
- Removed deprecated Edge CSRF
- Dynamic Github Action runner
This commit is contained in:
Giancarlo Buomprisco
2026-03-24 13:40:38 +08:00
committed by GitHub
parent 4912e402a3
commit 7ebff31475
840 changed files with 71395 additions and 20095 deletions

View File

@@ -55,7 +55,10 @@ create policy "projects_write" on public.projects for all
Use `server-action-builder` skill for detailed patterns.
**Rule: Services are decoupled from interfaces.** The service is pure logic that receives dependencies (database client, etc.) as arguments — it never imports framework-specific modules. The server action is a thin adapter that resolves dependencies and calls the service. This means the same service can be called from a server action, an MCP tool, a CLI command, or a unit test with zero changes.
**Rule: Services are decoupled from interfaces.** The service is pure logic that receives dependencies (database client,
etc.) as arguments — it never imports framework-specific modules. The server action is a thin adapter that resolves
dependencies and calls the service. This means the same service can be called from a server action, an MCP tool, a CLI
command, or a unit test with zero changes.
Create in route's `_lib/server/` directory:

View File

@@ -19,8 +19,8 @@ export class AuthPageObject {
}
async signOut() {
await this.page.click('[data-test="account-dropdown-trigger"]');
await this.page.click('[data-test="account-dropdown-sign-out"]');
await this.page.click('[data-test="workspace-dropdown-trigger"]');
await this.page.click('[data-test="workspace-sign-out"]');
}
async bootstrapUser(params: { email: string; password: string; name: string }) {
@@ -47,9 +47,19 @@ export class AuthPageObject {
## Common Selectors
```typescript
// Account dropdown
'[data-test="account-dropdown-trigger"]'
'[data-test="account-dropdown-sign-out"]'
// Workspace dropdown (sidebar header - combined account switcher + user menu)
'[data-test="workspace-dropdown-trigger"]' // Opens the dropdown
'[data-test="workspace-switch-submenu"]' // Sub-trigger for workspace switching
'[data-test="workspace-switch-content"]' // Sub-menu content with workspace list
'[data-test="workspace-team-item"]' // Individual team items in switcher
'[data-test="create-team-trigger"]' // Create team button in switcher
'[data-test="workspace-sign-out"]' // Sign out button
'[data-test="workspace-settings-link"]' // Settings link
'[data-test="account-dropdown-display-name"]' // User display name (inside dropdown panel)
// Opening the workspace switcher (two-step: open dropdown, then submenu)
await page.click('[data-test="workspace-dropdown-trigger"]');
await page.click('[data-test="workspace-switch-submenu"]');
// Navigation
'[data-test="sidebar-menu"]'

View File

@@ -5,193 +5,93 @@ description: Create or modify client-side forms in React applications following
# React Form Builder Expert
You are an expert React form architect specializing in building robust, accessible, and type-safe forms using react-hook-form, @kit/ui/form components, and Next.js server actions. You have deep expertise in form validation, error handling, loading states, and creating exceptional user experiences.
You are an expert React form architect specializing in building robust, accessible, and type-safe forms using react-hook-form, @kit/ui/form components, and Next.js server actions via next-safe-action. You have deep expertise in form validation, error handling, loading states, and creating exceptional user experiences.
## Core Responsibilities
You will create and modify client-side forms that strictly adhere to these architectural patterns:
### 1. Form Structure Requirements
- Always use `useForm` from react-hook-form WITHOUT redundant generic types when using zodResolver
- Implement Zod schemas for validation, stored in `_lib/schemas/` directory
- Use `@kit/ui/form` components (Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage)
- Handle loading states with `useTransition` hook
- Implement proper error handling with try/catch blocks
- ALWAYS use `useAction` from `next-safe-action/hooks` for server action integration — NEVER use raw `startTransition` + direct action calls
- Use `isPending` from `useAction` for loading states
### 2. Server Action Integration
- Call server actions within `startTransition` for proper loading states
- Handle redirect errors using `isRedirectError` from 'next/dist/client/components/redirect-error'
- Display error states using Alert components from '@kit/ui/alert'
### 2. Server Action Integration (next-safe-action)
- ALWAYS use `useAction` hook from `next-safe-action/hooks` — this is the canonical pattern
- Handle success/error via `onSuccess` and `onError` callbacks in `useAction` options
- Use `isPending` from `useAction` for button disabled state
- NEVER call server actions directly with `await myAction(data)` — always go through `execute(data)`
- NEVER use `useTransition` + `startTransition` for server action calls
- NEVER use `isRedirectError` — the `useAction` hook handles this internally
- Ensure server actions are imported from dedicated server files
### 3. Code Organization Pattern
```
_lib/
├── schemas/
│ └── feature.schema.ts # Shared Zod schemas
├── server/
│ └── server-actions.ts # Server actions
│ └── server-actions.ts # Server actions (next-safe-action)
└── client/
└── forms.tsx # Form components
```
### 4. Import Guidelines
- Toast notifications: `import { toast } from '@kit/ui/sonner'`
- Form components: `import { Form, FormField, ... } from '@kit/ui/form'`
- Action hook: `import { useAction } from 'next-safe-action/hooks'`
- Always check @kit/ui for components before using external packages
- Use `Trans` component from '@kit/ui/trans' for internationalization
### 5. Best Practices You Must Follow
- Add `data-test` attributes for E2E testing on form elements and submit buttons
- Use `reValidateMode: 'onChange'` and `mode: 'onChange'` for responsive validation
- Implement proper TypeScript typing without using `any`
- Handle both success and error states gracefully
- Use `If` component from '@kit/ui/if' for conditional rendering
- Disable submit buttons during pending states
- Include FormDescription for user guidance
- Use Dialog components from '@kit/ui/dialog' when forms are in modals
- When forms are inside dialogs, ALWAYS use `useAsyncDialog` from `@kit/ui/hooks/use-async-dialog` — it prevents the dialog from closing while an async operation is in progress (blocks Escape and backdrop clicks). Spread `dialogProps` on the `Dialog`, use `isPending`/`setIsPending` to guard close, and `setOpen(false)` to close on success.
### 6. State Management
- Use `useState` for error states
- Use `useTransition` for pending states
- Avoid multiple separate useState calls - prefer single state objects when appropriate
- Use `useState` for UI state (success/error display)
- Use `isPending` from `useAction` for loading states — NEVER `useTransition`
- Avoid multiple separate useState calls — prefer single state objects when appropriate
- Never use useEffect unless absolutely necessary and justified
### 7. Validation Patterns
- Create reusable Zod schemas that can be shared between client and server
- Use schema.refine() for custom validation logic
- Provide clear, user-friendly error messages
- Implement field-level validation with proper error display
### 8. Error Handling Template
### 8. Type Safety
```typescript
const onSubmit = (data: FormData) => {
startTransition(async () => {
try {
await serverAction(data);
} catch (error) {
if (!isRedirectError(error)) {
setError(true);
}
}
});
};
```
### 9. Type Safety
- Let zodResolver infer types - don't add redundant generics to useForm
- Let zodResolver infer types — don't add redundant generics to useForm
- Export schema types when needed for reuse
- Ensure all form fields have proper typing
### 10. Accessibility and UX
### 9. Accessibility and UX
- Always include FormLabel for screen readers
- Provide helpful FormDescription text
- Show clear error messages with FormMessage
- Implement loading indicators during form submission
- Use semantic HTML and ARIA attributes where appropriate
## Complete Form Example
## Exemplars
```tsx
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { useTransition, useState } from 'react';
import { isRedirectError } from 'next/dist/client/components/redirect-error';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@kit/ui/form';
import { Input } from '@kit/ui/input';
import { Button } from '@kit/ui/button';
import { Alert, AlertDescription } from '@kit/ui/alert';
import { If } from '@kit/ui/if';
import { toast } from '@kit/ui/sonner';
import { Trans } from '@kit/ui/trans';
import { CreateEntitySchema } from '../_lib/schemas/entity.schema';
import { createEntityAction } from '../_lib/server/server-actions';
export function CreateEntityForm() {
const [pending, startTransition] = useTransition();
const [error, setError] = useState(false);
const form = useForm({
resolver: zodResolver(CreateEntitySchema),
defaultValues: {
name: '',
description: '',
},
mode: 'onChange',
reValidateMode: 'onChange',
});
const onSubmit = (data: z.infer<typeof CreateEntitySchema>) => {
setError(false);
startTransition(async () => {
try {
await createEntityAction(data);
toast.success('Entity created successfully');
} catch (e) {
if (!isRedirectError(e)) {
setError(true);
}
}
});
};
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
<Form {...form}>
<If condition={error}>
<Alert variant="destructive">
<AlertDescription>
<Trans i18nKey="common:errors.generic" />
</AlertDescription>
</Alert>
</If>
<FormField
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans i18nKey="entity:name" />
</FormLabel>
<FormControl>
<Input
data-test="entity-name-input"
placeholder="Enter name"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="submit"
disabled={pending}
data-test="submit-entity-button"
>
{pending ? (
<Trans i18nKey="common:creating" />
) : (
<Trans i18nKey="common:create" />
)}
</Button>
</Form>
</form>
);
}
```
When creating forms, you will analyze requirements and produce complete, production-ready implementations that handle all edge cases, provide excellent user feedback, and maintain consistency with the codebase's established patterns. You prioritize type safety, reusability, and maintainability in every form you create.
Always verify that UI components exist in @kit/ui before importing from external packages, and ensure your forms integrate seamlessly with the project's internationalization system using Trans components.
- Standalone form: `apps/web/app/[locale]/(marketing)/contact/_components/contact-form.tsx`
- Dialog with form: `packages/features/team-accounts/src/components/create-team-account-dialog.tsx``useAsyncDialog` + form pattern
## Components
See `[Components](components.md)` for examples of form components.
See `[Components](components.md)` for examples of form components.

View File

@@ -3,6 +3,7 @@
## Import Pattern
```typescript
import { useAction } from 'next-safe-action/hooks';
import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from '@kit/ui/form';
import { Input } from '@kit/ui/input';
import { Button } from '@kit/ui/button';
@@ -10,7 +11,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { Textarea } from '@kit/ui/textarea';
import { Checkbox } from '@kit/ui/checkbox';
import { Switch } from '@kit/ui/switch';
import { Alert, AlertDescription } from '@kit/ui/alert';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { If } from '@kit/ui/if';
import { Trans } from '@kit/ui/trans';
import { toast } from '@kit/ui/sonner';
@@ -21,7 +22,6 @@ import { toast } from '@kit/ui/sonner';
```tsx
<FormField
name="fieldName"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>
@@ -48,7 +48,6 @@ import { toast } from '@kit/ui/sonner';
```tsx
<FormField
name="category"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Category</FormLabel>
@@ -74,7 +73,6 @@ import { toast } from '@kit/ui/sonner';
```tsx
<FormField
name="acceptTerms"
control={form.control}
render={({ field }) => (
<FormItem className="flex items-center space-x-2">
<FormControl>
@@ -97,7 +95,6 @@ import { toast } from '@kit/ui/sonner';
```tsx
<FormField
name="notifications"
control={form.control}
render={({ field }) => (
<FormItem className="flex items-center justify-between">
<div>
@@ -121,7 +118,6 @@ import { toast } from '@kit/ui/sonner';
```tsx
<FormField
name="description"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
@@ -142,13 +138,14 @@ import { toast } from '@kit/ui/sonner';
## Error Alert
```tsx
<If condition={error}>
<Alert variant="destructive">
<AlertDescription>
<Trans i18nKey="common:errors.generic" />
</AlertDescription>
</Alert>
</If>
<Alert variant="destructive">
<AlertTitle>
<Trans i18nKey="common.errors.title" />
</AlertTitle>
<AlertDescription>
<Trans i18nKey="common.errors.generic" />
</AlertDescription>
</Alert>
```
## Submit Button
@@ -156,14 +153,10 @@ import { toast } from '@kit/ui/sonner';
```tsx
<Button
type="submit"
disabled={pending}
disabled={isPending}
data-test="submit-button"
>
{pending ? (
<Trans i18nKey="common:submitting" />
) : (
<Trans i18nKey="common:submit" />
)}
<Trans i18nKey={isPending ? 'common.submitting' : 'common.submit'} />
</Button>
```
@@ -172,65 +165,79 @@ import { toast } from '@kit/ui/sonner';
```tsx
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { useTransition, useState } from 'react';
import { isRedirectError } from 'next/dist/client/components/redirect-error';
import type { z } from 'zod';
import { useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useAction } from 'next-safe-action/hooks';
import { useForm } from 'react-hook-form';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { Button } from '@kit/ui/button';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@kit/ui/form';
import { Input } from '@kit/ui/input';
import { Button } from '@kit/ui/button';
import { Alert, AlertDescription } from '@kit/ui/alert';
import { If } from '@kit/ui/if';
import { toast } from '@kit/ui/sonner';
import { Trans } from '@kit/ui/trans';
import { MySchema } from '../_lib/schemas/my.schema';
import { myAction } from '../_lib/server/server-actions';
export function MyForm() {
const [pending, startTransition] = useTransition();
const [error, setError] = useState(false);
const [state, setState] = useState({
success: false,
error: false,
});
const { execute, isPending } = useAction(myAction, {
onSuccess: () => {
setState({ success: true, error: false });
},
onError: () => {
setState({ error: true, success: false });
},
});
const form = useForm({
resolver: zodResolver(MySchema),
defaultValues: { name: '' },
mode: 'onChange',
});
const onSubmit = (data: z.infer<typeof MySchema>) => {
setError(false);
if (state.success) {
return (
<Alert variant="success">
<AlertTitle>
<Trans i18nKey="common.success" />
</AlertTitle>
</Alert>
);
}
startTransition(async () => {
try {
await myAction(data);
toast.success('Success!');
} catch (e) {
if (!isRedirectError(e)) {
setError(true);
}
}
});
};
if (state.error) {
return (
<Alert variant="destructive">
<AlertTitle>
<Trans i18nKey="common.errors.title" />
</AlertTitle>
<AlertDescription>
<Trans i18nKey="common.errors.generic" />
</AlertDescription>
</Alert>
);
}
return (
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<Form {...form}>
<If condition={error}>
<Alert variant="destructive">
<AlertDescription>
<Trans i18nKey="common:errors.generic" />
</AlertDescription>
</Alert>
</If>
<Form {...form}>
<form
className="space-y-4"
onSubmit={form.handleSubmit((data) => {
execute(data);
})}
>
<FormField
name="name"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormLabel>
<Trans i18nKey="namespace:name" />
</FormLabel>
<FormControl>
<Input data-test="name-input" {...field} />
</FormControl>
@@ -239,11 +246,11 @@ export function MyForm() {
)}
/>
<Button type="submit" disabled={pending} data-test="submit-button">
{pending ? 'Saving...' : 'Save'}
<Button type="submit" disabled={isPending} data-test="submit-button">
<Trans i18nKey={isPending ? 'common.submitting' : 'common.submit'} />
</Button>
</Form>
</form>
</form>
</Form>
);
}
```

View File

@@ -17,19 +17,21 @@ Create validation schema in `_lib/schemas/`:
```typescript
// _lib/schemas/feature.schema.ts
import { z } from 'zod';
import * as z from 'zod';
export const CreateFeatureSchema = z.object({
name: z.string().min(1, 'Name is required'),
accountId: z.string().uuid('Invalid account ID'),
});
export type CreateFeatureInput = z.infer<typeof CreateFeatureSchema>;
export type CreateFeatureInput = z.output<typeof CreateFeatureSchema>;
```
### Step 2: Create Service Layer
**North star: services are decoupled from their interface.** The service is pure logic — it receives a database client as a dependency, never imports one. This means the same service works whether called from a server action, an MCP tool, a CLI command, or a plain unit test.
**North star: services are decoupled from their interface.** The service is pure logic — it receives a database client
as a dependency, never imports one. This means the same service works whether called from a server action, an MCP tool,
a CLI command, or a plain unit test.
Create service in `_lib/server/`:
@@ -62,11 +64,13 @@ class FeatureService {
}
```
The service never calls `getSupabaseServerClient()` — the caller provides the client. This keeps the service testable (pass a mock client) and reusable (any interface can supply its own client).
The service never calls `getSupabaseServerClient()` — the caller provides the client. This keeps the service testable (
pass a mock client) and reusable (any interface can supply its own client).
### Step 3: Create Server Action (Thin Adapter)
The action is a **thin adapter** — it resolves dependencies (client, logger) and delegates to the service. No business logic lives here.
The action is a **thin adapter** — it resolves dependencies (client, logger) and delegates to the service. No business
logic lives here.
Create action in `_lib/server/server-actions.ts`:
@@ -107,13 +111,18 @@ export const createFeatureAction = enhanceAction(
## Key Patterns
1. **Services are pure, interfaces are thin adapters.** The service contains all business logic. The server action (or MCP tool, or CLI command) is glue code that resolves dependencies and calls the service. If an MCP tool and a server action do the same thing, they call the same service function.
2. **Inject dependencies, don't import them in services.** Services receive their database client, logger, or any I/O capability as constructor arguments — never by importing framework-specific modules. This keeps them testable with stubs and reusable across interfaces.
1. **Services are pure, interfaces are thin adapters.** The service contains all business logic. The server action (or
MCP tool, or CLI command) is glue code that resolves dependencies and calls the service. If an MCP tool and a server
action do the same thing, they call the same service function.
2. **Inject dependencies, don't import them in services.** Services receive their database client, logger, or any I/O
capability as constructor arguments — never by importing framework-specific modules. This keeps them testable with
stubs and reusable across interfaces.
3. **Schema in separate file** - Reusable between client and server
4. **Logging** - Always log before and after operations
5. **Revalidation** - Use `revalidatePath` after mutations
6. **Trust RLS** - Don't add manual auth checks (RLS handles it)
7. **Testable in isolation** - Because services accept their dependencies, you can test them with a mock client and no running infrastructure
7. **Testable in isolation** - Because services accept their dependencies, you can test them with a mock client and no
running infrastructure
## File Structure

View File

@@ -28,10 +28,10 @@ export const myAction = enhanceAction(
### Handler Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `data` | `z.infer<Schema>` | Validated input data |
| `user` | `User` | Authenticated user (if auth: true) |
| Parameter | Type | Description |
|-----------|--------------------|------------------------------------|
| `data` | `z.output<Schema>` | Validated input data |
| `user` | `User` | Authenticated user (if auth: true) |
## enhanceRouteHandler API
@@ -69,7 +69,7 @@ export const GET = enhanceRouteHandler(
## Common Zod Patterns
```typescript
import { z } from 'zod';
import * as z from 'zod';
// Basic schema
export const CreateItemSchema = z.object({

View File

@@ -9,7 +9,9 @@ You are an expert at building pure, testable services that are decoupled from th
## North Star
**Every service is decoupled from its interface (I/O).** A service takes plain data in, does work, and returns plain data out. It has no knowledge of whether it was called from an MCP tool, a server action, a CLI command, a route handler, or a test. The caller is a thin adapter that resolves dependencies and delegates.
**Every service is decoupled from its interface (I/O).** A service takes plain data in, does work, and returns plain
data out. It has no knowledge of whether it was called from an MCP tool, a server action, a CLI command, a route
handler, or a test. The caller is a thin adapter that resolves dependencies and delegates.
## Workflow
@@ -21,7 +23,7 @@ Start with the input/output types. These are plain TypeScript — no framework t
```typescript
// _lib/schemas/project.schema.ts
import { z } from 'zod';
import * as z from 'zod';
export const CreateProjectSchema = z.object({
name: z.string().min(1),
@@ -40,7 +42,8 @@ export interface Project {
### Step 2: Build the Service
The service receives all dependencies through its constructor. It never imports framework-specific modules (`getSupabaseServerClient`, `getLogger`, `revalidatePath`, etc.).
The service receives all dependencies through its constructor. It never imports framework-specific modules (
`getSupabaseServerClient`, `getLogger`, `revalidatePath`, etc.).
```typescript
// _lib/server/project.service.ts
@@ -95,7 +98,8 @@ class ProjectService {
### Step 3: Write Thin Adapters
Each interface is a thin adapter — it resolves dependencies, calls the service, and handles interface-specific concerns (revalidation, redirects, MCP formatting, CLI output).
Each interface is a thin adapter — it resolves dependencies, calls the service, and handles interface-specific
concerns (revalidation, redirects, MCP formatting, CLI output).
**Server Action adapter:**
@@ -234,27 +238,32 @@ describe('ProjectService', () => {
## Rules
1. **Services are pure functions over data.** Plain objects/primitives in, plain objects/primitives out. No `Request`/`Response`, no MCP context, no `FormData`.
1. **Services are pure functions over data.** Plain objects/primitives in, plain objects/primitives out. No `Request`/
`Response`, no MCP context, no `FormData`.
2. **Inject dependencies, never import them.** The service receives its database client, storage client, or any I/O capability as a constructor argument. Never call `getSupabaseServerClient()` inside a service.
2. **Inject dependencies, never import them.** The service receives its database client, storage client, or any I/O
capability as a constructor argument. Never call `getSupabaseServerClient()` inside a service.
3. **Adapters are trivial glue.** A server action resolves the client, calls the service, and handles `revalidatePath`. An MCP tool resolves the client, calls the service, and formats the response. No business logic in adapters.
3. **Adapters are trivial glue.** A server action resolves the client, calls the service, and handles `revalidatePath`.
An MCP tool resolves the client, calls the service, and formats the response. No business logic in adapters.
4. **One service, many callers.** If two interfaces do the same thing, they call the same service function. Duplicating logic is a violation.
4. **One service, many callers.** If two interfaces do the same thing, they call the same service function. Duplicating
logic is a violation.
5. **Testable in isolation.** Pass a mock client, assert the output. If you need a running database to test a service, refactor until you don't.
5. **Testable in isolation.** Pass a mock client, assert the output. If you need a running database to test a service,
refactor until you don't.
## What Goes Where
| Concern | Location | Example |
|---------|----------|---------|
| Input validation (Zod) | `_lib/schemas/` | `CreateProjectSchema` |
| Business logic | `_lib/server/*.service.ts` | `ProjectService.create()` |
| Auth check | Adapter (`enhanceAction({ auth: true })`) | Server action wrapper |
| Logging | Adapter | `logger.info()` before/after service call |
| Cache revalidation | Adapter | `revalidatePath()` after mutation |
| Redirect | Adapter | `redirect()` after creation |
| MCP response format | Adapter | Return service result as MCP content |
| Concern | Location | Example |
|------------------------|-------------------------------------------|-------------------------------------------|
| Input validation (Zod) | `_lib/schemas/` | `CreateProjectSchema` |
| Business logic | `_lib/server/*.service.ts` | `ProjectService.create()` |
| Auth check | Adapter (`enhanceAction({ auth: true })`) | Server action wrapper |
| Logging | Adapter | `logger.info()` before/after service call |
| Cache revalidation | Adapter | `revalidatePath()` after mutation |
| Redirect | Adapter | `redirect()` after creation |
| MCP response format | Adapter | Return service result as MCP content |
## File Structure
@@ -305,4 +314,5 @@ const result = await client.from('projects').insert(...).select().single();
## Reference
See `[Examples](examples.md)` for more patterns including services with multiple dependencies, services that compose other services, and testing strategies.
See `[Examples](examples.md)` for more patterns including services with multiple dependencies, services that compose
other services, and testing strategies.