vite-plugin-server-actions 0.1.0 → 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/LICENSE +21 -0
- package/README.md +616 -123
- package/index.d.ts +200 -0
- package/package.json +81 -16
- package/src/build-utils.js +101 -0
- package/src/index.js +531 -58
- package/src/middleware.js +89 -0
- package/src/openapi.js +369 -0
- package/src/types.ts +35 -0
- package/src/validation.js +307 -0
- package/.editorconfig +0 -20
- package/.prettierrc +0 -7
- package/examples/todo-app/.prettierrc +0 -17
- package/examples/todo-app/README.md +0 -58
- package/examples/todo-app/index.html +0 -12
- package/examples/todo-app/jsconfig.json +0 -32
- package/examples/todo-app/package.json +0 -22
- package/examples/todo-app/src/App.svelte +0 -155
- package/examples/todo-app/src/actions/auth.server.js +0 -14
- package/examples/todo-app/src/actions/todo.server.js +0 -57
- package/examples/todo-app/src/app.css +0 -0
- package/examples/todo-app/src/main.js +0 -8
- package/examples/todo-app/svelte.config.js +0 -7
- package/examples/todo-app/todos.json +0 -27
- package/examples/todo-app/vite.config.js +0 -12
- package/examples/todo-app/yarn.lock +0 -658
package/index.d.ts
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import type { RequestHandler } from "express";
|
|
3
|
+
import type { z } from "zod";
|
|
4
|
+
|
|
5
|
+
export interface ValidationOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Enable validation for server actions
|
|
8
|
+
* @default false
|
|
9
|
+
*/
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validation adapter to use
|
|
14
|
+
* @default "zod"
|
|
15
|
+
*/
|
|
16
|
+
adapter?: "zod";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface OpenAPIOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Enable OpenAPI documentation generation
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* OpenAPI specification info
|
|
28
|
+
*/
|
|
29
|
+
info?: {
|
|
30
|
+
title?: string;
|
|
31
|
+
version?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Path to serve the Swagger UI documentation
|
|
37
|
+
* @default "/api/docs"
|
|
38
|
+
*/
|
|
39
|
+
docsPath?: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Path to serve the OpenAPI JSON specification
|
|
43
|
+
* @default "/api/openapi.json"
|
|
44
|
+
*/
|
|
45
|
+
specPath?: string;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Enable Swagger UI
|
|
49
|
+
* @default true when OpenAPI is enabled
|
|
50
|
+
*/
|
|
51
|
+
swaggerUI?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ServerActionOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Custom API prefix for server action endpoints
|
|
57
|
+
* @default "/api"
|
|
58
|
+
*/
|
|
59
|
+
apiPrefix?: string;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Include patterns for server action files
|
|
63
|
+
* @default ["**\/*.server.js"]
|
|
64
|
+
*/
|
|
65
|
+
include?: string | string[];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Exclude patterns for server action files
|
|
69
|
+
* @default []
|
|
70
|
+
*/
|
|
71
|
+
exclude?: string | string[];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Middleware to run before server action handlers
|
|
75
|
+
* Can be a single middleware or array of middleware
|
|
76
|
+
*/
|
|
77
|
+
middleware?: RequestHandler | RequestHandler[];
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Transform function for module names (internal use)
|
|
81
|
+
* @param filePath - The file path relative to project root
|
|
82
|
+
* @returns The module name to use internally
|
|
83
|
+
*/
|
|
84
|
+
moduleNameTransform?: (filePath: string) => string;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Transform function for API routes
|
|
88
|
+
* @param filePath - The file path relative to project root
|
|
89
|
+
* @param functionName - The exported function name
|
|
90
|
+
* @returns The API route path (without prefix)
|
|
91
|
+
* @default Clean hierarchical paths (removes src/ and .server.js)
|
|
92
|
+
*/
|
|
93
|
+
routeTransform?: (filePath: string, functionName: string) => string;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Validation configuration
|
|
97
|
+
*/
|
|
98
|
+
validation?: ValidationOptions;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* OpenAPI documentation configuration
|
|
102
|
+
*/
|
|
103
|
+
openAPI?: OpenAPIOptions;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface ServerActionsPlugin extends Plugin {
|
|
107
|
+
name: "vite-plugin-server-actions";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Creates a Vite plugin that enables server actions
|
|
112
|
+
* @param options - Configuration options for the plugin
|
|
113
|
+
* @returns Vite plugin instance
|
|
114
|
+
*/
|
|
115
|
+
declare function serverActions(options?: ServerActionOptions): ServerActionsPlugin;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Built-in middleware for server actions
|
|
119
|
+
*/
|
|
120
|
+
export declare const middleware: {
|
|
121
|
+
/**
|
|
122
|
+
* Logging middleware that displays server action calls with formatted JSON output
|
|
123
|
+
*/
|
|
124
|
+
logging: RequestHandler;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Path transformation utilities
|
|
129
|
+
*/
|
|
130
|
+
export declare const pathUtils: {
|
|
131
|
+
/**
|
|
132
|
+
* Creates clean hierarchical routes: "actions/todo/create"
|
|
133
|
+
* @param filePath - The file path relative to project root
|
|
134
|
+
* @param functionName - The exported function name
|
|
135
|
+
* @returns Clean route path
|
|
136
|
+
*/
|
|
137
|
+
createCleanRoute: (filePath: string, functionName: string) => string;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Creates legacy underscore-separated routes: "src_actions_todo/create"
|
|
141
|
+
* @param filePath - The file path relative to project root
|
|
142
|
+
* @param functionName - The exported function name
|
|
143
|
+
* @returns Legacy route path
|
|
144
|
+
*/
|
|
145
|
+
createLegacyRoute: (filePath: string, functionName: string) => string;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Creates minimal routes: "actions/todo.server/create"
|
|
149
|
+
* @param filePath - The file path relative to project root
|
|
150
|
+
* @param functionName - The exported function name
|
|
151
|
+
* @returns Minimal route path
|
|
152
|
+
*/
|
|
153
|
+
createMinimalRoute: (filePath: string, functionName: string) => string;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Creates module names for internal use
|
|
157
|
+
* @param filePath - The file path relative to project root
|
|
158
|
+
* @returns Module name
|
|
159
|
+
*/
|
|
160
|
+
createModuleName: (filePath: string) => string;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Validation exports
|
|
164
|
+
export interface ValidationAdapter {
|
|
165
|
+
validate(schema: any, data: any): Promise<any>;
|
|
166
|
+
getSchemaType(schema: any): string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export declare class ZodAdapter implements ValidationAdapter {
|
|
170
|
+
validate(schema: z.ZodSchema<any>, data: any): Promise<any>;
|
|
171
|
+
getSchemaType(schema: z.ZodSchema<any>): string;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export declare class SchemaDiscovery {
|
|
175
|
+
constructor();
|
|
176
|
+
registerSchema(moduleName: string, functionName: string, schema: any): void;
|
|
177
|
+
getSchema(moduleName: string, functionName: string): any;
|
|
178
|
+
getAllSchemas(): Map<string, any>;
|
|
179
|
+
discoverFromModule(module: any, moduleName: string): void;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export declare const adapters: {
|
|
183
|
+
zod: ZodAdapter;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export declare function createValidationMiddleware(options: {
|
|
187
|
+
schemaDiscovery: SchemaDiscovery;
|
|
188
|
+
}): RequestHandler;
|
|
189
|
+
|
|
190
|
+
// OpenAPI exports
|
|
191
|
+
export declare class OpenAPIGenerator {
|
|
192
|
+
constructor(options?: { info?: any });
|
|
193
|
+
generateSpec(serverFunctions: Map<string, any>, schemaDiscovery: SchemaDiscovery, options?: any): any;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export declare function setupOpenAPIEndpoints(app: any, options: any): void;
|
|
197
|
+
|
|
198
|
+
export declare function createSwaggerMiddleware(spec: any): RequestHandler;
|
|
199
|
+
|
|
200
|
+
export default serverActions;
|
package/package.json
CHANGED
|
@@ -1,39 +1,104 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-server-actions",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"type": "module",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Server actions for Vite - call backend functions directly from your frontend with automatic API generation, TypeScript support, and zero configuration",
|
|
6
5
|
"keywords": [
|
|
6
|
+
"vite",
|
|
7
7
|
"vite-plugin",
|
|
8
|
+
"server-actions",
|
|
8
9
|
"server",
|
|
9
|
-
"
|
|
10
|
+
"backend",
|
|
11
|
+
"api",
|
|
12
|
+
"rpc",
|
|
13
|
+
"typescript",
|
|
14
|
+
"openapi",
|
|
15
|
+
"swagger",
|
|
16
|
+
"validation",
|
|
17
|
+
"zod",
|
|
18
|
+
"full-stack",
|
|
19
|
+
"serverless",
|
|
20
|
+
"functions",
|
|
21
|
+
"middleware",
|
|
22
|
+
"express"
|
|
10
23
|
],
|
|
11
|
-
"license": "MIT",
|
|
12
24
|
"homepage": "https://github.com/HelgeSverre/vite-plugin-server-actions",
|
|
13
|
-
"repository": {
|
|
14
|
-
"type": "git",
|
|
15
|
-
"url": "https://github.com/HelgeSverre/vite-plugin-server-actions"
|
|
16
|
-
},
|
|
17
25
|
"bugs": {
|
|
18
26
|
"url": "https://github.com/HelgeSverre/vite-plugin-server-actions/issues"
|
|
19
27
|
},
|
|
20
|
-
"
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/HelgeSverre/vite-plugin-server-actions.git"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
21
33
|
"author": {
|
|
22
34
|
"name": "Helge Sverre",
|
|
23
35
|
"email": "helge.sverre@gmail.com",
|
|
24
36
|
"url": "https://helgesver.re"
|
|
25
37
|
},
|
|
38
|
+
"type": "module",
|
|
26
39
|
"main": "src/index.js",
|
|
40
|
+
"types": "index.d.ts",
|
|
41
|
+
"exports": {
|
|
42
|
+
".": {
|
|
43
|
+
"import": "./src/index.js",
|
|
44
|
+
"types": "./index.d.ts"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"src",
|
|
49
|
+
"index.d.ts",
|
|
50
|
+
"README.md",
|
|
51
|
+
"LICENSE"
|
|
52
|
+
],
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=16.0.0"
|
|
55
|
+
},
|
|
27
56
|
"scripts": {
|
|
57
|
+
"example:svelte:build": "npm run build --prefix examples/svelte-todo-app",
|
|
58
|
+
"example:svelte:dev": "npm run dev --prefix examples/svelte-todo-app",
|
|
59
|
+
"example:vue:build": "npm run build --prefix examples/vue-todo-app",
|
|
60
|
+
"example:vue:dev": "npm run dev --prefix examples/vue-todo-app",
|
|
61
|
+
"example:react:build": "npm run build --prefix examples/react-todo-app",
|
|
62
|
+
"example:react:dev": "npm run dev --prefix examples/react-todo-app",
|
|
28
63
|
"format": "npx prettier --write src/ README.md",
|
|
29
|
-
"
|
|
64
|
+
"lint": "eslint src/",
|
|
65
|
+
"lint:fix": "eslint src/ --fix",
|
|
66
|
+
"sort": "npx sort-package-json",
|
|
67
|
+
"test": "vitest",
|
|
68
|
+
"test:run": "vitest run",
|
|
69
|
+
"test:e2e": "playwright test",
|
|
70
|
+
"test:e2e:ui": "playwright test --ui",
|
|
71
|
+
"typecheck": "tsc --noEmit",
|
|
72
|
+
"check": "npm run test:run && npm run lint && npm run typecheck",
|
|
73
|
+
"prepublishOnly": "npm run check"
|
|
30
74
|
},
|
|
31
|
-
"
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"@asteasolutions/zod-to-openapi": "^7.3.4",
|
|
32
77
|
"express": "^4.21.0",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
78
|
+
"minimatch": "^10.0.3",
|
|
79
|
+
"rollup": "^4.0.0",
|
|
80
|
+
"swagger-ui-express": "^5.0.0",
|
|
81
|
+
"zod": "^3.22.4"
|
|
82
|
+
},
|
|
83
|
+
"devDependencies": {
|
|
84
|
+
"@apidevtools/swagger-parser": "^12.0.0",
|
|
85
|
+
"@eslint/js": "^9.29.0",
|
|
86
|
+
"@playwright/test": "^1.53.1",
|
|
87
|
+
"@stoplight/spectral-core": "^1.20.0",
|
|
88
|
+
"@stoplight/spectral-rulesets": "^1.22.0",
|
|
89
|
+
"@types/express": "^5.0.3",
|
|
90
|
+
"@types/node": "^24.0.3",
|
|
91
|
+
"@types/swagger-ui-express": "^4.1.6",
|
|
92
|
+
"@vitest/runner": "^3.2.4",
|
|
93
|
+
"eslint": "^9.29.0",
|
|
94
|
+
"playwright": "^1.53.1",
|
|
95
|
+
"prettier": "^3.6.0",
|
|
96
|
+
"typescript": "^5.8.3",
|
|
97
|
+
"vite": "^6.3.5",
|
|
98
|
+
"vitest": "^3.2.4"
|
|
35
99
|
},
|
|
36
100
|
"peerDependencies": {
|
|
37
|
-
"vite": "^2.0.0 || ^3.0.0 || ^4.0.0"
|
|
38
|
-
}
|
|
101
|
+
"vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
|
|
102
|
+
},
|
|
103
|
+
"readme": "README.md"
|
|
39
104
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
import { pathToFileURL } from "url";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extract schemas from server modules during build time
|
|
6
|
+
*/
|
|
7
|
+
export async function extractSchemas(serverFunctions) {
|
|
8
|
+
const schemas = {};
|
|
9
|
+
|
|
10
|
+
for (const [moduleName, { id, functions }] of serverFunctions) {
|
|
11
|
+
schemas[moduleName] = {};
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
// Import the module to get schemas
|
|
15
|
+
const moduleUrl = pathToFileURL(id).href;
|
|
16
|
+
const module = await import(moduleUrl);
|
|
17
|
+
|
|
18
|
+
// Extract schemas from exported functions
|
|
19
|
+
for (const functionName of functions) {
|
|
20
|
+
if (module[functionName] && module[functionName].schema) {
|
|
21
|
+
// We need to serialize the Zod schema
|
|
22
|
+
// For now, we'll store a reference that can be imported
|
|
23
|
+
schemas[moduleName][functionName] = {
|
|
24
|
+
hasSchema: true,
|
|
25
|
+
// We'll need to generate import statements for these
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.warn(`Failed to extract schemas from ${id}: ${error.message}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return schemas;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Generate validation setup code for production
|
|
39
|
+
*/
|
|
40
|
+
export function generateValidationCode(options, serverFunctions) {
|
|
41
|
+
if (!options.validation?.enabled) {
|
|
42
|
+
return {
|
|
43
|
+
imports: "",
|
|
44
|
+
setup: "",
|
|
45
|
+
middlewareFactory: "",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Generate imports
|
|
50
|
+
const imports = `
|
|
51
|
+
import { createValidationMiddleware, SchemaDiscovery } from '../../../src/validation.js';
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
// Generate schema imports from the bundled actions
|
|
55
|
+
const schemaImports = Array.from(serverFunctions.entries())
|
|
56
|
+
.map(([moduleName, { functions }]) => {
|
|
57
|
+
return functions.map((fn) => `// Import schema for ${moduleName}.${fn} if it exists`).join("\n");
|
|
58
|
+
})
|
|
59
|
+
.join("\n");
|
|
60
|
+
|
|
61
|
+
// Generate setup code
|
|
62
|
+
const setup = `
|
|
63
|
+
// Setup validation
|
|
64
|
+
const schemaDiscovery = new SchemaDiscovery();
|
|
65
|
+
const validationMiddleware = createValidationMiddleware({ schemaDiscovery });
|
|
66
|
+
|
|
67
|
+
// Register schemas from server actions
|
|
68
|
+
${Array.from(serverFunctions.entries())
|
|
69
|
+
.map(([moduleName, { functions }]) => {
|
|
70
|
+
return functions
|
|
71
|
+
.map(
|
|
72
|
+
(fn) => `
|
|
73
|
+
if (serverActions.${moduleName}.${fn}.schema) {
|
|
74
|
+
schemaDiscovery.registerSchema('${moduleName}', '${fn}', serverActions.${moduleName}.${fn}.schema);
|
|
75
|
+
}`,
|
|
76
|
+
)
|
|
77
|
+
.join("\n");
|
|
78
|
+
})
|
|
79
|
+
.join("\n")}
|
|
80
|
+
`;
|
|
81
|
+
|
|
82
|
+
// Generate middleware factory
|
|
83
|
+
const middlewareFactory = `
|
|
84
|
+
function createContextualValidationMiddleware(moduleName, functionName) {
|
|
85
|
+
return (req, res, next) => {
|
|
86
|
+
req.validationContext = {
|
|
87
|
+
moduleName,
|
|
88
|
+
functionName,
|
|
89
|
+
schema: serverActions[moduleName]?.[functionName]?.schema
|
|
90
|
+
};
|
|
91
|
+
return validationMiddleware(req, res, next);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
imports: imports + schemaImports,
|
|
98
|
+
setup,
|
|
99
|
+
middlewareFactory,
|
|
100
|
+
};
|
|
101
|
+
}
|