* refactor: consolidate AGENTS.md and CLAUDE.md files, update tech stack and architecture details - Merged content from CLAUDE.md into AGENTS.md for better organization. - Updated tech stack section to reflect the current technologies used, including Next.js, Supabase, and Tailwind CSS. - Enhanced monorepo structure documentation with detailed directory purposes. - Streamlined multi-tenant architecture explanation and essential commands. - Added key patterns for naming conventions and server actions. - Removed outdated agent files related to Playwright and PostgreSQL, ensuring a cleaner codebase. - Bumped version to 2.23.7 to reflect changes.
180 lines
4.0 KiB
Markdown
180 lines
4.0 KiB
Markdown
# Server Action Reference
|
|
|
|
## enhanceAction API
|
|
|
|
```typescript
|
|
import { enhanceAction } from '@kit/next/actions';
|
|
|
|
export const myAction = enhanceAction(
|
|
async function (data, user) {
|
|
// data: validated input (typed from schema)
|
|
// user: authenticated user object (if auth: true)
|
|
|
|
return { success: true, data: result };
|
|
},
|
|
{
|
|
auth: true, // Require authentication (default: false)
|
|
schema: ZodSchema, // Zod schema for validation (optional)
|
|
},
|
|
);
|
|
```
|
|
|
|
### Options
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `auth` | `boolean` | `false` | Require authenticated user |
|
|
| `schema` | `ZodSchema` | - | Zod schema for input validation |
|
|
|
|
### Handler Parameters
|
|
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `data` | `z.infer<Schema>` | Validated input data |
|
|
| `user` | `User` | Authenticated user (if auth: true) |
|
|
|
|
## enhanceRouteHandler API
|
|
|
|
```typescript
|
|
import { enhanceRouteHandler } from '@kit/next/routes';
|
|
import { NextResponse } from 'next/server';
|
|
|
|
export const POST = enhanceRouteHandler(
|
|
async function ({ body, user, request }) {
|
|
// body: validated request body
|
|
// user: authenticated user (if auth: true)
|
|
// request: original NextRequest
|
|
|
|
return NextResponse.json({ success: true });
|
|
},
|
|
{
|
|
auth: true,
|
|
schema: ZodSchema,
|
|
},
|
|
);
|
|
|
|
export const GET = enhanceRouteHandler(
|
|
async function ({ user, request }) {
|
|
const url = new URL(request.url);
|
|
const param = url.searchParams.get('param');
|
|
|
|
return NextResponse.json({ data: result });
|
|
},
|
|
{
|
|
auth: true,
|
|
},
|
|
);
|
|
```
|
|
|
|
## Common Zod Patterns
|
|
|
|
```typescript
|
|
import { z } from 'zod';
|
|
|
|
// Basic schema
|
|
export const CreateItemSchema = z.object({
|
|
name: z.string().min(1, 'Name is required'),
|
|
description: z.string().optional(),
|
|
accountId: z.string().uuid('Invalid account ID'),
|
|
});
|
|
|
|
// With transforms
|
|
export const SearchSchema = z.object({
|
|
query: z.string().trim().min(1),
|
|
page: z.coerce.number().int().positive().default(1),
|
|
limit: z.coerce.number().int().min(1).max(100).default(10),
|
|
});
|
|
|
|
// With refinements
|
|
export const DateRangeSchema = z.object({
|
|
startDate: z.coerce.date(),
|
|
endDate: z.coerce.date(),
|
|
}).refine(
|
|
(data) => data.endDate > data.startDate,
|
|
{ message: 'End date must be after start date' }
|
|
);
|
|
|
|
// Enum values
|
|
export const StatusSchema = z.object({
|
|
status: z.enum(['active', 'inactive', 'pending']),
|
|
});
|
|
```
|
|
|
|
## Revalidation
|
|
|
|
```typescript
|
|
import { revalidatePath } from 'next/cache';
|
|
import { revalidateTag } from 'next/cache';
|
|
|
|
// Revalidate specific path
|
|
revalidatePath('/home/[account]/items');
|
|
|
|
// Revalidate with dynamic segment
|
|
revalidatePath(`/home/${accountSlug}/items`);
|
|
|
|
// Revalidate by tag
|
|
revalidateTag('items');
|
|
```
|
|
|
|
## Redirect
|
|
|
|
```typescript
|
|
import { redirect } from 'next/navigation';
|
|
|
|
// Redirect after action
|
|
redirect('/success');
|
|
|
|
// Redirect with dynamic path
|
|
redirect(`/home/${accountSlug}/items/${itemId}`);
|
|
```
|
|
|
|
## Logging
|
|
|
|
```typescript
|
|
import { getLogger } from '@kit/shared/logger';
|
|
|
|
const logger = await getLogger();
|
|
|
|
// Context object for all logs
|
|
const ctx = {
|
|
name: 'action-name',
|
|
userId: user.id,
|
|
accountId: data.accountId,
|
|
};
|
|
|
|
// Log levels
|
|
logger.info(ctx, 'Starting operation');
|
|
logger.warn({ ...ctx, warning: 'details' }, 'Warning message');
|
|
logger.error({ ...ctx, error }, 'Operation failed');
|
|
```
|
|
|
|
## Supabase Clients
|
|
|
|
```typescript
|
|
// Standard client (RLS enforced)
|
|
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
|
const client = getSupabaseServerClient();
|
|
|
|
// Admin client (bypasses RLS - use sparingly)
|
|
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
|
const adminClient = getSupabaseServerAdminClient();
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
```typescript
|
|
import { isRedirectError } from 'next/dist/client/components/redirect-error';
|
|
|
|
try {
|
|
await operation();
|
|
redirect('/success');
|
|
} catch (error) {
|
|
if (!isRedirectError(error)) {
|
|
// Handle actual error
|
|
logger.error({ error }, 'Operation failed');
|
|
throw error;
|
|
}
|
|
throw error; // Re-throw redirect
|
|
}
|
|
```
|