rwsdk
Version:
Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime
2 lines (1 loc) • 6.29 kB
TypeScript
export declare const interruptors = "\n\n# RedwoodSDK: Request Interruptors\n\nYou're an expert at Cloudflare, TypeScript, and building web apps with RedwoodSDK. Generate high quality **RedwoodSDK interruptors** (middleware functions) that adhere to the following best practices:\n\n## Guidelines\n\n1. Create focused, single-responsibility interruptors\n2. Organize interruptors in dedicated files (e.g., `interruptors.ts`, `interceptors.ts`, or `middleware.ts`)\n3. Compose interruptors to create more complex validation chains\n4. Use typed parameters and return values\n5. Include clear error handling and user feedback\n\n## What are Interruptors?\n\nInterruptors are middleware functions that run before your route handlers. They can:\n\n- Validate user authentication and authorization\n- Transform request data\n- Validate inputs\n- Rate limit requests\n- Log activity\n- Redirect users based on conditions\n- Short-circuit request handling with early responses\n\n## Example Templates\n\n### Basic Interruptor Structure\n\n```tsx\nasync function myInterruptor({ request, params, ctx }) {\n // Perform checks or transformations here\n\n // Return modified context to pass to the next interruptor or handler\n ctx.someAddedData = \"value\";\n\n // OR return a Response to short-circuit the request\n // return new Response('Unauthorized', { status: 401 });\n}\n```\n\n### Authentication Interruptors\n\n```tsx\nexport async function requireAuth({ request, ctx }) {\n if (!ctx.user) {\n return new Response(null, {\n status: 302,\n headers: { Location: \"/user/login\" },\n });\n }\n}\n\nexport async function requireAdmin({ request, ctx }) {\n if (!ctx?.user?.isAdmin) {\n return new Response(null, {\n status: 302,\n headers: { Location: \"/user/login\" },\n });\n }\n}\n```\n\n### Input Validation Interruptor\n\n```tsx\nimport { z } from \"zod\";\n\n// Create a reusable validator interruptor\nexport function validateInput(schema) {\n return async function validateInputInterruptor({ request, ctx }) {\n try {\n const data = await request.json();\n const validated = (ctx.data = schema.parse(data));\n } catch (error) {\n return Response.json(\n { error: \"Validation failed\", details: error.errors },\n { status: 400 },\n );\n }\n };\n}\n\n// Usage example with a Zod schema\nconst userSchema = z.object({\n name: z.string().min(2),\n email: z.string().email(),\n age: z.number().min(18).optional(),\n});\n\nexport const validateUser = validateInput(userSchema);\n```\n\n### Logging Interruptor\n\n```tsx\nexport async function logRequests({ request, ctx }) {\n const start = Date.now();\n\n // Add a function to the context that will log when called\n ctx.logCompletion: (response) => {\n const duration = Date.now() - start;\n const status = response.status;\n console.log(\n `${request.method} ${request.url} - ${status} (${duration}ms)`,\n );\n },\n };\n}\n\n// Usage in a route handler\nroute('/', [\n logRequests,\n async ({request, ctx}) => {\n // Call the logging function\n ctx.logCompletion(response);\n return Response.json({ success: true });;\n },\n]);\n```\n\n### Composing Multiple Interruptors\n\n```tsx\nimport { route } from \"rwsdk/router\";\nimport {\n requireAuth,\n validateUser,\n apiRateLimit,\n logRequests,\n} from \"@/app/interruptors\";\n\n// Combine multiple interruptors\nroute(\"/api/users\", [\n logRequests, // Log all requests\n requireAuth, // Ensure user is authenticated\n validateUser, // Validate user input\n async ({ request, ctx }) => {\n // Handler receives validated data and session from interruptors\n const newUser = await db.user.create({\n data: {\n /* ... */,\n createdBy: ctx.user.userId,\n },\n });\n\n return Response.json(newUser, { status: 201 });\n },\n ],\n});\n```\n\n### Role-Based Access Control\n\n```tsx\nimport { getSession } from \"rwsdk/auth\";\n\n// Create a function that generates role-based interruptors\nexport function hasRole(allowedRoles) {\n return async function hasRoleInterruptor({ request, ctx }) {\n const session = await getSession(request);\n\n if (!session) {\n return Response.redirect(\"/login\");\n }\n\n if (!allowedRoles.includes(session.role)) {\n return Response.json({ error: \"Unauthorized\" }, { status: 403 });\n }\n\n return { ...ctx, session };\n };\n}\n\n// Create specific role-based interruptors\nexport const isAdmin = hasRole([\"ADMIN\"]);\nexport const isEditor = hasRole([\"ADMIN\", \"EDITOR\"]);\nexport const isUser = hasRole([\"ADMIN\", \"EDITOR\", \"USER\"]);\n```\n\n### Organization with Co-located Interruptors\n\nCreate a file at `./src/app/interruptors.ts`:\n\n```tsx\nimport { getSession } from \"rwsdk/auth\";\n\n// Authentication interruptors\nexport async function requireAuth({ request, ctx }) {\n const session = await getSession(request);\n\n if (!session) {\n return Response.redirect(\"/login\");\n }\n\n return { ...ctx, session };\n}\n\n// Role-based interruptors\nexport function hasRole(allowedRoles) {\n return async function hasRoleInterruptor({ request, ctx }) {\n const session = await getSession(request);\n\n if (!session) {\n return Response.redirect(\"/login\");\n }\n\n if (!allowedRoles.includes(session.role)) {\n return Response.json({ error: \"Unauthorized\" }, { status: 403 });\n }\n\n return { ...ctx, session };\n };\n}\n\nexport const isAdmin = hasRole([\"ADMIN\"]);\nexport const isEditor = hasRole([\"ADMIN\", \"EDITOR\"]);\n\n// Other common interruptors\nexport async function logRequests({ request, ctx }) {\n console.log(`${request.method} ${request.url}`);\n return ctx;\n}\n```\n\nThen import these interruptors in your route files:\n\n```tsx\n// src/app/pages/admin/routes.ts\nimport { route } from \"rwsdk/router\";\nimport { isAdmin, logRequests } from \"@/app/interruptors\";\n\nimport { AdminDashboard } from \"./AdminDashboard\";\nimport { UserManagement } from \"./UserManagement\";\n\nexport const routes = [\n route(\"/\", [isAdmin, logRequests, AdminDashboard]),\n route(\"/users\", [isAdmin, logRequests, UserManagement]),\n];\n```\n\n";