vite-plugin-server-actions 1.0.1 → 1.2.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 +162 -52
- package/index.d.ts +76 -32
- package/package.json +23 -14
- package/src/ast-parser.js +535 -0
- package/src/build-utils.js +10 -12
- package/src/dev-validator.js +272 -0
- package/src/error-enhancer.js +283 -0
- package/src/index.js +428 -80
- package/src/openapi.js +67 -29
- package/src/security.js +118 -0
- package/src/type-generator.js +378 -0
- package/src/types.ts +1 -1
- package/src/validation-runtime.js +100 -0
- package/src/validation.js +126 -21
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import { parse } from "@babel/parser";
|
|
2
|
+
import traverse from "@babel/traverse";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extract exported functions from JavaScript/TypeScript code using AST parsing
|
|
6
|
+
* @param {string} code - The source code to parse
|
|
7
|
+
* @param {string} filename - The filename (for better error messages)
|
|
8
|
+
* @returns {Array<{name: string, isAsync: boolean, isDefault: boolean, type: string, params: Array, returnType: string|null, jsdoc: string|null}>}
|
|
9
|
+
*/
|
|
10
|
+
export function extractExportedFunctions(code, filename = "unknown") {
|
|
11
|
+
const functions = [];
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
// Parse the code into an AST
|
|
15
|
+
const ast = parse(code, {
|
|
16
|
+
sourceType: "module",
|
|
17
|
+
plugins: [
|
|
18
|
+
"typescript",
|
|
19
|
+
"jsx",
|
|
20
|
+
"decorators-legacy",
|
|
21
|
+
"dynamicImport",
|
|
22
|
+
"exportDefaultFrom",
|
|
23
|
+
"exportNamespaceFrom",
|
|
24
|
+
"topLevelAwait",
|
|
25
|
+
"classProperties",
|
|
26
|
+
"classPrivateProperties",
|
|
27
|
+
"classPrivateMethods",
|
|
28
|
+
],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Traverse the AST to find exported functions
|
|
32
|
+
const traverseFn = traverse.default || traverse;
|
|
33
|
+
traverseFn(ast, {
|
|
34
|
+
// Handle: export function name() {} or export async function name() {}
|
|
35
|
+
ExportNamedDeclaration(path) {
|
|
36
|
+
const declaration = path.node.declaration;
|
|
37
|
+
|
|
38
|
+
if (declaration && declaration.type === "FunctionDeclaration") {
|
|
39
|
+
if (declaration.id) {
|
|
40
|
+
functions.push({
|
|
41
|
+
name: declaration.id.name,
|
|
42
|
+
isAsync: declaration.async || false,
|
|
43
|
+
isDefault: false,
|
|
44
|
+
type: "function",
|
|
45
|
+
params: extractDetailedParams(declaration.params),
|
|
46
|
+
returnType: extractTypeAnnotation(declaration.returnType),
|
|
47
|
+
jsdoc: extractJSDoc(path.node.leadingComments),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Handle: export const name = () => {} or export const name = async () => {}
|
|
53
|
+
if (declaration && declaration.type === "VariableDeclaration") {
|
|
54
|
+
declaration.declarations.forEach((decl) => {
|
|
55
|
+
if (
|
|
56
|
+
decl.init &&
|
|
57
|
+
(decl.init.type === "ArrowFunctionExpression" || decl.init.type === "FunctionExpression")
|
|
58
|
+
) {
|
|
59
|
+
functions.push({
|
|
60
|
+
name: decl.id.name,
|
|
61
|
+
isAsync: decl.init.async || false,
|
|
62
|
+
isDefault: false,
|
|
63
|
+
type: "arrow",
|
|
64
|
+
params: extractDetailedParams(decl.init.params),
|
|
65
|
+
returnType: extractTypeAnnotation(decl.init.returnType),
|
|
66
|
+
jsdoc: extractJSDoc(declaration.leadingComments),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// Handle: export default function() {} or export default async function name() {}
|
|
74
|
+
ExportDefaultDeclaration(path) {
|
|
75
|
+
const declaration = path.node.declaration;
|
|
76
|
+
|
|
77
|
+
if (declaration.type === "FunctionDeclaration") {
|
|
78
|
+
functions.push({
|
|
79
|
+
name: declaration.id ? declaration.id.name : "default",
|
|
80
|
+
isAsync: declaration.async || false,
|
|
81
|
+
isDefault: true,
|
|
82
|
+
type: "function",
|
|
83
|
+
params: extractDetailedParams(declaration.params),
|
|
84
|
+
returnType: extractTypeAnnotation(declaration.returnType),
|
|
85
|
+
jsdoc: extractJSDoc(path.node.leadingComments),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Handle: export default () => {} or export default async () => {}
|
|
90
|
+
if (declaration.type === "ArrowFunctionExpression" || declaration.type === "FunctionExpression") {
|
|
91
|
+
functions.push({
|
|
92
|
+
name: "default",
|
|
93
|
+
isAsync: declaration.async || false,
|
|
94
|
+
isDefault: true,
|
|
95
|
+
type: "arrow",
|
|
96
|
+
params: extractDetailedParams(declaration.params),
|
|
97
|
+
returnType: extractTypeAnnotation(declaration.returnType),
|
|
98
|
+
jsdoc: extractJSDoc(path.node.leadingComments),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// Handle: export { functionName } or export { internalName as publicName }
|
|
104
|
+
ExportSpecifier(path) {
|
|
105
|
+
// We need to track these and match them with function declarations
|
|
106
|
+
const localName = path.node.local.name;
|
|
107
|
+
const exportedName = path.node.exported.name;
|
|
108
|
+
|
|
109
|
+
// Look for the function in the module scope
|
|
110
|
+
const binding = path.scope.getBinding(localName);
|
|
111
|
+
if (binding && binding.path.isFunctionDeclaration()) {
|
|
112
|
+
functions.push({
|
|
113
|
+
name: exportedName,
|
|
114
|
+
isAsync: binding.path.node.async || false,
|
|
115
|
+
isDefault: false,
|
|
116
|
+
type: "renamed",
|
|
117
|
+
params: extractDetailedParams(binding.path.node.params),
|
|
118
|
+
returnType: extractTypeAnnotation(binding.path.node.returnType),
|
|
119
|
+
jsdoc: extractJSDoc(binding.path.node.leadingComments),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check if it's a variable with arrow function
|
|
124
|
+
if (binding && binding.path.isVariableDeclarator()) {
|
|
125
|
+
const init = binding.path.node.init;
|
|
126
|
+
if (init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) {
|
|
127
|
+
functions.push({
|
|
128
|
+
name: exportedName,
|
|
129
|
+
isAsync: init.async || false,
|
|
130
|
+
isDefault: false,
|
|
131
|
+
type: "renamed-arrow",
|
|
132
|
+
params: extractDetailedParams(init.params),
|
|
133
|
+
returnType: extractTypeAnnotation(init.returnType),
|
|
134
|
+
jsdoc: extractJSDoc(binding.path.node.leadingComments),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error(`Failed to parse ${filename}: ${error.message}`);
|
|
142
|
+
// Return empty array on parse error rather than throwing
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Remove duplicates and return
|
|
147
|
+
const uniqueFunctions = Array.from(new Map(functions.map((fn) => [fn.name, fn])).values());
|
|
148
|
+
|
|
149
|
+
return uniqueFunctions;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Validate if a function name is valid JavaScript identifier
|
|
154
|
+
* @param {string} name - The function name to validate
|
|
155
|
+
* @returns {boolean}
|
|
156
|
+
*/
|
|
157
|
+
export function isValidFunctionName(name) {
|
|
158
|
+
// Check if it's a valid JavaScript identifier
|
|
159
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Extract detailed parameter information from function parameters
|
|
164
|
+
* @param {Array} params - Array of parameter AST nodes
|
|
165
|
+
* @returns {Array<{name: string, type: string|null, defaultValue: string|null, isOptional: boolean, isRest: boolean}>}
|
|
166
|
+
*/
|
|
167
|
+
export function extractDetailedParams(params) {
|
|
168
|
+
if (!params) return [];
|
|
169
|
+
|
|
170
|
+
return params.map((param) => {
|
|
171
|
+
const paramInfo = {
|
|
172
|
+
name: "",
|
|
173
|
+
type: null,
|
|
174
|
+
defaultValue: null,
|
|
175
|
+
isOptional: false,
|
|
176
|
+
isRest: false,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
if (param.type === "Identifier") {
|
|
180
|
+
paramInfo.name = param.name;
|
|
181
|
+
paramInfo.type = extractTypeAnnotation(param.typeAnnotation);
|
|
182
|
+
paramInfo.isOptional = param.optional || false;
|
|
183
|
+
} else if (param.type === "AssignmentPattern") {
|
|
184
|
+
// Handle default parameters: function(name = 'default')
|
|
185
|
+
paramInfo.name = param.left.name;
|
|
186
|
+
paramInfo.type = extractTypeAnnotation(param.left.typeAnnotation);
|
|
187
|
+
paramInfo.defaultValue = generateCode(param.right);
|
|
188
|
+
paramInfo.isOptional = true;
|
|
189
|
+
} else if (param.type === "RestElement") {
|
|
190
|
+
// Handle rest parameters: function(...args)
|
|
191
|
+
paramInfo.name = `...${param.argument.name}`;
|
|
192
|
+
paramInfo.type = extractTypeAnnotation(param.typeAnnotation);
|
|
193
|
+
paramInfo.isRest = true;
|
|
194
|
+
} else if (param.type === "ObjectPattern") {
|
|
195
|
+
// Handle destructuring: function({name, age})
|
|
196
|
+
paramInfo.name = generateCode(param);
|
|
197
|
+
paramInfo.type = extractTypeAnnotation(param.typeAnnotation);
|
|
198
|
+
paramInfo.isOptional = param.optional || false;
|
|
199
|
+
} else if (param.type === "ArrayPattern") {
|
|
200
|
+
// Handle array destructuring: function([first, second])
|
|
201
|
+
paramInfo.name = generateCode(param);
|
|
202
|
+
paramInfo.type = extractTypeAnnotation(param.typeAnnotation);
|
|
203
|
+
paramInfo.isOptional = param.optional || false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return paramInfo;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Extract type annotation as string
|
|
212
|
+
* @param {object} typeAnnotation - Type annotation AST node
|
|
213
|
+
* @returns {string|null}
|
|
214
|
+
*/
|
|
215
|
+
export function extractTypeAnnotation(typeAnnotation) {
|
|
216
|
+
if (!typeAnnotation || !typeAnnotation.typeAnnotation) return null;
|
|
217
|
+
|
|
218
|
+
return generateCode(typeAnnotation.typeAnnotation);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Extract JSDoc comments
|
|
223
|
+
* @param {Array} comments - Array of comment nodes
|
|
224
|
+
* @returns {string|null}
|
|
225
|
+
*/
|
|
226
|
+
export function extractJSDoc(comments) {
|
|
227
|
+
if (!comments) return null;
|
|
228
|
+
|
|
229
|
+
const jsdocComment = comments.find((comment) => comment.type === "CommentBlock" && comment.value.startsWith("*"));
|
|
230
|
+
|
|
231
|
+
return jsdocComment ? `/*${jsdocComment.value}*/` : null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Generate code string from AST node (simplified)
|
|
236
|
+
* @param {object} node - AST node
|
|
237
|
+
* @returns {string}
|
|
238
|
+
*/
|
|
239
|
+
function generateCode(node) {
|
|
240
|
+
if (!node) return "";
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
// Simple code generation for common cases
|
|
244
|
+
switch (node.type) {
|
|
245
|
+
case "Identifier":
|
|
246
|
+
return node.name;
|
|
247
|
+
case "StringLiteral":
|
|
248
|
+
return `"${node.value}"`;
|
|
249
|
+
case "NumericLiteral":
|
|
250
|
+
return String(node.value);
|
|
251
|
+
case "BooleanLiteral":
|
|
252
|
+
return String(node.value);
|
|
253
|
+
case "NullLiteral":
|
|
254
|
+
return "null";
|
|
255
|
+
case "TSStringKeyword":
|
|
256
|
+
return "string";
|
|
257
|
+
case "TSNumberKeyword":
|
|
258
|
+
return "number";
|
|
259
|
+
case "TSBooleanKeyword":
|
|
260
|
+
return "boolean";
|
|
261
|
+
case "TSAnyKeyword":
|
|
262
|
+
return "any";
|
|
263
|
+
case "TSUnknownKeyword":
|
|
264
|
+
return "unknown";
|
|
265
|
+
case "TSVoidKeyword":
|
|
266
|
+
return "void";
|
|
267
|
+
case "TSArrayType":
|
|
268
|
+
return `${generateCode(node.elementType)}[]`;
|
|
269
|
+
case "TSUnionType":
|
|
270
|
+
return node.types.map((type) => generateCode(type)).join(" | ");
|
|
271
|
+
case "TSLiteralType":
|
|
272
|
+
return generateCode(node.literal);
|
|
273
|
+
case "ObjectPattern":
|
|
274
|
+
const props = node.properties
|
|
275
|
+
.map((prop) => {
|
|
276
|
+
if (prop.type === "ObjectProperty") {
|
|
277
|
+
return prop.key.name;
|
|
278
|
+
} else if (prop.type === "RestElement") {
|
|
279
|
+
return `...${prop.argument.name}`;
|
|
280
|
+
}
|
|
281
|
+
return "";
|
|
282
|
+
})
|
|
283
|
+
.filter(Boolean);
|
|
284
|
+
return `{${props.join(", ")}}`;
|
|
285
|
+
case "ArrayPattern":
|
|
286
|
+
const elements = node.elements.map((elem, i) =>
|
|
287
|
+
elem ? (elem.type === "Identifier" ? elem.name : `_${i}`) : `_${i}`,
|
|
288
|
+
);
|
|
289
|
+
return `[${elements.join(", ")}]`;
|
|
290
|
+
case "TSTypeReference":
|
|
291
|
+
// Handle type references like Todo, CreateTodoInput, etc.
|
|
292
|
+
if (node.typeName) {
|
|
293
|
+
let typeName = "";
|
|
294
|
+
|
|
295
|
+
// Handle qualified names like z.infer
|
|
296
|
+
if (node.typeName.type === "TSQualifiedName") {
|
|
297
|
+
typeName = generateCode(node.typeName);
|
|
298
|
+
} else if (node.typeName.type === "Identifier") {
|
|
299
|
+
typeName = node.typeName.name;
|
|
300
|
+
} else {
|
|
301
|
+
return "unknown";
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Handle generic types like Promise<T>, Array<T>
|
|
305
|
+
if (node.typeParameters && node.typeParameters.params && node.typeParameters.params.length > 0) {
|
|
306
|
+
const typeArgs = node.typeParameters.params.map((param) => generateCode(param)).join(", ");
|
|
307
|
+
return `${typeName}<${typeArgs}>`;
|
|
308
|
+
}
|
|
309
|
+
return typeName;
|
|
310
|
+
}
|
|
311
|
+
return "unknown";
|
|
312
|
+
case "TSTypeLiteral":
|
|
313
|
+
// Handle object type literals
|
|
314
|
+
if (node.members && node.members.length > 0) {
|
|
315
|
+
const members = node.members
|
|
316
|
+
.map((member) => {
|
|
317
|
+
if (member.type === "TSPropertySignature" && member.key) {
|
|
318
|
+
const key = member.key.type === "Identifier" ? member.key.name : "unknown";
|
|
319
|
+
const type = member.typeAnnotation ? generateCode(member.typeAnnotation.typeAnnotation) : "any";
|
|
320
|
+
const optional = member.optional ? "?" : "";
|
|
321
|
+
return `${key}${optional}: ${type}`;
|
|
322
|
+
}
|
|
323
|
+
return "";
|
|
324
|
+
})
|
|
325
|
+
.filter(Boolean);
|
|
326
|
+
return `{ ${members.join("; ")} }`;
|
|
327
|
+
}
|
|
328
|
+
return "{}";
|
|
329
|
+
case "TSInterfaceDeclaration":
|
|
330
|
+
// Handle interface declarations
|
|
331
|
+
return node.id ? node.id.name : "unknown";
|
|
332
|
+
case "TSNullKeyword":
|
|
333
|
+
return "null";
|
|
334
|
+
case "TSUndefinedKeyword":
|
|
335
|
+
return "undefined";
|
|
336
|
+
case "TSFunctionType":
|
|
337
|
+
// Handle function type signatures
|
|
338
|
+
const funcParams = node.parameters
|
|
339
|
+
.map((param) => {
|
|
340
|
+
let paramStr = "";
|
|
341
|
+
const paramName = param.name ? param.name : "_";
|
|
342
|
+
const paramType = param.typeAnnotation ? generateCode(param.typeAnnotation.typeAnnotation) : "any";
|
|
343
|
+
|
|
344
|
+
// Handle rest parameters
|
|
345
|
+
if (param.type === "RestElement") {
|
|
346
|
+
paramStr = `...${param.argument.name}: ${paramType}`;
|
|
347
|
+
} else {
|
|
348
|
+
paramStr = `${paramName}`;
|
|
349
|
+
// Handle optional parameters
|
|
350
|
+
if (param.optional) {
|
|
351
|
+
paramStr += "?";
|
|
352
|
+
}
|
|
353
|
+
paramStr += `: ${paramType}`;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return paramStr;
|
|
357
|
+
})
|
|
358
|
+
.join(", ");
|
|
359
|
+
const funcReturn = node.typeAnnotation ? generateCode(node.typeAnnotation.typeAnnotation) : "void";
|
|
360
|
+
return `(${funcParams}) => ${funcReturn}`;
|
|
361
|
+
case "TSIntersectionType":
|
|
362
|
+
// Handle intersection types: A & B & C
|
|
363
|
+
return node.types.map((type) => generateCode(type)).join(" & ");
|
|
364
|
+
case "TSTupleType":
|
|
365
|
+
// Handle tuple types: [string, number, boolean]
|
|
366
|
+
const tupleElements = node.elementTypes.map((elem) => generateCode(elem)).join(", ");
|
|
367
|
+
return `[${tupleElements}]`;
|
|
368
|
+
case "TSIndexSignature":
|
|
369
|
+
// Handle index signatures: { [key: string]: any }
|
|
370
|
+
if (node.parameters && node.parameters.length > 0) {
|
|
371
|
+
const param = node.parameters[0];
|
|
372
|
+
const keyName = param.name || "key";
|
|
373
|
+
const keyType = param.typeAnnotation ? generateCode(param.typeAnnotation.typeAnnotation) : "string";
|
|
374
|
+
const valueType = node.typeAnnotation ? generateCode(node.typeAnnotation.typeAnnotation) : "any";
|
|
375
|
+
return `[${keyName}: ${keyType}]: ${valueType}`;
|
|
376
|
+
}
|
|
377
|
+
return "[key: string]: any";
|
|
378
|
+
case "TSBigIntKeyword":
|
|
379
|
+
return "bigint";
|
|
380
|
+
case "TSSymbolKeyword":
|
|
381
|
+
return "symbol";
|
|
382
|
+
case "TSNeverKeyword":
|
|
383
|
+
return "never";
|
|
384
|
+
case "TSThisType":
|
|
385
|
+
return "this";
|
|
386
|
+
case "TSTemplateLiteralType":
|
|
387
|
+
// Handle template literal types
|
|
388
|
+
if (node.quasis && node.types) {
|
|
389
|
+
let result = "";
|
|
390
|
+
for (let i = 0; i < node.quasis.length; i++) {
|
|
391
|
+
result += node.quasis[i].value.raw;
|
|
392
|
+
if (i < node.types.length) {
|
|
393
|
+
result += "${" + generateCode(node.types[i]) + "}";
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return "`" + result + "`";
|
|
397
|
+
}
|
|
398
|
+
return "`${string}`";
|
|
399
|
+
case "TemplateLiteral":
|
|
400
|
+
// Handle template literals (non-type version)
|
|
401
|
+
if (node.quasis && node.expressions) {
|
|
402
|
+
let result = "";
|
|
403
|
+
for (let i = 0; i < node.quasis.length; i++) {
|
|
404
|
+
result += node.quasis[i].value.raw;
|
|
405
|
+
if (i < node.expressions.length) {
|
|
406
|
+
result += "${" + generateCode(node.expressions[i]) + "}";
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return "`" + result + "`";
|
|
410
|
+
}
|
|
411
|
+
return "`${string}`";
|
|
412
|
+
case "TSConditionalType":
|
|
413
|
+
// Handle conditional types: T extends U ? X : Y
|
|
414
|
+
const checkType = generateCode(node.checkType);
|
|
415
|
+
const extendsType = generateCode(node.extendsType);
|
|
416
|
+
const trueType = generateCode(node.trueType);
|
|
417
|
+
const falseType = generateCode(node.falseType);
|
|
418
|
+
return `${checkType} extends ${extendsType} ? ${trueType} : ${falseType}`;
|
|
419
|
+
case "TSTypeOperator":
|
|
420
|
+
// Handle type operators like readonly, keyof
|
|
421
|
+
const operator = node.operator;
|
|
422
|
+
const typeArg = generateCode(node.typeAnnotation);
|
|
423
|
+
return `${operator} ${typeArg}`;
|
|
424
|
+
case "TSIndexedAccessType":
|
|
425
|
+
// Handle indexed access types: T[K]
|
|
426
|
+
const objectType = generateCode(node.objectType);
|
|
427
|
+
const indexType = generateCode(node.indexType);
|
|
428
|
+
return `${objectType}[${indexType}]`;
|
|
429
|
+
case "TSMappedType":
|
|
430
|
+
// Handle mapped types: { [K in T]: U }
|
|
431
|
+
let mapped = "{";
|
|
432
|
+
if (node.readonly) {
|
|
433
|
+
mapped += node.readonly === "+" ? "readonly " : "-readonly ";
|
|
434
|
+
}
|
|
435
|
+
mapped += "[";
|
|
436
|
+
if (node.typeParameter) {
|
|
437
|
+
mapped += node.typeParameter.name;
|
|
438
|
+
if (node.typeParameter.constraint) {
|
|
439
|
+
mapped += " in " + generateCode(node.typeParameter.constraint);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
mapped += "]";
|
|
443
|
+
if (node.optional) {
|
|
444
|
+
mapped += node.optional === "+" ? "?" : "-?";
|
|
445
|
+
}
|
|
446
|
+
mapped += ": ";
|
|
447
|
+
if (node.typeAnnotation) {
|
|
448
|
+
mapped += generateCode(node.typeAnnotation);
|
|
449
|
+
}
|
|
450
|
+
mapped += "}";
|
|
451
|
+
return mapped;
|
|
452
|
+
case "TSTypePredicate":
|
|
453
|
+
// Handle type predicates: value is Type
|
|
454
|
+
const paramName = node.parameterName ? node.parameterName.name : "value";
|
|
455
|
+
const predicateType = node.typeAnnotation ? generateCode(node.typeAnnotation.typeAnnotation) : "unknown";
|
|
456
|
+
return `${paramName} is ${predicateType}`;
|
|
457
|
+
case "TSParenthesizedType":
|
|
458
|
+
// Handle parenthesized types: (string | number)
|
|
459
|
+
return `(${generateCode(node.typeAnnotation)})`;
|
|
460
|
+
case "TSTypeQuery":
|
|
461
|
+
// Handle typeof operator: typeof someValue
|
|
462
|
+
const exprName = node.exprName;
|
|
463
|
+
if (exprName.type === "Identifier") {
|
|
464
|
+
return `typeof ${exprName.name}`;
|
|
465
|
+
}
|
|
466
|
+
return "typeof unknown";
|
|
467
|
+
case "TSQualifiedName":
|
|
468
|
+
// Handle qualified names like A.B.C
|
|
469
|
+
if (node.left.type === "TSQualifiedName") {
|
|
470
|
+
return generateCode(node.left) + "." + node.right.name;
|
|
471
|
+
} else if (node.left.type === "Identifier") {
|
|
472
|
+
return node.left.name + "." + node.right.name;
|
|
473
|
+
}
|
|
474
|
+
return "unknown";
|
|
475
|
+
case "TSOptionalType":
|
|
476
|
+
// Handle optional types in function parameters
|
|
477
|
+
return generateCode(node.typeAnnotation);
|
|
478
|
+
case "TSRestType":
|
|
479
|
+
// Handle rest types: ...Type[]
|
|
480
|
+
return "..." + generateCode(node.typeAnnotation);
|
|
481
|
+
case "TSNamedTupleMember":
|
|
482
|
+
// Handle named tuple members
|
|
483
|
+
let namedTuple = "";
|
|
484
|
+
if (node.label) {
|
|
485
|
+
namedTuple += node.label.name + ": ";
|
|
486
|
+
}
|
|
487
|
+
namedTuple += generateCode(node.elementType);
|
|
488
|
+
if (node.optional) {
|
|
489
|
+
namedTuple += "?";
|
|
490
|
+
}
|
|
491
|
+
return namedTuple;
|
|
492
|
+
case "TSInferType":
|
|
493
|
+
// Handle infer types: infer T
|
|
494
|
+
return `infer ${node.typeParameter.name}`;
|
|
495
|
+
case "TSImportType":
|
|
496
|
+
// Handle import types: import("module").Type
|
|
497
|
+
let importStr = `import("${node.argument.value}")`;
|
|
498
|
+
if (node.qualifier) {
|
|
499
|
+
// Handle nested qualifiers like import("./types").users.Admin
|
|
500
|
+
if (node.qualifier.type === "TSQualifiedName") {
|
|
501
|
+
// Recursively build the qualified name
|
|
502
|
+
const buildQualifiedName = (qName) => {
|
|
503
|
+
if (qName.left.type === "TSQualifiedName") {
|
|
504
|
+
return buildQualifiedName(qName.left) + "." + qName.right.name;
|
|
505
|
+
} else {
|
|
506
|
+
return qName.left.name + "." + qName.right.name;
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
importStr += "." + buildQualifiedName(node.qualifier);
|
|
510
|
+
} else {
|
|
511
|
+
importStr += "." + node.qualifier.name;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (node.typeParameters) {
|
|
515
|
+
const typeArgs = node.typeParameters.params.map((param) => generateCode(param)).join(", ");
|
|
516
|
+
importStr += `<${typeArgs}>`;
|
|
517
|
+
}
|
|
518
|
+
return importStr;
|
|
519
|
+
default:
|
|
520
|
+
// Fallback for complex types
|
|
521
|
+
return node.type || "unknown";
|
|
522
|
+
}
|
|
523
|
+
} catch (error) {
|
|
524
|
+
return "unknown";
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Extract function parameter names from AST (legacy compatibility)
|
|
530
|
+
* @param {object} functionNode - The function AST node
|
|
531
|
+
* @returns {Array<string>}
|
|
532
|
+
*/
|
|
533
|
+
export function extractFunctionParams(functionNode) {
|
|
534
|
+
return extractDetailedParams(functionNode.params).map((param) => param.name);
|
|
535
|
+
}
|
package/src/build-utils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "module";
|
|
2
2
|
import { pathToFileURL } from "url";
|
|
3
|
+
import fs from "fs/promises";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Extract schemas from server modules during build time
|
|
@@ -37,27 +38,23 @@ export async function extractSchemas(serverFunctions) {
|
|
|
37
38
|
/**
|
|
38
39
|
* Generate validation setup code for production
|
|
39
40
|
*/
|
|
40
|
-
export function generateValidationCode(options, serverFunctions) {
|
|
41
|
+
export async function generateValidationCode(options, serverFunctions) {
|
|
41
42
|
if (!options.validation?.enabled) {
|
|
42
43
|
return {
|
|
43
44
|
imports: "",
|
|
44
45
|
setup: "",
|
|
45
46
|
middlewareFactory: "",
|
|
47
|
+
validationRuntime: "",
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
//
|
|
50
|
-
const
|
|
51
|
-
|
|
51
|
+
// Read the validation runtime code that will be embedded
|
|
52
|
+
const validationRuntimePath = new URL("./validation-runtime.js", import.meta.url);
|
|
53
|
+
const validationRuntime = `
|
|
54
|
+
// Embedded validation runtime
|
|
55
|
+
${await fs.readFile(validationRuntimePath, "utf-8")}
|
|
52
56
|
`;
|
|
53
57
|
|
|
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
58
|
// Generate setup code
|
|
62
59
|
const setup = `
|
|
63
60
|
// Setup validation
|
|
@@ -94,8 +91,9 @@ function createContextualValidationMiddleware(moduleName, functionName) {
|
|
|
94
91
|
`;
|
|
95
92
|
|
|
96
93
|
return {
|
|
97
|
-
imports:
|
|
94
|
+
imports: "",
|
|
98
95
|
setup,
|
|
99
96
|
middlewareFactory,
|
|
97
|
+
validationRuntime,
|
|
100
98
|
};
|
|
101
99
|
}
|