vite-plugin-server-actions 0.1.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,181 +4,682 @@
4
4
  [![Downloads](https://img.shields.io/npm/dm/vite-plugin-server-actions.svg?style=flat)](https://www.npmjs.com/package/vite-plugin-server-actions)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
- > 🚧 **Experimental:** This is currently a proof of concept. Use at your own risk.
7
+ **Write server functions. Call them from the client. That's it.**
8
8
 
9
- **Vite Server Actions** is a Vite plugin that enables you to create server-side functions and call them from your
10
- client-side code as if they were local functions.
9
+ Vite Server Actions brings the simplicity of server-side development to your Vite applications. Import server functions into your client code and call them directly - no API routes, no HTTP handling, no boilerplate.
11
10
 
12
- ## ✨ Features
11
+ ```javascript
12
+ // server/db.server.js
13
+ export async function getUsers() {
14
+ return await database.users.findAll();
15
+ }
16
+
17
+ // App.vue
18
+ import { getUsers } from "./server/db.server.js";
19
+
20
+ const users = await getUsers(); // Just call it!
21
+ ```
22
+
23
+ ## 🚀 Why Vite Server Actions?
24
+
25
+ - **Zero API Boilerplate** - No need to define routes, handle HTTP methods, or parse request bodies
26
+ - **Type Safety** - Full TypeScript support with proper type inference across client-server boundary
27
+ - **Built-in Validation** - Automatic request validation using Zod schemas
28
+ - **Auto Documentation** - OpenAPI spec and Swagger UI generated from your code
29
+ - **Production Ready** - Builds to a standard Node.js Express server
30
+ - **Developer Experience** - Hot reload, middleware support, and helpful error messages
13
31
 
14
- - 🔄 Automatic API endpoint creation for server functions (e.g. `POST /api/todos/addTodo`)
15
- - 🔗 Seamless client-side proxies for easy usage (e.g. `import {addTodo} from './server/todos.server.js'`)
16
- - 🛠 Support for both development and production environments ( `vite build` )
17
- - 🚀 Zero-config setup for instant productivity
32
+ ## Core Features
33
+
34
+ - 🔗 **Seamless Imports** - Import server functions like any other module
35
+ - 🛡️ **Secure by Default** - Server code never exposed to client
36
+ - ✅ **Request Validation** - Attach Zod schemas for automatic validation
37
+ - 📖 **API Documentation** - Auto-generated OpenAPI specs and Swagger UI
38
+ - 🔌 **Middleware Support** - Add authentication, logging, CORS, etc.
39
+ - 🎯 **Flexible Routing** - Customize how file paths map to API endpoints
40
+ - 📦 **Production Optimized** - Builds to efficient Express server with all features
18
41
 
19
42
  ## 🚀 Quick Start
20
43
 
21
- 1. Install the plugin:
44
+ ### 1. Install
22
45
 
23
46
  ```bash
24
- # Install using npm
25
47
  npm install vite-plugin-server-actions
26
-
27
- # Or using yarn
28
- yarn add vite-plugin-server-actions
29
48
  ```
30
49
 
31
- 2. Add it to your `vite.config.js` file ([example](examples/todo-app/vite.config.js)):
50
+ ### 2. Configure Vite
32
51
 
33
52
  ```javascript
34
53
  // vite.config.js
35
54
  import { defineConfig } from "vite";
36
-
37
- // Import the plugin
38
55
  import serverActions from "vite-plugin-server-actions";
39
56
 
40
57
  export default defineConfig({
41
58
  plugins: [
42
- // Add the plugin
43
- serverActions(),
59
+ serverActions(), // That's it! Zero config needed
44
60
  ],
45
61
  });
46
62
  ```
47
63
 
48
- 2. Create a server action file (e.g., `todo.server.js`):
64
+ ### 3. Create a Server Function
49
65
 
50
- You can put it anywhere in your project, but it has to end with `.server.js`.
66
+ Any file ending with `.server.js` becomes a server module:
51
67
 
52
68
  ```javascript
53
- // ex: src/actions/todo.server.js
54
- import fs from "fs";
55
- import path from "path";
69
+ // actions/todos.server.js
70
+ import { db } from "./database";
56
71
 
57
- const TODO_FILE_PATH = path.join(process.cwd(), "list-of-todos.json");
72
+ export async function getTodos(userId) {
73
+ // This runs on the server with full Node.js access
74
+ return await db.todos.findMany({ where: { userId } });
75
+ }
58
76
 
59
- export async function deleteTodoById(id) {
60
- const data = fs.readFileSync(TODO_FILE_PATH, "utf-8");
61
- const todos = JSON.parse(data);
62
- const newTodos = todos.filter((todo) => todo.id !== id);
63
- fs.writeFileSync(TODO_FILE_PATH, JSON.stringify(newTodos, null, 2));
77
+ export async function addTodo(text, userId) {
78
+ return await db.todos.create({
79
+ data: { text, userId, completed: false },
80
+ });
64
81
  }
82
+ ```
83
+
84
+ ### 4. Use in Your Client
65
85
 
66
- export async function saveTodoToJsonFile(todo) {
67
- const data = fs.readFileSync(TODO_FILE_PATH, "utf-8");
68
- const todos = JSON.parse(data);
69
- todos.push(todo);
70
- fs.writeFileSync(TODO_FILE_PATH, JSON.stringify(todos, null, 2));
86
+ ```javascript
87
+ // App.jsx
88
+ import { getTodos, addTodo } from './actions/todos.server.js'
89
+
90
+ function TodoApp({ userId }) {
91
+ const [todos, setTodos] = useState([])
92
+
93
+ useEffect(() => {
94
+ // Just call the server function!
95
+ getTodos(userId).then(setTodos)
96
+ }, [userId])
97
+
98
+ async function handleAdd(text) {
99
+ const newTodo = await addTodo(text, userId)
100
+ setTodos([...todos, newTodo])
101
+ }
102
+
103
+ return (
104
+ // Your UI here...
105
+ )
71
106
  }
107
+ ```
108
+
109
+ That's it! The plugin automatically:
110
+
111
+ - ✅ Creates API endpoints for each function
112
+ - ✅ Handles serialization/deserialization
113
+ - ✅ Provides full TypeScript support
114
+ - ✅ Works in development and production
115
+
116
+ ## 📚 Real-World Examples
117
+
118
+ ### Database Operations
119
+
120
+ ```javascript
121
+ // server/database.server.js
122
+ import { PrismaClient } from "@prisma/client";
72
123
 
73
- export async function listTodos() {
74
- const data = fs.readFileSync(TODO_FILE_PATH, "utf-8");
75
- return JSON.parse(data);
124
+ const prisma = new PrismaClient();
125
+
126
+ export async function getUser(id) {
127
+ return await prisma.user.findUnique({
128
+ where: { id },
129
+ include: { profile: true },
130
+ });
131
+ }
132
+
133
+ export async function updateUser(id, data) {
134
+ return await prisma.user.update({
135
+ where: { id },
136
+ data,
137
+ });
138
+ }
139
+ ```
140
+
141
+ ### File Uploads
142
+
143
+ ```javascript
144
+ // server/upload.server.js
145
+ import { writeFile } from "fs/promises";
146
+ import path from "path";
147
+
148
+ export async function uploadFile(filename, base64Data) {
149
+ const buffer = Buffer.from(base64Data, "base64");
150
+ const filepath = path.join(process.cwd(), "uploads", filename);
151
+
152
+ await writeFile(filepath, buffer);
153
+ return { success: true, path: `/uploads/${filename}` };
154
+ }
155
+ ```
156
+
157
+ ### External API Integration
158
+
159
+ ```javascript
160
+ // server/weather.server.js
161
+ export async function getWeather(city) {
162
+ const response = await fetch(`https://api.weather.com/v1/current?city=${city}&key=${process.env.API_KEY}`);
163
+ return response.json();
76
164
  }
77
165
  ```
78
166
 
79
- 4. Import and use your server actions in your client-side code:
167
+ ### With Validation
80
168
 
81
- ```svelte
82
- <!-- ex: src/App.svelte -->
83
- <script>
84
- import { deleteTodoById, listTodos, saveTodoToJsonFile } from "./actions/todo.server.js";
169
+ ```javascript
170
+ // server/auth.server.js
171
+ import { z } from "zod";
172
+ import bcrypt from "bcrypt";
173
+ import { signJWT } from "./jwt";
174
+
175
+ const LoginSchema = z.object({
176
+ email: z.string().email(),
177
+ password: z.string().min(8),
178
+ });
85
179
 
86
- let todos = [];
87
- let newTodoText = "";
180
+ export async function login(credentials) {
181
+ // Validation happens automatically!
182
+ const user = await db.users.findByEmail(credentials.email);
88
183
 
89
- async function fetchTodos() {
90
- todos = await listTodos();
184
+ if (!user || !(await bcrypt.compare(credentials.password, user.passwordHash))) {
185
+ throw new Error("Invalid credentials");
91
186
  }
92
187
 
93
- async function addTodo() {
94
- await saveTodoToJsonFile({ id: Math.random(), text: newTodoText });
95
- newTodoText = "";
96
- await fetchTodos();
188
+ return { token: signJWT(user), user };
97
189
  }
98
190
 
99
- async function removeTodo(id) {
100
- await deleteTodoById(id);
101
- await fetchTodos();
191
+ // Attach schema for automatic validation
192
+ login.schema = LoginSchema;
193
+ ```
194
+
195
+ ### Complete Examples
196
+
197
+ - [Todo App with Svelte](examples/svelte-todo-app) - Full-featured todo application with validation
198
+ - [Todo App with Vue](examples/vue-todo-app) - Same todo app built with Vue 3
199
+ - [Todo App with React](examples/react-todo-app) - Same todo app built with React
200
+ - More examples coming soon for other frameworks
201
+
202
+ ## 🔍 How It Works
203
+
204
+ When you import a `.server.js` file in your client code, Vite Server Actions:
205
+
206
+ 1. **Intercepts the import** - Replaces server module imports with client proxies
207
+ 2. **Creates proxy functions** - Each exported function becomes a client-side proxy
208
+ 3. **Generates API endpoints** - Maps each function to an HTTP endpoint
209
+ 4. **Handles the transport** - Serializes arguments and return values automatically
210
+
211
+ ```javascript
212
+ // What you write:
213
+ import { getUser } from "./user.server.js";
214
+ const user = await getUser(123);
215
+
216
+ // What runs in the browser:
217
+ const user = await fetch("/api/user/getUser", {
218
+ method: "POST",
219
+ body: JSON.stringify([123]),
220
+ }).then((r) => r.json());
221
+ ```
222
+
223
+ ### Development vs Production
224
+
225
+ - **Development**: Server functions run as Express middleware in Vite's dev server
226
+ - **Production**: Builds to a standalone Express server with all your functions
227
+
228
+ ## ⚙️ Configuration
229
+
230
+ ### Common Use Cases
231
+
232
+ #### Enable Validation & API Documentation
233
+
234
+ ```javascript
235
+ serverActions({
236
+ validation: {
237
+ enabled: true,
238
+ },
239
+ openAPI: {
240
+ enabled: true,
241
+ swaggerUI: true,
242
+ },
243
+ });
244
+ ```
245
+
246
+ This gives you:
247
+
248
+ - Automatic request validation with Zod schemas
249
+ - OpenAPI spec at `/api/openapi.json`
250
+ - Interactive docs at `/api/docs`
251
+
252
+ #### Add Authentication Middleware
253
+
254
+ ```javascript
255
+ serverActions({
256
+ middleware: [
257
+ // Add auth check to all server actions
258
+ (req, res, next) => {
259
+ if (!req.headers.authorization) {
260
+ return res.status(401).json({ error: "Unauthorized" });
261
+ }
262
+ next();
263
+ },
264
+ ],
265
+ });
266
+ ```
267
+
268
+ #### Custom API Routes
269
+
270
+ ```javascript
271
+ serverActions({
272
+ apiPrefix: "/rpc", // Change from /api to /rpc
273
+ routeTransform: (filePath, functionName) => {
274
+ // users.server.js -> /rpc/users.list
275
+ const module = filePath.replace(".server.js", "");
276
+ return `${module}.${functionName}`;
277
+ },
278
+ });
279
+ ```
280
+
281
+ ### All Configuration Options
282
+
283
+ | Option | Type | Default | Description |
284
+ | ---------------- | ------------ | -------------------- | ------------------------------ |
285
+ | `apiPrefix` | `string` | `"/api"` | URL prefix for all endpoints |
286
+ | `include` | `string[]` | `["**/*.server.js"]` | Files to process |
287
+ | `exclude` | `string[]` | `[]` | Files to ignore |
288
+ | `middleware` | `Function[]` | `[]` | Express middleware stack |
289
+ | `routeTransform` | `Function` | See below | Customize URL generation |
290
+ | `validation` | `Object` | `{ enabled: false }` | Validation settings |
291
+ | `openAPI` | `Object` | `{ enabled: false }` | OpenAPI documentation settings |
292
+
293
+ #### Route Transform Options
294
+
295
+ ```javascript
296
+ import { pathUtils } from "vite-plugin-server-actions";
297
+
298
+ // Available presets:
299
+ pathUtils.createCleanRoute; // (default) auth.server.js → /api/auth/login
300
+ pathUtils.createLegacyRoute; // auth.server.js → /api/auth_server/login
301
+ pathUtils.createMinimalRoute; // auth.server.js → /api/auth.server/login
302
+ ```
303
+
304
+ #### Validation Options
305
+
306
+ | Option | Type | Default | Description |
307
+ | --------- | --------- | ------- | ------------------------------------- |
308
+ | `enabled` | `boolean` | `false` | Enable request validation |
309
+ | `adapter` | `string` | `"zod"` | Validation library adapter (only zod) |
310
+
311
+ #### OpenAPI Options
312
+
313
+ | Option | Type | Default | Description |
314
+ | ----------- | --------- | --------------------- | ----------------------------------------- |
315
+ | `enabled` | `boolean` | `false` | Enable OpenAPI generation |
316
+ | `swaggerUI` | `boolean` | `true` | Enable Swagger UI when OpenAPI is enabled |
317
+ | `info` | `Object` | See below | OpenAPI specification info |
318
+ | `docsPath` | `string` | `"/api/docs"` | Path for Swagger UI |
319
+ | `specPath` | `string` | `"/api/openapi.json"` | Path for OpenAPI JSON spec |
320
+
321
+ Default `info` object:
322
+
323
+ ```javascript
324
+ {
325
+ title: "Server Actions API",
326
+ version: "1.0.0",
327
+ description: "Auto-generated API documentation for Vite Server Actions"
102
328
  }
329
+ ```
330
+
331
+ ## 🔍 Built-in Middleware
332
+
333
+ ### Logging Middleware
334
+
335
+ Vite Server Actions includes a built-in logging middleware that provides detailed console output for debugging:
336
+
337
+ ```javascript
338
+ import serverActions, { middleware } from "vite-plugin-server-actions";
339
+
340
+ export default defineConfig({
341
+ plugins: [
342
+ serverActions({
343
+ middleware: middleware.logging,
344
+ }),
345
+ ],
346
+ });
347
+ ```
348
+
349
+ The logging middleware displays:
103
350
 
104
- fetchTodos();
105
- </script>
351
+ - 🚀 Action trigger details (module, function, endpoint)
352
+ - 📦 Formatted request body with syntax highlighting
353
+ - ✅ Response time and data
354
+ - ❌ Error responses with status codes
355
+
356
+ Example output:
106
357
 
107
- <div>
108
- <h1>Todos</h1>
109
- <ul>
110
- {#each todos as todo}
111
- <li>
112
- {todo.text}
113
- <button on:click="{() => removeTodo(todo.id)}">Remove</button>
114
- </li>
115
- {/each}
116
- </ul>
117
- <input type="text" bind:value="{newTodoText}" />
118
- <button on:click="{addTodo}">Add Todo</button>
119
- </div>
120
358
  ```
359
+ [2024-01-21T10:30:45.123Z] 🚀 Server Action Triggered
360
+ ├─ Module: src_actions_todo
361
+ ├─ Function: addTodo
362
+ ├─ Method: POST
363
+ └─ Endpoint: /api/src_actions_todo/addTodo
364
+
365
+ 📦 Request Body:
366
+ {
367
+ text: 'Buy groceries',
368
+ priority: 'high'
369
+ }
121
370
 
122
- That's it! Your server actions are now ready to use. 🎉
371
+ Response sent in 25ms
372
+ 📤 Response data:
373
+ {
374
+ id: 1,
375
+ text: 'Buy groceries',
376
+ priority: 'high',
377
+ completed: false
378
+ }
379
+ ──────────────────────────────────────────────────
380
+ ```
123
381
 
124
- ## 📝 Examples
382
+ ### Custom Middleware
125
383
 
126
- To see a real-world example of how to use Vite Server Actions, check out the TODO app example:
384
+ You can add your own Express middleware for authentication, validation, etc:
127
385
 
128
- - [TODO App Example](examples/todo-app/README.md)
386
+ ```javascript
387
+ import serverActions from "vite-plugin-server-actions";
129
388
 
130
- ## 📚 How it works
389
+ // Authentication middleware
390
+ const authMiddleware = (req, res, next) => {
391
+ const token = req.headers.authorization;
392
+ if (!token) {
393
+ return res.status(401).json({ error: "Unauthorized" });
394
+ }
395
+ // Verify token...
396
+ next();
397
+ };
398
+
399
+ // CORS middleware
400
+ const corsMiddleware = (req, res, next) => {
401
+ res.header("Access-Control-Allow-Origin", "*");
402
+ res.header("Access-Control-Allow-Methods", "POST");
403
+ res.header("Access-Control-Allow-Headers", "Content-Type");
404
+ next();
405
+ };
131
406
 
132
- **Vite Server Actions** creates an API endpoint for each server function you define. When you import a server action in
133
- your client-side code, it returns a proxy function) that sends a request to the server endpoint instead of executing the
134
- function locally.
407
+ export default defineConfig({
408
+ plugins: [
409
+ serverActions({
410
+ middleware: [corsMiddleware, authMiddleware],
411
+ }),
412
+ ],
413
+ });
414
+ ```
135
415
 
136
- In _development_, the server actions run as a middleware in the Vite dev server.
137
- While in _production_, it's bundled into a single file that can be run with Node.js.
416
+ ## Automatic Validation & Documentation
138
417
 
139
- ## 🔧 Configuration
418
+ Add validation to any server function by attaching a Zod schema. The plugin automatically validates requests and generates OpenAPI documentation.
140
419
 
141
- Vite Server Actions works out of the box, but you can customize it by passing options to the plugin:
420
+ ### Quick Setup
142
421
 
143
422
  ```javascript
423
+ // vite.config.js
144
424
  serverActions({
145
- // Options (coming soon)
425
+ validation: {
426
+ enabled: true,
427
+ },
428
+ openAPI: {
429
+ enabled: true,
430
+ swaggerUI: true,
431
+ },
432
+ });
433
+ ```
434
+
435
+ ### Add Validation to Any Function
436
+
437
+ ```javascript
438
+ // server/users.server.js
439
+ import { z } from "zod";
440
+
441
+ const CreateUserSchema = z.object({
442
+ name: z.string().min(2),
443
+ email: z.string().email(),
444
+ role: z.enum(["admin", "user"]).default("user"),
445
+ });
446
+
447
+ export async function createUser(data) {
448
+ // Input is pre-validated - this will never run with invalid data
449
+ const user = await db.users.create({ data });
450
+
451
+ // Send welcome email, etc...
452
+ return user;
453
+ }
454
+
455
+ // Just attach the schema!
456
+ createUser.schema = CreateUserSchema;
457
+ ```
458
+
459
+ ### What You Get
460
+
461
+ 1. **Automatic Validation** - Invalid requests return 400 with detailed errors
462
+ 2. **Type Safety** - Full TypeScript inference from your Zod schemas
463
+ 3. **API Documentation** - Browse and test your API at `/api/docs`
464
+ 4. **OpenAPI Spec** - Machine-readable spec at `/api/openapi.json`
465
+
466
+ ### Advanced Validation
467
+
468
+ ```javascript
469
+ // Handle arrays and complex inputs
470
+ const BulkUpdateSchema = z.array(
471
+ z.object({
472
+ id: z.number(),
473
+ status: z.enum(["active", "inactive"]),
474
+ }),
475
+ );
476
+
477
+ export async function bulkUpdateUsers(updates) {
478
+ // Type: { id: number, status: 'active' | 'inactive' }[]
479
+ return await db.users.updateMany(updates);
480
+ }
481
+ bulkUpdateUsers.schema = BulkUpdateSchema;
482
+
483
+ // Validate multiple parameters
484
+ export async function getDateRange(startDate, endDate) {
485
+ // Validate both parameters
486
+ return await db.analytics.query({ startDate, endDate });
487
+ }
488
+ getDateRange.schema = z.tuple([
489
+ z.string().datetime(), // startDate
490
+ z.string().datetime(), // endDate
491
+ ]);
492
+ ```
493
+
494
+ ## 🚀 Production Deployment
495
+
496
+ ### Building for Production
497
+
498
+ ```bash
499
+ npm run build
500
+ ```
501
+
502
+ This generates:
503
+
504
+ - `dist/server.js` - Your Express server with all endpoints
505
+ - `dist/actions.js` - Bundled server functions
506
+ - `dist/openapi.json` - API specification (if enabled)
507
+ - Client assets with proxy functions
508
+
509
+ ### Running in Production
510
+
511
+ ```bash
512
+ node dist/server.js
513
+ ```
514
+
515
+ Or with PM2:
516
+
517
+ ```bash
518
+ pm2 start dist/server.js --name my-app
519
+ ```
520
+
521
+ ### Environment Variables
522
+
523
+ ```javascript
524
+ // Access environment variables in server functions
525
+ export async function sendEmail(to, subject, body) {
526
+ const apiKey = process.env.SENDGRID_API_KEY;
527
+ // ...
528
+ }
529
+ ```
530
+
531
+ ## 🛡️ Security Considerations
532
+
533
+ ### Server Code Isolation
534
+
535
+ - Server files (`.server.js`) are never bundled into client code
536
+ - Development builds include safety checks to prevent accidental imports
537
+ - Production builds completely separate server and client code
538
+
539
+ ### Best Practices
540
+
541
+ 1. **Never trust client input** - Always validate with Zod schemas
542
+ 2. **Use middleware for auth** - Add authentication checks globally
543
+ 3. **Sanitize file operations** - Be careful with file paths from clients
544
+ 4. **Limit exposed functions** - Only export what clients need
545
+ 5. **Use environment variables** - Keep secrets out of code
546
+
547
+ ### Example: Secure File Access
548
+
549
+ ```javascript
550
+ // ❌ Dangerous - allows arbitrary file access
551
+ export async function readFile(path) {
552
+ return await fs.readFile(path, "utf-8");
553
+ }
554
+
555
+ // ✅ Safe - validates and restricts access
556
+ import { z } from "zod";
557
+
558
+ const FileSchema = z.enum(["report.pdf", "summary.txt"]);
559
+
560
+ export async function readAllowedFile(filename) {
561
+ const safePath = path.join(SAFE_DIR, filename);
562
+ return await fs.readFile(safePath, "utf-8");
563
+ }
564
+ readAllowedFile.schema = FileSchema;
565
+ ```
566
+
567
+ ## 💻 TypeScript Support
568
+
569
+ Vite Server Actions has first-class TypeScript support with automatic type inference:
570
+
571
+ ```typescript
572
+ // server/users.server.ts
573
+ export async function getUser(id: number) {
574
+ return await db.users.findUnique({ where: { id } });
575
+ }
576
+
577
+ // App.tsx - Full type inference!
578
+ import { getUser } from "./server/users.server";
579
+
580
+ const user = await getUser(123); // Type: User | null
581
+ ```
582
+
583
+ ### With Zod Validation
584
+
585
+ ```typescript
586
+ import { z } from "zod";
587
+
588
+ const schema = z.object({
589
+ name: z.string(),
590
+ age: z.number(),
146
591
  });
592
+
593
+ export async function createUser(data: z.infer<typeof schema>) {
594
+ return await db.users.create({ data });
595
+ }
596
+ createUser.schema = schema;
147
597
  ```
148
598
 
149
- ## 🛠️ Configuration Options
599
+ ## 🔧 Error Handling
150
600
 
151
- Coming soon...
601
+ Server errors are automatically caught and returned with proper HTTP status codes:
152
602
 
153
- ## TODO
603
+ ```javascript
604
+ // server/api.server.js
605
+ export async function riskyOperation() {
606
+ throw new Error("Something went wrong");
607
+ }
608
+
609
+ // Client receives:
610
+ // Status: 500
611
+ // Body: { error: "Internal server error", details: "Something went wrong" }
612
+ ```
613
+
614
+ ### Custom Error Responses
615
+
616
+ ```javascript
617
+ export async function authenticate(token) {
618
+ if (!token) {
619
+ const error = new Error("No token provided");
620
+ error.status = 401;
621
+ throw error;
622
+ }
623
+ // ...
624
+ }
625
+ ```
626
+
627
+ ## 🎯 Common Patterns
628
+
629
+ ### Authenticated Actions
630
+
631
+ ```javascript
632
+ // server/auth.server.js
633
+ export async function withAuth(handler) {
634
+ return async (...args) => {
635
+ const token = args[args.length - 1]; // Pass token as last arg
636
+ const user = await verifyToken(token);
637
+ if (!user) throw new Error("Unauthorized");
638
+
639
+ return handler(...args.slice(0, -1), user);
640
+ };
641
+ }
154
642
 
155
- This is a proof of concept, and things are still missing, such as:
643
+ // server/protected.server.js
644
+ import { withAuth } from "./auth.server";
156
645
 
157
- - [ ] Add configuration options
158
- - [ ] Add tests
159
- - [ ] Allow customizing the HTTP method for each action (e.g. `GET`, `POST`, `PUT`, `DELETE`)
160
- - [ ] Make sure name collisions are handled correctly
161
- - [ ] Make sure the actions are only available on the server when running in production mode.
162
- - [ ] Add more examples (Vue, React, etc.)
163
- - [ ] Publish to npm
646
+ export const getSecretData = withAuth(async (user) => {
647
+ return await db.secrets.findMany({ userId: user.id });
648
+ });
649
+ ```
164
650
 
165
- ## 🧑‍💻 Development Setup
651
+ ### Caching
166
652
 
167
- To set up the project for development, follow these steps:
653
+ ```javascript
654
+ const cache = new Map();
655
+
656
+ export async function getExpensiveData(key) {
657
+ if (cache.has(key)) {
658
+ return cache.get(key);
659
+ }
168
660
 
169
- ```shell
170
- # Clone the repository
171
- git clone git@github.com:HelgeSverre/vite-plugin-server-actions.git
172
- cd vite-plugin-server-actions
661
+ const data = await expensiveOperation(key);
662
+ cache.set(key, data);
173
663
 
174
- # Install dependencies
175
- npm install
176
- npm run dev
664
+ // Clear after 5 minutes
665
+ setTimeout(() => cache.delete(key), 5 * 60 * 1000);
177
666
 
178
- # Format code
179
- npm run format
667
+ return data;
668
+ }
180
669
  ```
181
670
 
182
- ## 📝 License
671
+ ## 🤝 Contributing
672
+
673
+ Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
674
+
675
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
676
+
677
+ ## 📄 License
678
+
679
+ This project is [MIT](LICENSE) licensed.
680
+
681
+ ---
183
682
 
184
- This project is [MIT](https://opensource.org/licenses/MIT) licensed.
683
+ <p align="center">
684
+ Made with ❤️ by <a href="https://helgesver.re">Helge Sverre</a>
685
+ </p>