schemantic 0.1.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/CHANGELOG.md +92 -0
- package/LICENSE +21 -0
- package/README.md +827 -0
- package/dist/__tests__/setup.d.ts +6 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +30 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/__tests__/test-config.d.ts +36 -0
- package/dist/__tests__/test-config.d.ts.map +1 -0
- package/dist/__tests__/test-config.js +563 -0
- package/dist/__tests__/test-config.js.map +1 -0
- package/dist/cli/index.d.ts +118 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +657 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +21 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/schemantic.d.ts +95 -0
- package/dist/core/schemantic.d.ts.map +1 -0
- package/dist/core/schemantic.js +543 -0
- package/dist/core/schemantic.js.map +1 -0
- package/dist/generators/api-client-generator.d.ts +145 -0
- package/dist/generators/api-client-generator.d.ts.map +1 -0
- package/dist/generators/api-client-generator.js +856 -0
- package/dist/generators/api-client-generator.js.map +1 -0
- package/dist/generators/base.d.ts +117 -0
- package/dist/generators/base.d.ts.map +1 -0
- package/dist/generators/base.js +232 -0
- package/dist/generators/base.js.map +1 -0
- package/dist/generators/enum-generator.d.ts +52 -0
- package/dist/generators/enum-generator.d.ts.map +1 -0
- package/dist/generators/enum-generator.js +178 -0
- package/dist/generators/enum-generator.js.map +1 -0
- package/dist/generators/factory.d.ts +59 -0
- package/dist/generators/factory.d.ts.map +1 -0
- package/dist/generators/factory.js +127 -0
- package/dist/generators/factory.js.map +1 -0
- package/dist/generators/hook-generator.d.ts +36 -0
- package/dist/generators/hook-generator.d.ts.map +1 -0
- package/dist/generators/hook-generator.js +495 -0
- package/dist/generators/hook-generator.js.map +1 -0
- package/dist/generators/index.d.ts +10 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +26 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/object-generator.d.ts +48 -0
- package/dist/generators/object-generator.d.ts.map +1 -0
- package/dist/generators/object-generator.js +280 -0
- package/dist/generators/object-generator.js.map +1 -0
- package/dist/generators/primitive-generator.d.ts +40 -0
- package/dist/generators/primitive-generator.d.ts.map +1 -0
- package/dist/generators/primitive-generator.js +150 -0
- package/dist/generators/primitive-generator.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/base.d.ts +101 -0
- package/dist/parsers/base.d.ts.map +1 -0
- package/dist/parsers/base.js +77 -0
- package/dist/parsers/base.js.map +1 -0
- package/dist/parsers/factory.d.ts +64 -0
- package/dist/parsers/factory.d.ts.map +1 -0
- package/dist/parsers/factory.js +116 -0
- package/dist/parsers/factory.js.map +1 -0
- package/dist/parsers/index.d.ts +7 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +23 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/openapi.d.ts +52 -0
- package/dist/parsers/openapi.d.ts.map +1 -0
- package/dist/parsers/openapi.js +396 -0
- package/dist/parsers/openapi.js.map +1 -0
- package/dist/plugins/branded-types.d.ts +28 -0
- package/dist/plugins/branded-types.d.ts.map +1 -0
- package/dist/plugins/branded-types.js +568 -0
- package/dist/plugins/branded-types.js.map +1 -0
- package/dist/plugins/builtin.d.ts +38 -0
- package/dist/plugins/builtin.d.ts.map +1 -0
- package/dist/plugins/builtin.js +283 -0
- package/dist/plugins/builtin.js.map +1 -0
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +23 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/loader.d.ts +92 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +245 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/manager.d.ts +99 -0
- package/dist/plugins/manager.d.ts.map +1 -0
- package/dist/plugins/manager.js +233 -0
- package/dist/plugins/manager.js.map +1 -0
- package/dist/plugins/performance-monitoring.d.ts +98 -0
- package/dist/plugins/performance-monitoring.d.ts.map +1 -0
- package/dist/plugins/performance-monitoring.js +596 -0
- package/dist/plugins/performance-monitoring.js.map +1 -0
- package/dist/plugins/request-deduplication.d.ts +27 -0
- package/dist/plugins/request-deduplication.d.ts.map +1 -0
- package/dist/plugins/request-deduplication.js +650 -0
- package/dist/plugins/request-deduplication.js.map +1 -0
- package/dist/plugins/zod-validation.d.ts +63 -0
- package/dist/plugins/zod-validation.d.ts.map +1 -0
- package/dist/plugins/zod-validation.js +743 -0
- package/dist/plugins/zod-validation.js.map +1 -0
- package/dist/types/core.d.ts +203 -0
- package/dist/types/core.d.ts.map +1 -0
- package/dist/types/core.js +24 -0
- package/dist/types/core.js.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/openapi.d.ts +250 -0
- package/dist/types/openapi.d.ts.map +1 -0
- package/dist/types/openapi.js +8 -0
- package/dist/types/openapi.js.map +1 -0
- package/dist/types/schema.d.ts +68 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +22 -0
- package/dist/types/schema.js.map +1 -0
- package/docs/ADVANCED_PLUGINS_IMPLEMENTATION.md +355 -0
- package/docs/ADVANCED_PLUGIN_DEVELOPMENT.md +309 -0
- package/docs/API.md +600 -0
- package/docs/CONFIGURATION.md +722 -0
- package/docs/FASTAPI_TUTORIAL.md +1497 -0
- package/docs/PLUGINS.md +683 -0
- package/docs/PLUGIN_DEVELOPMENT.md +720 -0
- package/package.json +90 -0
|
@@ -0,0 +1,743 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Advanced Zod Validation Plugin
|
|
4
|
+
*
|
|
5
|
+
* Generates Zod schemas from OpenAPI specifications for runtime validation.
|
|
6
|
+
* Implements sophisticated type-level validation mapping with performance optimizations.
|
|
7
|
+
*
|
|
8
|
+
* Key Features:
|
|
9
|
+
* - Type-safe request/response validation pipelines
|
|
10
|
+
* - Performance-optimized validation caching
|
|
11
|
+
* - Branded type integration for compile-time safety
|
|
12
|
+
* - Runtime type guard generation
|
|
13
|
+
*
|
|
14
|
+
* Architecture:
|
|
15
|
+
* - Schema transformation with memoization
|
|
16
|
+
* - Lazy evaluation for expensive operations
|
|
17
|
+
* - Memory-efficient validation cache management
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.zodValidationPlugin = void 0;
|
|
21
|
+
exports.memoizedValidation = memoizedValidation;
|
|
22
|
+
exports.getZodValidationPerformanceStats = getZodValidationPerformanceStats;
|
|
23
|
+
exports.clearZodValidationCaches = clearZodValidationCaches;
|
|
24
|
+
exports.getValidationCacheStats = getValidationCacheStats;
|
|
25
|
+
const schema_1 = require("../types/schema");
|
|
26
|
+
/**
|
|
27
|
+
* Schema cache for performance optimization
|
|
28
|
+
*/
|
|
29
|
+
const schemaCache = new Map();
|
|
30
|
+
/**
|
|
31
|
+
* Memoized schema transformation for performance
|
|
32
|
+
*/
|
|
33
|
+
const memoizedSchemaTransform = new Map();
|
|
34
|
+
/**
|
|
35
|
+
* Performance metric recording
|
|
36
|
+
*/
|
|
37
|
+
const performanceMetrics = new Map();
|
|
38
|
+
function recordPerformanceMetric(operation, duration) {
|
|
39
|
+
if (!performanceMetrics.has(operation)) {
|
|
40
|
+
performanceMetrics.set(operation, []);
|
|
41
|
+
}
|
|
42
|
+
performanceMetrics.get(operation).push(duration);
|
|
43
|
+
// Keep only last 100 measurements
|
|
44
|
+
const measurements = performanceMetrics.get(operation);
|
|
45
|
+
if (measurements.length > 100) {
|
|
46
|
+
measurements.splice(0, measurements.length - 100);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Advanced Zod Validation Plugin Implementation
|
|
51
|
+
*/
|
|
52
|
+
exports.zodValidationPlugin = {
|
|
53
|
+
name: "zod-validation",
|
|
54
|
+
version: "2.0.0",
|
|
55
|
+
description: "Advanced Zod schema generation with performance optimizations and type safety enhancements",
|
|
56
|
+
/**
|
|
57
|
+
* Transform schema to include Zod validation metadata
|
|
58
|
+
*/
|
|
59
|
+
transformSchema: (schema, context) => {
|
|
60
|
+
const startTime = performance.now();
|
|
61
|
+
try {
|
|
62
|
+
const options = getPluginOptions(context);
|
|
63
|
+
const schemaKey = generateSchemaKey(schema);
|
|
64
|
+
// Use memoized transformation for performance
|
|
65
|
+
if (options.cacheSchemas && memoizedSchemaTransform.has(schemaKey)) {
|
|
66
|
+
return memoizedSchemaTransform.get(schemaKey);
|
|
67
|
+
}
|
|
68
|
+
const transformedSchema = transformSchemaForZod(schema, options);
|
|
69
|
+
if (options.cacheSchemas) {
|
|
70
|
+
memoizedSchemaTransform.set(schemaKey, transformedSchema);
|
|
71
|
+
}
|
|
72
|
+
return transformedSchema;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.warn(`Zod validation plugin transformSchema failed:`, error);
|
|
76
|
+
return schema;
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
const endTime = performance.now();
|
|
80
|
+
recordPerformanceMetric("transformSchema", endTime - startTime);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
/**
|
|
84
|
+
* Enhance type generation with Zod schemas
|
|
85
|
+
*/
|
|
86
|
+
afterTypeGeneration: async (typeName, generatedType, context) => {
|
|
87
|
+
const startTime = performance.now();
|
|
88
|
+
try {
|
|
89
|
+
const options = getPluginOptions(context);
|
|
90
|
+
// Build schema using core builder that handles unions/objects/primitives
|
|
91
|
+
const srcSchema = generatedType.sourceSchema;
|
|
92
|
+
const zodSchema = await generateZodSchema(typeName, srcSchema, options, context);
|
|
93
|
+
// Generate branded type if enabled
|
|
94
|
+
const brandedType = options.generateBrandedTypes
|
|
95
|
+
? generateBrandedType(typeName, generatedType)
|
|
96
|
+
: "";
|
|
97
|
+
// Generate type guard if enabled
|
|
98
|
+
const typeGuard = options.generateTypeGuards
|
|
99
|
+
? generateTypeGuard(typeName, zodSchema)
|
|
100
|
+
: "";
|
|
101
|
+
// Add Zod imports and schemas
|
|
102
|
+
const zodImports = generateZodImports(options);
|
|
103
|
+
const validationModule = generateValidationModule(typeName, zodSchema, brandedType, typeGuard, options);
|
|
104
|
+
// Update generated type content
|
|
105
|
+
generatedType.content = [
|
|
106
|
+
zodImports,
|
|
107
|
+
generatedType.content,
|
|
108
|
+
validationModule,
|
|
109
|
+
]
|
|
110
|
+
.filter(Boolean)
|
|
111
|
+
.join("\n\n");
|
|
112
|
+
// Update dependencies
|
|
113
|
+
generatedType.dependencies.push("zod");
|
|
114
|
+
// Add branded type exports if enabled
|
|
115
|
+
if (options.generateBrandedTypes) {
|
|
116
|
+
generatedType.exports.push(`Branded${typeName}`);
|
|
117
|
+
}
|
|
118
|
+
// Add validation exports
|
|
119
|
+
generatedType.exports.push(`${typeName}Schema`, `validate${typeName}`, `parse${typeName}`);
|
|
120
|
+
if (options.generateTypeGuards) {
|
|
121
|
+
generatedType.exports.push(`is${typeName}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.warn(`Zod validation plugin afterTypeGeneration failed for ${typeName}:`, error);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
const endTime = performance.now();
|
|
129
|
+
recordPerformanceMetric("afterTypeGeneration", endTime - startTime);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
/**
|
|
133
|
+
* Enhance API client with validation integration
|
|
134
|
+
*/
|
|
135
|
+
afterClientGeneration: async (generatedClient, context) => {
|
|
136
|
+
const startTime = performance.now();
|
|
137
|
+
try {
|
|
138
|
+
const options = getPluginOptions(context);
|
|
139
|
+
// Add validation middleware to client
|
|
140
|
+
const validationMiddleware = generateValidationMiddleware(generatedClient, context, options);
|
|
141
|
+
// Add request/response validation helpers
|
|
142
|
+
const validationHelpers = generateValidationHelpers(options);
|
|
143
|
+
// Update client content with validation integration
|
|
144
|
+
generatedClient.content = [
|
|
145
|
+
generatedClient.content,
|
|
146
|
+
validationMiddleware,
|
|
147
|
+
validationHelpers,
|
|
148
|
+
]
|
|
149
|
+
.filter(Boolean)
|
|
150
|
+
.join("\n\n");
|
|
151
|
+
// Update dependencies
|
|
152
|
+
if (!generatedClient.dependencies.includes("zod")) {
|
|
153
|
+
generatedClient.dependencies.push("zod");
|
|
154
|
+
}
|
|
155
|
+
// Add validation exports
|
|
156
|
+
generatedClient.exports.push("validateRequest", "validateResponse", "ValidationError");
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
console.warn(`Zod validation plugin afterClientGeneration failed:`, error);
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
const endTime = performance.now();
|
|
163
|
+
recordPerformanceMetric("afterClientGeneration", endTime - startTime);
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Get plugin options from generation context
|
|
169
|
+
*/
|
|
170
|
+
function getPluginOptions(context) {
|
|
171
|
+
const pluginConfig = context.config.plugins?.find((p) => p.name === "zod-validation");
|
|
172
|
+
return {
|
|
173
|
+
strictMode: true,
|
|
174
|
+
cacheSchemas: true,
|
|
175
|
+
generateBrandedTypes: true,
|
|
176
|
+
generateTypeGuards: true,
|
|
177
|
+
optimizeBundle: true,
|
|
178
|
+
customErrorMessages: {},
|
|
179
|
+
...(pluginConfig?.options || {}),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Generate a unique key for schema caching
|
|
184
|
+
*/
|
|
185
|
+
function generateSchemaKey(schema) {
|
|
186
|
+
return JSON.stringify(schema);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Transform schema to include Zod validation metadata
|
|
190
|
+
*/
|
|
191
|
+
function transformSchemaForZod(schema, options) {
|
|
192
|
+
if (!(0, schema_1.isOpenAPISchemaObject)(schema)) {
|
|
193
|
+
return schema;
|
|
194
|
+
}
|
|
195
|
+
const transformedSchema = { ...schema };
|
|
196
|
+
// Add Zod validation metadata
|
|
197
|
+
transformedSchema._zodValidation = {
|
|
198
|
+
strictMode: options.strictMode,
|
|
199
|
+
customMessages: options.customErrorMessages,
|
|
200
|
+
generated: true,
|
|
201
|
+
};
|
|
202
|
+
// Process properties recursively
|
|
203
|
+
if (schema.properties) {
|
|
204
|
+
transformedSchema.properties = {};
|
|
205
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
206
|
+
if (typeof propSchema === "object" && propSchema !== null) {
|
|
207
|
+
transformedSchema.properties[propName] = transformSchemaForZod(propSchema, options);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
transformedSchema.properties[propName] = propSchema;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return transformedSchema;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Generate Zod schema for a type
|
|
218
|
+
*/
|
|
219
|
+
async function generateZodSchema(typeName, schema, options, context) {
|
|
220
|
+
const cacheKey = `${typeName}-${generateSchemaKey(schema)}`;
|
|
221
|
+
if (options.cacheSchemas && schemaCache.has(cacheKey)) {
|
|
222
|
+
return schemaCache.get(cacheKey);
|
|
223
|
+
}
|
|
224
|
+
const zodSchema = buildZodSchema(schema, options, 0, context);
|
|
225
|
+
if (options.cacheSchemas) {
|
|
226
|
+
schemaCache.set(cacheKey, zodSchema);
|
|
227
|
+
}
|
|
228
|
+
return zodSchema;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Build Zod schema from OpenAPI schema
|
|
232
|
+
*/
|
|
233
|
+
function buildZodSchema(schema, options, depth = 0, context) {
|
|
234
|
+
// Handle oneOf/discriminated unions early
|
|
235
|
+
if ((0, schema_1.isOpenAPISchemaObject)(schema)) {
|
|
236
|
+
const obj = schema;
|
|
237
|
+
if (Array.isArray(obj.oneOf) && obj.oneOf.length > 0) {
|
|
238
|
+
return buildOneOfSchema(obj, options, context);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Handle schema references first
|
|
242
|
+
if (typeof schema === "object" &&
|
|
243
|
+
schema !== null &&
|
|
244
|
+
"$ref" in schema &&
|
|
245
|
+
schema.$ref) {
|
|
246
|
+
// Extract type name from reference and generate schema reference
|
|
247
|
+
const refPath = schema.$ref;
|
|
248
|
+
const typeName = refPath.split("/").pop();
|
|
249
|
+
// Try to resolve and inline if it's a union (oneOf)
|
|
250
|
+
if (context?.schemaResolver) {
|
|
251
|
+
const resolved = context.schemaResolver(refPath);
|
|
252
|
+
if (resolved && (0, schema_1.isOpenAPISchemaObject)(resolved)) {
|
|
253
|
+
const obj = resolved;
|
|
254
|
+
if (Array.isArray(obj.oneOf) && obj.oneOf.length > 0) {
|
|
255
|
+
return buildOneOfSchema(obj, options, context);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (typeName) {
|
|
260
|
+
const formattedTypeName = formatTypeName(typeName);
|
|
261
|
+
return `${formattedTypeName}Schema`;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (!(0, schema_1.isOpenAPISchemaObject)(schema)) {
|
|
265
|
+
return "z.unknown()";
|
|
266
|
+
}
|
|
267
|
+
// Prevent infinite recursion
|
|
268
|
+
if (depth > 10) {
|
|
269
|
+
return "z.unknown()";
|
|
270
|
+
}
|
|
271
|
+
switch (schema.type) {
|
|
272
|
+
case "string":
|
|
273
|
+
return buildStringSchema(schema, options);
|
|
274
|
+
case "number":
|
|
275
|
+
case "integer":
|
|
276
|
+
return buildNumberSchema(schema, options);
|
|
277
|
+
case "boolean":
|
|
278
|
+
return "z.boolean()";
|
|
279
|
+
case "array": {
|
|
280
|
+
const itemSchema = schema.items
|
|
281
|
+
? buildZodSchema(schema.items, options, depth + 1, context)
|
|
282
|
+
: "z.unknown()";
|
|
283
|
+
return `z.array(${itemSchema})`;
|
|
284
|
+
}
|
|
285
|
+
case "object":
|
|
286
|
+
return buildObjectSchema(schema, options, depth, context);
|
|
287
|
+
default:
|
|
288
|
+
return "z.unknown()";
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Helper function to format type names consistently
|
|
293
|
+
*/
|
|
294
|
+
function formatTypeName(name) {
|
|
295
|
+
// Convert to PascalCase and add API prefix if not present.
|
|
296
|
+
// Preserve internal capitalization (e.g., UserRole -> UserRole).
|
|
297
|
+
const tokens = name.split(/[-_\s]+/).filter(Boolean);
|
|
298
|
+
const pascalCase = tokens
|
|
299
|
+
.map((word) => (word ? word.charAt(0).toUpperCase() + word.slice(1) : ""))
|
|
300
|
+
.join("");
|
|
301
|
+
return pascalCase.startsWith("API") ? pascalCase : `API${pascalCase}`;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Build Zod string schema with validation rules
|
|
305
|
+
*/
|
|
306
|
+
function buildStringSchema(schema, _options) {
|
|
307
|
+
let zodSchema = "z.string()";
|
|
308
|
+
if (schema.minLength !== undefined) {
|
|
309
|
+
zodSchema += `.min(${schema.minLength})`;
|
|
310
|
+
}
|
|
311
|
+
if (schema.maxLength !== undefined) {
|
|
312
|
+
zodSchema += `.max(${schema.maxLength})`;
|
|
313
|
+
}
|
|
314
|
+
if (schema.pattern) {
|
|
315
|
+
zodSchema += `.regex(/${schema.pattern}/)`;
|
|
316
|
+
}
|
|
317
|
+
if (schema.format) {
|
|
318
|
+
switch (schema.format) {
|
|
319
|
+
case "email":
|
|
320
|
+
zodSchema += ".email()";
|
|
321
|
+
break;
|
|
322
|
+
case "uri":
|
|
323
|
+
case "url":
|
|
324
|
+
zodSchema += ".url()";
|
|
325
|
+
break;
|
|
326
|
+
case "uuid":
|
|
327
|
+
zodSchema += ".uuid()";
|
|
328
|
+
break;
|
|
329
|
+
case "date-time":
|
|
330
|
+
zodSchema += ".datetime()";
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (schema.enum && Array.isArray(schema.enum)) {
|
|
335
|
+
if (schema.enum.length === 1) {
|
|
336
|
+
zodSchema = `z.literal(${JSON.stringify(schema.enum[0])})`;
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
const enumValues = schema.enum
|
|
340
|
+
.map((val) => JSON.stringify(val))
|
|
341
|
+
.join(", ");
|
|
342
|
+
zodSchema = `z.enum([${enumValues}])`;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Add custom error messages if configured
|
|
346
|
+
if (_options.customErrorMessages && schema.description) {
|
|
347
|
+
const customMessage = _options.customErrorMessages[schema.description];
|
|
348
|
+
if (customMessage) {
|
|
349
|
+
zodSchema += `.describe("${customMessage}")`;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return zodSchema;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Build Zod number schema with validation rules
|
|
356
|
+
*/
|
|
357
|
+
function buildNumberSchema(schema, _options) {
|
|
358
|
+
let zodSchema = schema.type === "integer" ? "z.number().int()" : "z.number()";
|
|
359
|
+
if (schema.minimum !== undefined) {
|
|
360
|
+
zodSchema += `.min(${schema.minimum})`;
|
|
361
|
+
}
|
|
362
|
+
if (schema.maximum !== undefined) {
|
|
363
|
+
zodSchema += `.max(${schema.maximum})`;
|
|
364
|
+
}
|
|
365
|
+
if (schema.multipleOf !== undefined) {
|
|
366
|
+
zodSchema += `.multipleOf(${schema.multipleOf})`;
|
|
367
|
+
}
|
|
368
|
+
return zodSchema;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Build Zod object schema
|
|
372
|
+
*/
|
|
373
|
+
function buildObjectSchema(schema, options, depth, context) {
|
|
374
|
+
if (!schema.properties) {
|
|
375
|
+
return "z.object({})";
|
|
376
|
+
}
|
|
377
|
+
const properties = [];
|
|
378
|
+
const required = schema.required || [];
|
|
379
|
+
const transformPairs = [];
|
|
380
|
+
const wantsCamel = context?.config?.namingConvention === "camelCase";
|
|
381
|
+
// Helper: convert prop name to camelCase similar to generator
|
|
382
|
+
const toCamel = (str) => {
|
|
383
|
+
if (!str)
|
|
384
|
+
return str;
|
|
385
|
+
// sanitize: collapse non-alnum into underscore, trim
|
|
386
|
+
const sanitized = str
|
|
387
|
+
.replace(/[^A-Za-z0-9]+/g, "_")
|
|
388
|
+
.replace(/^_+|_+$/g, "")
|
|
389
|
+
.replace(/_+/g, "_");
|
|
390
|
+
const parts = sanitized.split(/_+/).filter(Boolean);
|
|
391
|
+
if (parts.length === 0)
|
|
392
|
+
return "";
|
|
393
|
+
const head = parts[0] ?? "";
|
|
394
|
+
const tail = parts.slice(1);
|
|
395
|
+
const camelHead = head ? head.charAt(0).toLowerCase() + head.slice(1) : "";
|
|
396
|
+
const camelTail = tail.map((p) => p ? p.charAt(0).toUpperCase() + p.slice(1) : "");
|
|
397
|
+
return camelHead + camelTail.join("");
|
|
398
|
+
};
|
|
399
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
400
|
+
if (typeof propSchema === "object" && propSchema !== null) {
|
|
401
|
+
let propZodSchema = buildZodSchema(propSchema, options, depth + 1, context);
|
|
402
|
+
if (!required.includes(propName)) {
|
|
403
|
+
propZodSchema += ".optional()";
|
|
404
|
+
}
|
|
405
|
+
properties.push(` ${propName}: ${propZodSchema}`);
|
|
406
|
+
if (wantsCamel) {
|
|
407
|
+
const camel = toCamel(propName);
|
|
408
|
+
if (camel !== propName) {
|
|
409
|
+
transformPairs.push(` ${camel}: val[${JSON.stringify(propName)}]`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const strictMode = options.strictMode ? ".strict()" : "";
|
|
415
|
+
const base = `z.object({\n${properties.join(",\n")}\n})${strictMode}`;
|
|
416
|
+
if (wantsCamel && transformPairs.length > 0) {
|
|
417
|
+
return `${base}.transform((val) => ({\n${transformPairs.join(",\n")}\n}))`;
|
|
418
|
+
}
|
|
419
|
+
return base;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Build Zod union/discriminatedUnion schema for oneOf
|
|
423
|
+
*/
|
|
424
|
+
function buildOneOfSchema(schema, options, context) {
|
|
425
|
+
const variants = (schema.oneOf || []).map((variant) => {
|
|
426
|
+
if (typeof variant === "object" &&
|
|
427
|
+
variant !== null &&
|
|
428
|
+
"$ref" in variant) {
|
|
429
|
+
const refPath = variant["$ref"];
|
|
430
|
+
const name = refPath.split("/").pop() || "Unknown";
|
|
431
|
+
return `${formatTypeName(name)}Schema`;
|
|
432
|
+
}
|
|
433
|
+
return buildZodSchema(variant, options, 0, context);
|
|
434
|
+
});
|
|
435
|
+
const discriminator = schema.discriminator?.propertyName;
|
|
436
|
+
if (discriminator) {
|
|
437
|
+
return `z.discriminatedUnion(${JSON.stringify(discriminator)}, [${variants.join(", ")}])`;
|
|
438
|
+
}
|
|
439
|
+
return `z.union([${variants.join(", ")}])`;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Generate branded type for compile-time safety
|
|
443
|
+
*/
|
|
444
|
+
function generateBrandedType(typeName, _generatedType) {
|
|
445
|
+
return `
|
|
446
|
+
/**
|
|
447
|
+
* Branded type for ${typeName} with compile-time guarantees
|
|
448
|
+
*/
|
|
449
|
+
export type Branded${typeName} = ${typeName} & { __brand: '${typeName}' };
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Create a branded ${typeName} instance
|
|
453
|
+
*/
|
|
454
|
+
export function createBranded${typeName}(data: ${typeName}): Branded${typeName} {
|
|
455
|
+
return data as Branded${typeName};
|
|
456
|
+
}`;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Generate runtime type guard
|
|
460
|
+
*/
|
|
461
|
+
function generateTypeGuard(typeName, _zodSchema) {
|
|
462
|
+
return `
|
|
463
|
+
/**
|
|
464
|
+
* Runtime type guard for ${typeName}
|
|
465
|
+
*/
|
|
466
|
+
export function is${typeName}(value: unknown): value is ${typeName} {
|
|
467
|
+
return ${typeName}Schema.safeParse(value).success;
|
|
468
|
+
}`;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Generate Zod imports based on options
|
|
472
|
+
*/
|
|
473
|
+
function generateZodImports(options) {
|
|
474
|
+
const imports = ["z"];
|
|
475
|
+
if (options.generateBrandedTypes) {
|
|
476
|
+
imports.push("ZodType");
|
|
477
|
+
}
|
|
478
|
+
return `import { ${imports.join(", ")} } from 'zod';`;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Generate validation module for a type
|
|
482
|
+
*/
|
|
483
|
+
function generateValidationModule(typeName, zodSchema, brandedType, typeGuard, _options) {
|
|
484
|
+
return `
|
|
485
|
+
/**
|
|
486
|
+
* Zod validation schema for ${typeName}
|
|
487
|
+
*/
|
|
488
|
+
export const ${typeName}Schema = ${zodSchema};
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Validate ${typeName} data with detailed error reporting
|
|
492
|
+
*/
|
|
493
|
+
export function validate${typeName}(data: unknown): { success: true; data: ${typeName} } | { success: false; errors: string[] } {
|
|
494
|
+
const result = ${typeName}Schema.safeParse(data);
|
|
495
|
+
|
|
496
|
+
if (result.success) {
|
|
497
|
+
return { success: true, data: result.data };
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
success: false,
|
|
502
|
+
errors: result.error.errors.map(err => \`\${err.path.join('.')}: \${err.message}\`)
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Parse ${typeName} data with exception on validation failure
|
|
508
|
+
*/
|
|
509
|
+
export function parse${typeName}(data: unknown): ${typeName} {
|
|
510
|
+
return ${typeName}Schema.parse(data);
|
|
511
|
+
}${brandedType}${typeGuard}`;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Generate validation middleware for API client
|
|
515
|
+
*/
|
|
516
|
+
function generateValidationMiddleware(_generatedClient, _context, options) {
|
|
517
|
+
// Ensure z/ZodType import is present in client content
|
|
518
|
+
if (!_generatedClient.content.includes("from 'zod'")) {
|
|
519
|
+
_generatedClient.content =
|
|
520
|
+
`import { z, ZodType } from 'zod';\n` + _generatedClient.content;
|
|
521
|
+
}
|
|
522
|
+
else if (!_generatedClient.content.includes("ZodType")) {
|
|
523
|
+
// Append ZodType to existing import if only z is imported
|
|
524
|
+
_generatedClient.content = _generatedClient.content.replace(/import\s*\{([^}]*)\}\s*from\s*['"]zod['"];?/, (_m, p1) => {
|
|
525
|
+
const names = p1
|
|
526
|
+
.split(",")
|
|
527
|
+
.map((s) => s.trim())
|
|
528
|
+
.filter(Boolean);
|
|
529
|
+
if (!names.includes("ZodType"))
|
|
530
|
+
names.push("ZodType");
|
|
531
|
+
return `import { ${names.join(", ")} } from 'zod';`;
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
const strictModeCode = options.strictMode
|
|
535
|
+
? 'throw new ValidationError(result.error.errors.map(err => `${err.path.join(".")}: ${err.message}`), data);'
|
|
536
|
+
: "// In non-strict mode, return data as-is";
|
|
537
|
+
return `
|
|
538
|
+
/**
|
|
539
|
+
* Validation middleware for API requests and responses
|
|
540
|
+
*/
|
|
541
|
+
export class ValidationError extends Error {
|
|
542
|
+
constructor(public errors: string[], public data: unknown) {
|
|
543
|
+
super(\`Validation failed: \${errors.join(', ')}\`);
|
|
544
|
+
this.name = 'ValidationError';
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Validate request data before sending
|
|
550
|
+
*/
|
|
551
|
+
export function validateRequest<T>(data: unknown, schema: ZodType<T>): T {
|
|
552
|
+
const result = schema.safeParse(data);
|
|
553
|
+
|
|
554
|
+
if (!result.success) {
|
|
555
|
+
throw new ValidationError(
|
|
556
|
+
result.error.errors.map(err => \`\${err.path.join('.')}: \${err.message}\`),
|
|
557
|
+
data
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return result.data;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Validate response data after receiving
|
|
566
|
+
*/
|
|
567
|
+
export function validateResponse<T>(data: unknown, schema: ZodType<T>): T {
|
|
568
|
+
const result = schema.safeParse(data);
|
|
569
|
+
|
|
570
|
+
if (!result.success) {
|
|
571
|
+
console.warn('Response validation failed:', result.error.errors);
|
|
572
|
+
${strictModeCode}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return result.data;
|
|
576
|
+
}`;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Generate validation helper functions
|
|
580
|
+
*/
|
|
581
|
+
function generateValidationHelpers(_options) {
|
|
582
|
+
return `
|
|
583
|
+
/**
|
|
584
|
+
* Utility functions for validation operations
|
|
585
|
+
*/
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Create a validation pipeline for multiple schemas
|
|
589
|
+
*/
|
|
590
|
+
export function createValidationPipeline<T>(...schemas: ZodType<unknown>[]): ZodType<T> {
|
|
591
|
+
return schemas.reduce((acc, schema) => acc.pipe(schema)) as ZodType<T>;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Lazy validation for performance optimization
|
|
596
|
+
*/
|
|
597
|
+
export function createLazyValidator<T>(schemaFactory: () => ZodType<T>) {
|
|
598
|
+
let schema: ZodType<T> | null = null;
|
|
599
|
+
|
|
600
|
+
return (data: unknown): T => {
|
|
601
|
+
if (!schema) {
|
|
602
|
+
schema = schemaFactory();
|
|
603
|
+
}
|
|
604
|
+
return schema.parse(data);
|
|
605
|
+
};
|
|
606
|
+
}`;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Memory-bounded validation cache with LRU eviction
|
|
610
|
+
*/
|
|
611
|
+
class BoundedValidationCache {
|
|
612
|
+
constructor(maxSize = 1000, ttl = 5 * 60 * 1000) {
|
|
613
|
+
this.cache = new Map();
|
|
614
|
+
this.accessOrder = [];
|
|
615
|
+
this.maxSize = maxSize;
|
|
616
|
+
this.ttl = ttl;
|
|
617
|
+
}
|
|
618
|
+
get(key) {
|
|
619
|
+
const entry = this.cache.get(key);
|
|
620
|
+
if (!entry)
|
|
621
|
+
return undefined;
|
|
622
|
+
const now = Date.now();
|
|
623
|
+
if (now - entry.timestamp > this.ttl) {
|
|
624
|
+
this.cache.delete(key);
|
|
625
|
+
this.removeFromAccessOrder(key);
|
|
626
|
+
return undefined;
|
|
627
|
+
}
|
|
628
|
+
// Update access time and move to end (most recently used)
|
|
629
|
+
entry.accessTime = now;
|
|
630
|
+
this.moveToEnd(key);
|
|
631
|
+
return entry;
|
|
632
|
+
}
|
|
633
|
+
set(key, value) {
|
|
634
|
+
const now = Date.now();
|
|
635
|
+
if (this.cache.has(key)) {
|
|
636
|
+
// Update existing entry
|
|
637
|
+
this.cache.set(key, { ...value, accessTime: now });
|
|
638
|
+
this.moveToEnd(key);
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
// Add new entry
|
|
642
|
+
if (this.cache.size >= this.maxSize) {
|
|
643
|
+
this.evictLRU();
|
|
644
|
+
}
|
|
645
|
+
this.cache.set(key, { ...value, accessTime: now });
|
|
646
|
+
this.accessOrder.push(key);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
evictLRU() {
|
|
650
|
+
if (this.accessOrder.length === 0)
|
|
651
|
+
return;
|
|
652
|
+
const lruKey = this.accessOrder.shift();
|
|
653
|
+
this.cache.delete(lruKey);
|
|
654
|
+
}
|
|
655
|
+
moveToEnd(key) {
|
|
656
|
+
this.removeFromAccessOrder(key);
|
|
657
|
+
this.accessOrder.push(key);
|
|
658
|
+
}
|
|
659
|
+
removeFromAccessOrder(key) {
|
|
660
|
+
const index = this.accessOrder.indexOf(key);
|
|
661
|
+
if (index > -1) {
|
|
662
|
+
this.accessOrder.splice(index, 1);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
clear() {
|
|
666
|
+
this.cache.clear();
|
|
667
|
+
this.accessOrder = [];
|
|
668
|
+
}
|
|
669
|
+
size() {
|
|
670
|
+
return this.cache.size;
|
|
671
|
+
}
|
|
672
|
+
cleanup() {
|
|
673
|
+
const now = Date.now();
|
|
674
|
+
const expiredKeys = [];
|
|
675
|
+
for (const [key, entry] of this.cache) {
|
|
676
|
+
if (now - entry.timestamp > this.ttl) {
|
|
677
|
+
expiredKeys.push(key);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
for (const key of expiredKeys) {
|
|
681
|
+
this.cache.delete(key);
|
|
682
|
+
this.removeFromAccessOrder(key);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
const validationCache = new BoundedValidationCache();
|
|
687
|
+
function memoizedValidation(data, schema, cacheKey, schemaVersion = "1.0") {
|
|
688
|
+
if (!cacheKey) {
|
|
689
|
+
return schema.parse(data);
|
|
690
|
+
}
|
|
691
|
+
const cached = validationCache.get(cacheKey);
|
|
692
|
+
if (cached && cached.schemaVersion === schemaVersion) {
|
|
693
|
+
return cached.result;
|
|
694
|
+
}
|
|
695
|
+
const result = schema.parse(data);
|
|
696
|
+
validationCache.set(cacheKey, {
|
|
697
|
+
result,
|
|
698
|
+
timestamp: Date.now(),
|
|
699
|
+
schemaVersion,
|
|
700
|
+
});
|
|
701
|
+
return result;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Get performance statistics for the plugin
|
|
705
|
+
*/
|
|
706
|
+
function getZodValidationPerformanceStats() {
|
|
707
|
+
const stats = {};
|
|
708
|
+
for (const [operation, measurements] of performanceMetrics) {
|
|
709
|
+
if (measurements.length > 0) {
|
|
710
|
+
stats[operation] = {
|
|
711
|
+
avg: measurements.reduce((sum, val) => sum + val, 0) / measurements.length,
|
|
712
|
+
min: Math.min(...measurements),
|
|
713
|
+
max: Math.max(...measurements),
|
|
714
|
+
count: measurements.length,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return stats;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Clear performance metrics and caches
|
|
722
|
+
*/
|
|
723
|
+
function clearZodValidationCaches() {
|
|
724
|
+
schemaCache.clear();
|
|
725
|
+
memoizedSchemaTransform.clear();
|
|
726
|
+
performanceMetrics.clear();
|
|
727
|
+
validationCache.clear();
|
|
728
|
+
// Optional: Trigger cleanup to remove expired entries
|
|
729
|
+
validationCache.cleanup();
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Get validation cache statistics for monitoring
|
|
733
|
+
*/
|
|
734
|
+
function getValidationCacheStats() {
|
|
735
|
+
return {
|
|
736
|
+
size: validationCache.size(),
|
|
737
|
+
maxSize: 1000, // TODO: Make configurable
|
|
738
|
+
// Additional metrics could be added with cache hit/miss tracking
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
// Export plugin for registration
|
|
742
|
+
exports.default = exports.zodValidationPlugin;
|
|
743
|
+
//# sourceMappingURL=zod-validation.js.map
|