zenko 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/LICENSE +21 -0
- package/README.md +243 -0
- package/dist/cli.cjs +898 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +875 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.cjs +729 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.mjs +702 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +73 -0
package/dist/index.mjs
ADDED
@@ -0,0 +1,702 @@
|
|
1
|
+
// src/utils/topological-sort.ts
|
2
|
+
function topologicalSort(schemas) {
|
3
|
+
const visited = /* @__PURE__ */ new Set();
|
4
|
+
const visiting = /* @__PURE__ */ new Set();
|
5
|
+
const result = [];
|
6
|
+
const visit = (name) => {
|
7
|
+
if (visited.has(name)) return;
|
8
|
+
if (visiting.has(name)) {
|
9
|
+
return;
|
10
|
+
}
|
11
|
+
visiting.add(name);
|
12
|
+
const schema = schemas[name];
|
13
|
+
const dependencies = extractDependencies(schema);
|
14
|
+
for (const dep of dependencies) {
|
15
|
+
if (schemas[dep]) {
|
16
|
+
visit(dep);
|
17
|
+
}
|
18
|
+
}
|
19
|
+
visiting.delete(name);
|
20
|
+
visited.add(name);
|
21
|
+
result.push(name);
|
22
|
+
};
|
23
|
+
for (const name of Object.keys(schemas)) {
|
24
|
+
visit(name);
|
25
|
+
}
|
26
|
+
return result;
|
27
|
+
}
|
28
|
+
function extractDependencies(schema) {
|
29
|
+
const dependencies = [];
|
30
|
+
const traverse = (obj) => {
|
31
|
+
if (typeof obj !== "object" || obj === null) return;
|
32
|
+
if (obj.$ref && typeof obj.$ref === "string") {
|
33
|
+
const refName = extractRefName(obj.$ref);
|
34
|
+
dependencies.push(refName);
|
35
|
+
return;
|
36
|
+
}
|
37
|
+
if (Array.isArray(obj)) {
|
38
|
+
obj.forEach(traverse);
|
39
|
+
} else {
|
40
|
+
Object.values(obj).forEach(traverse);
|
41
|
+
}
|
42
|
+
};
|
43
|
+
traverse(schema);
|
44
|
+
return [...new Set(dependencies)];
|
45
|
+
}
|
46
|
+
function extractRefName(ref) {
|
47
|
+
return ref.split("/").pop() || "Unknown";
|
48
|
+
}
|
49
|
+
|
50
|
+
// src/utils/property-name.ts
|
51
|
+
function isValidJSIdentifier(name) {
|
52
|
+
if (!name) return false;
|
53
|
+
const firstChar = name.at(0);
|
54
|
+
if (firstChar === void 0) return false;
|
55
|
+
if (!/[a-zA-Z_$]/.test(firstChar)) return false;
|
56
|
+
if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) return false;
|
57
|
+
const reservedWords = /* @__PURE__ */ new Set([
|
58
|
+
"abstract",
|
59
|
+
"arguments",
|
60
|
+
"await",
|
61
|
+
"boolean",
|
62
|
+
"break",
|
63
|
+
"byte",
|
64
|
+
"case",
|
65
|
+
"catch",
|
66
|
+
"char",
|
67
|
+
"class",
|
68
|
+
"const",
|
69
|
+
"continue",
|
70
|
+
"debugger",
|
71
|
+
"default",
|
72
|
+
"delete",
|
73
|
+
"do",
|
74
|
+
"double",
|
75
|
+
"else",
|
76
|
+
"enum",
|
77
|
+
"eval",
|
78
|
+
"export",
|
79
|
+
"extends",
|
80
|
+
"false",
|
81
|
+
"final",
|
82
|
+
"finally",
|
83
|
+
"float",
|
84
|
+
"for",
|
85
|
+
"function",
|
86
|
+
"goto",
|
87
|
+
"if",
|
88
|
+
"implements",
|
89
|
+
"import",
|
90
|
+
"in",
|
91
|
+
"instanceof",
|
92
|
+
"int",
|
93
|
+
"interface",
|
94
|
+
"let",
|
95
|
+
"long",
|
96
|
+
"native",
|
97
|
+
"new",
|
98
|
+
"null",
|
99
|
+
"package",
|
100
|
+
"private",
|
101
|
+
"protected",
|
102
|
+
"public",
|
103
|
+
"return",
|
104
|
+
"short",
|
105
|
+
"static",
|
106
|
+
"super",
|
107
|
+
"switch",
|
108
|
+
"synchronized",
|
109
|
+
"this",
|
110
|
+
"throw",
|
111
|
+
"throws",
|
112
|
+
"transient",
|
113
|
+
"true",
|
114
|
+
"try",
|
115
|
+
"typeof",
|
116
|
+
"var",
|
117
|
+
"void",
|
118
|
+
"volatile",
|
119
|
+
"while",
|
120
|
+
"with",
|
121
|
+
"yield",
|
122
|
+
"async"
|
123
|
+
]);
|
124
|
+
return !reservedWords.has(name);
|
125
|
+
}
|
126
|
+
function formatPropertyName(name) {
|
127
|
+
return isValidJSIdentifier(name) ? name : `"${name}"`;
|
128
|
+
}
|
129
|
+
|
130
|
+
// src/utils/http-status.ts
|
131
|
+
var statusNameMap = {
|
132
|
+
"400": "badRequest",
|
133
|
+
"401": "unauthorized",
|
134
|
+
"402": "paymentRequired",
|
135
|
+
"403": "forbidden",
|
136
|
+
"404": "notFound",
|
137
|
+
"405": "methodNotAllowed",
|
138
|
+
"406": "notAcceptable",
|
139
|
+
"407": "proxyAuthenticationRequired",
|
140
|
+
"408": "requestTimeout",
|
141
|
+
"409": "conflict",
|
142
|
+
"410": "gone",
|
143
|
+
"411": "lengthRequired",
|
144
|
+
"412": "preconditionFailed",
|
145
|
+
"413": "payloadTooLarge",
|
146
|
+
"414": "uriTooLong",
|
147
|
+
"415": "unsupportedMediaType",
|
148
|
+
"416": "rangeNotSatisfiable",
|
149
|
+
"417": "expectationFailed",
|
150
|
+
"418": "imATeapot",
|
151
|
+
"421": "misdirectedRequest",
|
152
|
+
"422": "unprocessableEntity",
|
153
|
+
"423": "locked",
|
154
|
+
"424": "failedDependency",
|
155
|
+
"425": "tooEarly",
|
156
|
+
"426": "upgradeRequired",
|
157
|
+
"428": "preconditionRequired",
|
158
|
+
"429": "tooManyRequests",
|
159
|
+
"431": "requestHeaderFieldsTooLarge",
|
160
|
+
"451": "unavailableForLegalReasons",
|
161
|
+
"500": "internalServerError",
|
162
|
+
"501": "notImplemented",
|
163
|
+
"502": "badGateway",
|
164
|
+
"503": "serviceUnavailable",
|
165
|
+
"504": "gatewayTimeout",
|
166
|
+
"505": "httpVersionNotSupported",
|
167
|
+
"506": "variantAlsoNegotiates",
|
168
|
+
"507": "insufficientStorage",
|
169
|
+
"508": "loopDetected",
|
170
|
+
"510": "notExtended",
|
171
|
+
"511": "networkAuthenticationRequired"
|
172
|
+
};
|
173
|
+
function mapStatusToIdentifier(status) {
|
174
|
+
if (status === "default") return "defaultError";
|
175
|
+
const trimmed = status.trim();
|
176
|
+
const mapped = statusNameMap[trimmed];
|
177
|
+
if (mapped) return mapped;
|
178
|
+
if (/^\d{3}$/.test(trimmed)) {
|
179
|
+
return `status${trimmed}`;
|
180
|
+
}
|
181
|
+
const sanitized = trimmed.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
182
|
+
if (!sanitized) return "unknownError";
|
183
|
+
const parts = sanitized.split(/\s+/);
|
184
|
+
const [first, ...rest] = parts;
|
185
|
+
const candidate = first + rest.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
|
186
|
+
if (!candidate) return "unknownError";
|
187
|
+
return /^[a-zA-Z_$]/.test(candidate) ? candidate : `status${candidate.charAt(0).toUpperCase()}${candidate.slice(1)}`;
|
188
|
+
}
|
189
|
+
function getStatusCategory(status) {
|
190
|
+
if (status === "default") return "default";
|
191
|
+
const code = Number(status);
|
192
|
+
if (!Number.isInteger(code)) return "unknown";
|
193
|
+
if (code >= 400 && code <= 499) return "client";
|
194
|
+
if (code >= 500 && code <= 599) return "server";
|
195
|
+
return "unknown";
|
196
|
+
}
|
197
|
+
function isErrorStatus(status) {
|
198
|
+
if (status === "default") return true;
|
199
|
+
const code = Number(status);
|
200
|
+
if (!Number.isInteger(code)) return false;
|
201
|
+
return code >= 400;
|
202
|
+
}
|
203
|
+
|
204
|
+
// src/zenko.ts
|
205
|
+
function generate(spec, options = {}) {
|
206
|
+
const output = [];
|
207
|
+
const generatedTypes = /* @__PURE__ */ new Set();
|
208
|
+
const { strictDates = false, strictNumeric = false } = options;
|
209
|
+
const schemaOptions = {
|
210
|
+
strictDates,
|
211
|
+
strictNumeric
|
212
|
+
};
|
213
|
+
output.push('import { z } from "zod";');
|
214
|
+
output.push("");
|
215
|
+
if (spec.components?.schemas) {
|
216
|
+
output.push("// Generated Zod Schemas");
|
217
|
+
output.push("");
|
218
|
+
const sortedSchemas = topologicalSort(spec.components.schemas);
|
219
|
+
for (const name of sortedSchemas) {
|
220
|
+
const schema = spec.components.schemas[name];
|
221
|
+
output.push(
|
222
|
+
generateZodSchema(name, schema, generatedTypes, schemaOptions)
|
223
|
+
);
|
224
|
+
output.push("");
|
225
|
+
output.push(`export type ${name} = z.infer<typeof ${name}>;`);
|
226
|
+
output.push("");
|
227
|
+
}
|
228
|
+
}
|
229
|
+
const operations = parseOperations(spec);
|
230
|
+
output.push("// Path Functions");
|
231
|
+
output.push("export const paths = {");
|
232
|
+
for (const op of operations) {
|
233
|
+
if (op.pathParams.length === 0) {
|
234
|
+
output.push(` ${op.operationId}: () => "${op.path}",`);
|
235
|
+
} else {
|
236
|
+
const paramNames = op.pathParams.map((p) => p.name).join(", ");
|
237
|
+
const paramTypes = op.pathParams.map((p) => `${p.name}: string`).join(", ");
|
238
|
+
const pathWithParams = op.path.replace(/{([^}]+)}/g, "${$1}");
|
239
|
+
output.push(
|
240
|
+
` ${op.operationId}: ({ ${paramNames} }: { ${paramTypes} }) => \`${pathWithParams}\`,`
|
241
|
+
);
|
242
|
+
}
|
243
|
+
}
|
244
|
+
output.push("} as const;");
|
245
|
+
output.push("");
|
246
|
+
output.push("// Header Functions");
|
247
|
+
output.push("export const headers = {");
|
248
|
+
for (const op of operations) {
|
249
|
+
if (!op.requestHeaders || op.requestHeaders.length === 0) {
|
250
|
+
output.push(` ${op.operationId}: () => ({}),`);
|
251
|
+
continue;
|
252
|
+
}
|
253
|
+
const typeEntries = op.requestHeaders.map(
|
254
|
+
(header) => `${formatPropertyName(header.name)}${header.required ? "" : "?"}: ${mapHeaderType(
|
255
|
+
header
|
256
|
+
)}`
|
257
|
+
).join(", ");
|
258
|
+
const requiredHeaders = op.requestHeaders.filter(
|
259
|
+
(header) => header.required
|
260
|
+
);
|
261
|
+
const optionalHeaders = op.requestHeaders.filter(
|
262
|
+
(header) => !header.required
|
263
|
+
);
|
264
|
+
const hasRequired = requiredHeaders.length > 0;
|
265
|
+
const signature = hasRequired ? `(params: { ${typeEntries} })` : `(params: { ${typeEntries} } = {})`;
|
266
|
+
if (optionalHeaders.length === 0) {
|
267
|
+
output.push(` ${op.operationId}: ${signature} => ({`);
|
268
|
+
for (const header of requiredHeaders) {
|
269
|
+
const propertyKey = formatPropertyName(header.name);
|
270
|
+
const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
|
271
|
+
output.push(` ${propertyKey}: ${accessor},`);
|
272
|
+
}
|
273
|
+
output.push(" }),");
|
274
|
+
continue;
|
275
|
+
}
|
276
|
+
if (!hasRequired && optionalHeaders.length === 1 && optionalHeaders[0]) {
|
277
|
+
const header = optionalHeaders[0];
|
278
|
+
const propertyKey = formatPropertyName(header.name);
|
279
|
+
const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
|
280
|
+
output.push(` ${op.operationId}: ${signature} =>`);
|
281
|
+
output.push(
|
282
|
+
` ${accessor} !== undefined ? { ${propertyKey}: ${accessor} } : {},`
|
283
|
+
);
|
284
|
+
continue;
|
285
|
+
}
|
286
|
+
const valueTypes = Array.from(
|
287
|
+
new Set(optionalHeaders.map((header) => mapHeaderType(header)))
|
288
|
+
).join(" | ");
|
289
|
+
output.push(` ${op.operationId}: ${signature} => {`);
|
290
|
+
if (hasRequired) {
|
291
|
+
output.push(" const headers = {");
|
292
|
+
for (const header of requiredHeaders) {
|
293
|
+
const propertyKey = formatPropertyName(header.name);
|
294
|
+
const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
|
295
|
+
output.push(` ${propertyKey}: ${accessor},`);
|
296
|
+
}
|
297
|
+
output.push(" }");
|
298
|
+
} else {
|
299
|
+
output.push(` const headers: Record<string, ${valueTypes}> = {}`);
|
300
|
+
}
|
301
|
+
for (const header of optionalHeaders) {
|
302
|
+
const propertyKey = formatPropertyName(header.name);
|
303
|
+
const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
|
304
|
+
const assignment = isValidJSIdentifier(header.name) ? `headers.${header.name}` : `headers[${propertyKey}]`;
|
305
|
+
output.push(` if (${accessor} !== undefined) {`);
|
306
|
+
output.push(` ${assignment} = ${accessor}`);
|
307
|
+
output.push(" }");
|
308
|
+
}
|
309
|
+
output.push(" return headers");
|
310
|
+
output.push(" },");
|
311
|
+
}
|
312
|
+
output.push("} as const;");
|
313
|
+
output.push("");
|
314
|
+
output.push("// Operation Objects");
|
315
|
+
for (const op of operations) {
|
316
|
+
output.push(`export const ${op.operationId} = {`);
|
317
|
+
output.push(` path: paths.${op.operationId},`);
|
318
|
+
appendOperationField(output, "request", op.requestType);
|
319
|
+
appendOperationField(output, "response", op.responseType);
|
320
|
+
if (op.requestHeaders && op.requestHeaders.length > 0) {
|
321
|
+
output.push(` headers: headers.${op.operationId},`);
|
322
|
+
}
|
323
|
+
if (op.errors && hasAnyErrors(op.errors)) {
|
324
|
+
output.push(" errors: {");
|
325
|
+
appendErrorGroup(output, "clientErrors", op.errors.clientErrors);
|
326
|
+
appendErrorGroup(output, "serverErrors", op.errors.serverErrors);
|
327
|
+
appendErrorGroup(output, "defaultErrors", op.errors.defaultErrors);
|
328
|
+
appendErrorGroup(output, "otherErrors", op.errors.otherErrors);
|
329
|
+
output.push(" },");
|
330
|
+
}
|
331
|
+
output.push("} as const;");
|
332
|
+
output.push("");
|
333
|
+
}
|
334
|
+
return output.join("\n");
|
335
|
+
}
|
336
|
+
function appendOperationField(buffer, key, value) {
|
337
|
+
if (!value) return;
|
338
|
+
buffer.push(` ${key}: ${value},`);
|
339
|
+
}
|
340
|
+
function appendErrorGroup(buffer, label, errors) {
|
341
|
+
if (!errors || Object.keys(errors).length === 0) return;
|
342
|
+
buffer.push(` ${label}: {`);
|
343
|
+
for (const [name, typeName] of Object.entries(errors)) {
|
344
|
+
buffer.push(` ${formatPropertyName(name)}: ${typeName},`);
|
345
|
+
}
|
346
|
+
buffer.push(" },");
|
347
|
+
}
|
348
|
+
function hasAnyErrors(group) {
|
349
|
+
return [
|
350
|
+
group.clientErrors,
|
351
|
+
group.serverErrors,
|
352
|
+
group.defaultErrors,
|
353
|
+
group.otherErrors
|
354
|
+
].some((bucket) => bucket && Object.keys(bucket).length > 0);
|
355
|
+
}
|
356
|
+
function parseOperations(spec) {
|
357
|
+
const operations = [];
|
358
|
+
for (const [path, pathItem] of Object.entries(spec.paths)) {
|
359
|
+
for (const [method, operation] of Object.entries(pathItem)) {
|
360
|
+
if (!operation.operationId) continue;
|
361
|
+
const pathParams = extractPathParams(path);
|
362
|
+
const requestType = getRequestType(operation);
|
363
|
+
const { successResponse, errors } = getResponseTypes(
|
364
|
+
operation,
|
365
|
+
operation.operationId
|
366
|
+
);
|
367
|
+
const resolvedParameters = collectParameters(pathItem, operation, spec);
|
368
|
+
const requestHeaders = getRequestHeaders(resolvedParameters);
|
369
|
+
operations.push({
|
370
|
+
operationId: operation.operationId,
|
371
|
+
path,
|
372
|
+
method: method.toLowerCase(),
|
373
|
+
pathParams,
|
374
|
+
requestType,
|
375
|
+
responseType: successResponse,
|
376
|
+
requestHeaders,
|
377
|
+
errors
|
378
|
+
});
|
379
|
+
}
|
380
|
+
}
|
381
|
+
return operations;
|
382
|
+
}
|
383
|
+
function collectParameters(pathItem, operation, spec) {
|
384
|
+
const parametersMap = /* @__PURE__ */ new Map();
|
385
|
+
const addParameters = (params) => {
|
386
|
+
if (!Array.isArray(params)) return;
|
387
|
+
for (const param of params) {
|
388
|
+
const resolved = resolveParameter(param, spec);
|
389
|
+
if (!resolved) continue;
|
390
|
+
const key = `${resolved.in}:${resolved.name}`;
|
391
|
+
parametersMap.set(key, resolved);
|
392
|
+
}
|
393
|
+
};
|
394
|
+
addParameters(pathItem.parameters);
|
395
|
+
addParameters(operation.parameters);
|
396
|
+
return Array.from(parametersMap.values());
|
397
|
+
}
|
398
|
+
function resolveParameter(parameter, spec) {
|
399
|
+
if (!parameter) return void 0;
|
400
|
+
if (parameter.$ref) {
|
401
|
+
const refName = extractRefName(parameter.$ref);
|
402
|
+
const resolved = spec.components?.parameters?.[refName];
|
403
|
+
if (!resolved) return void 0;
|
404
|
+
const { $ref, ...overrides } = parameter;
|
405
|
+
return {
|
406
|
+
...resolved,
|
407
|
+
...overrides
|
408
|
+
};
|
409
|
+
}
|
410
|
+
return parameter;
|
411
|
+
}
|
412
|
+
function extractPathParams(path) {
|
413
|
+
const params = [];
|
414
|
+
const matches = path.match(/{([^}]+)}/g);
|
415
|
+
if (matches) {
|
416
|
+
for (const match of matches) {
|
417
|
+
const paramName = match.slice(1, -1);
|
418
|
+
params.push({
|
419
|
+
name: paramName,
|
420
|
+
type: "string"
|
421
|
+
// OpenAPI path params are always strings
|
422
|
+
});
|
423
|
+
}
|
424
|
+
}
|
425
|
+
return params;
|
426
|
+
}
|
427
|
+
function getRequestType(operation) {
|
428
|
+
const requestBody = operation.requestBody?.content?.["application/json"]?.schema;
|
429
|
+
if (!requestBody) return void 0;
|
430
|
+
if (requestBody.$ref) {
|
431
|
+
return extractRefName(requestBody.$ref);
|
432
|
+
}
|
433
|
+
const typeName = `${capitalize(operation.operationId)}Request`;
|
434
|
+
return typeName;
|
435
|
+
}
|
436
|
+
function getResponseTypes(operation, operationId) {
|
437
|
+
const responses = operation.responses ?? {};
|
438
|
+
const successCodes = /* @__PURE__ */ new Map();
|
439
|
+
const errorEntries = [];
|
440
|
+
for (const [statusCode, response] of Object.entries(responses)) {
|
441
|
+
const resolvedSchema = response?.content?.["application/json"]?.schema;
|
442
|
+
if (!resolvedSchema) continue;
|
443
|
+
if (isErrorStatus(statusCode)) {
|
444
|
+
errorEntries.push({ code: statusCode, schema: resolvedSchema });
|
445
|
+
continue;
|
446
|
+
}
|
447
|
+
if (/^2\d\d$/.test(statusCode) || statusCode === "default") {
|
448
|
+
successCodes.set(statusCode, resolvedSchema);
|
449
|
+
}
|
450
|
+
}
|
451
|
+
const successResponse = selectSuccessResponse(successCodes, operationId);
|
452
|
+
const errors = buildErrorGroups(errorEntries, operationId);
|
453
|
+
return { successResponse, errors };
|
454
|
+
}
|
455
|
+
function selectSuccessResponse(responses, operationId) {
|
456
|
+
if (responses.size === 0) return void 0;
|
457
|
+
const preferredOrder = ["200", "201", "204"];
|
458
|
+
for (const code of preferredOrder) {
|
459
|
+
const schema = responses.get(code);
|
460
|
+
if (schema) {
|
461
|
+
return resolveResponseType(
|
462
|
+
schema,
|
463
|
+
`${capitalize(operationId)}Response${code}`
|
464
|
+
);
|
465
|
+
}
|
466
|
+
}
|
467
|
+
const [firstCode, firstSchema] = responses.entries().next().value ?? [];
|
468
|
+
if (!firstSchema) return void 0;
|
469
|
+
return resolveResponseType(
|
470
|
+
firstSchema,
|
471
|
+
`${capitalize(operationId)}Response${firstCode ?? "Default"}`
|
472
|
+
);
|
473
|
+
}
|
474
|
+
function buildErrorGroups(errors = [], operationId) {
|
475
|
+
if (!errors.length) return void 0;
|
476
|
+
const group = {};
|
477
|
+
for (const { code, schema } of errors) {
|
478
|
+
const category = getStatusCategory(code);
|
479
|
+
const identifier = mapStatusToIdentifier(code);
|
480
|
+
const typeName = resolveResponseType(
|
481
|
+
schema,
|
482
|
+
`${capitalize(operationId)}${capitalize(identifier)}`
|
483
|
+
);
|
484
|
+
switch (category) {
|
485
|
+
case "client":
|
486
|
+
group.clientErrors ??= {};
|
487
|
+
group.clientErrors[identifier] = typeName;
|
488
|
+
break;
|
489
|
+
case "server":
|
490
|
+
group.serverErrors ??= {};
|
491
|
+
group.serverErrors[identifier] = typeName;
|
492
|
+
break;
|
493
|
+
case "default":
|
494
|
+
group.defaultErrors ??= {};
|
495
|
+
group.defaultErrors[identifier] = typeName;
|
496
|
+
break;
|
497
|
+
default:
|
498
|
+
group.otherErrors ??= {};
|
499
|
+
group.otherErrors[identifier] = typeName;
|
500
|
+
break;
|
501
|
+
}
|
502
|
+
}
|
503
|
+
return group;
|
504
|
+
}
|
505
|
+
function resolveResponseType(schema, fallbackName) {
|
506
|
+
if (schema.$ref) {
|
507
|
+
return extractRefName(schema.$ref);
|
508
|
+
}
|
509
|
+
return fallbackName;
|
510
|
+
}
|
511
|
+
function getRequestHeaders(parameters) {
|
512
|
+
const headers = [];
|
513
|
+
for (const param of parameters ?? []) {
|
514
|
+
if (param.in === "header") {
|
515
|
+
headers.push({
|
516
|
+
name: param.name,
|
517
|
+
description: param.description,
|
518
|
+
schema: param.schema,
|
519
|
+
required: param.required
|
520
|
+
});
|
521
|
+
}
|
522
|
+
}
|
523
|
+
return headers;
|
524
|
+
}
|
525
|
+
function mapHeaderType(header) {
|
526
|
+
const schemaType = header.schema?.type;
|
527
|
+
switch (schemaType) {
|
528
|
+
case "integer":
|
529
|
+
case "number":
|
530
|
+
return "number";
|
531
|
+
case "boolean":
|
532
|
+
return "boolean";
|
533
|
+
default:
|
534
|
+
return "string";
|
535
|
+
}
|
536
|
+
}
|
537
|
+
function generateZodSchema(name, schema, generatedTypes, options) {
|
538
|
+
if (generatedTypes.has(name)) return "";
|
539
|
+
generatedTypes.add(name);
|
540
|
+
if (schema.enum) {
|
541
|
+
const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
|
542
|
+
return `export const ${name} = z.enum([${enumValues}]);`;
|
543
|
+
}
|
544
|
+
if (schema.type === "object" || schema.properties) {
|
545
|
+
const properties = [];
|
546
|
+
for (const [propName, propSchema] of Object.entries(
|
547
|
+
schema.properties || {}
|
548
|
+
)) {
|
549
|
+
const isRequired = schema.required?.includes(propName) ?? false;
|
550
|
+
const zodType = getZodTypeFromSchema(propSchema, options);
|
551
|
+
const finalType = isRequired ? zodType : `${zodType}.optional()`;
|
552
|
+
properties.push(` ${formatPropertyName(propName)}: ${finalType},`);
|
553
|
+
}
|
554
|
+
return `export const ${name} = z.object({
|
555
|
+
${properties.join("\n")}
|
556
|
+
});`;
|
557
|
+
}
|
558
|
+
if (schema.type === "array") {
|
559
|
+
const itemSchema = schema.items ?? { type: "unknown" };
|
560
|
+
const itemType = getZodTypeFromSchema(itemSchema, options);
|
561
|
+
const builder = applyStrictArrayBounds(
|
562
|
+
schema,
|
563
|
+
`z.array(${itemType})`,
|
564
|
+
itemSchema,
|
565
|
+
options.strictNumeric
|
566
|
+
);
|
567
|
+
return `export const ${name} = ${builder};`;
|
568
|
+
}
|
569
|
+
return `export const ${name} = ${getZodTypeFromSchema(schema, options)};`;
|
570
|
+
}
|
571
|
+
function getZodTypeFromSchema(schema, options) {
|
572
|
+
if (schema.$ref) {
|
573
|
+
return extractRefName(schema.$ref);
|
574
|
+
}
|
575
|
+
if (schema.enum) {
|
576
|
+
const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
|
577
|
+
return `z.enum([${enumValues}])`;
|
578
|
+
}
|
579
|
+
switch (schema.type) {
|
580
|
+
case "string":
|
581
|
+
return buildString(schema, options);
|
582
|
+
case "boolean":
|
583
|
+
return "z.boolean()";
|
584
|
+
case "array":
|
585
|
+
return `z.array(${getZodTypeFromSchema(
|
586
|
+
schema.items ?? { type: "unknown" },
|
587
|
+
options
|
588
|
+
)})`;
|
589
|
+
case "null":
|
590
|
+
return "z.null()";
|
591
|
+
case "number":
|
592
|
+
return buildNumber(schema, options);
|
593
|
+
case "integer":
|
594
|
+
return buildInteger(schema, options);
|
595
|
+
default:
|
596
|
+
return "z.unknown()";
|
597
|
+
}
|
598
|
+
}
|
599
|
+
function buildString(schema, options) {
|
600
|
+
if (options.strictDates) {
|
601
|
+
switch (schema.format) {
|
602
|
+
case "date-time":
|
603
|
+
return "z.string().datetime()";
|
604
|
+
case "date":
|
605
|
+
return "z.string().date()";
|
606
|
+
case "time":
|
607
|
+
return "z.string().time()";
|
608
|
+
case "duration":
|
609
|
+
return "z.string().duration()";
|
610
|
+
}
|
611
|
+
}
|
612
|
+
let builder = "z.string()";
|
613
|
+
if (options.strictNumeric) {
|
614
|
+
if (typeof schema.minLength === "number") {
|
615
|
+
builder += `.min(${schema.minLength})`;
|
616
|
+
}
|
617
|
+
if (typeof schema.maxLength === "number") {
|
618
|
+
builder += `.max(${schema.maxLength})`;
|
619
|
+
}
|
620
|
+
if (schema.pattern) {
|
621
|
+
builder += `.regex(new RegExp(${JSON.stringify(schema.pattern)}))`;
|
622
|
+
}
|
623
|
+
}
|
624
|
+
switch (schema.format) {
|
625
|
+
case "uuid":
|
626
|
+
return `${builder}.uuid()`;
|
627
|
+
case "email":
|
628
|
+
return `${builder}.email()`;
|
629
|
+
case "uri":
|
630
|
+
case "url":
|
631
|
+
return `${builder}.url()`;
|
632
|
+
case "ipv4":
|
633
|
+
return `${builder}.ip({ version: "v4" })`;
|
634
|
+
case "ipv6":
|
635
|
+
return `${builder}.ip({ version: "v6" })`;
|
636
|
+
default:
|
637
|
+
return builder;
|
638
|
+
}
|
639
|
+
}
|
640
|
+
function buildNumber(schema, options) {
|
641
|
+
let builder = "z.number()";
|
642
|
+
if (options.strictNumeric) {
|
643
|
+
builder = applyNumericBounds(schema, builder);
|
644
|
+
if (typeof schema.multipleOf === "number" && schema.multipleOf !== 0) {
|
645
|
+
builder += `.refine((value) => Math.abs(value / ${schema.multipleOf} - Math.round(value / ${schema.multipleOf})) < Number.EPSILON, { message: "Must be a multiple of ${schema.multipleOf}" })`;
|
646
|
+
}
|
647
|
+
}
|
648
|
+
return builder;
|
649
|
+
}
|
650
|
+
function buildInteger(schema, options) {
|
651
|
+
let builder = buildNumber(schema, options);
|
652
|
+
builder += ".int()";
|
653
|
+
return builder;
|
654
|
+
}
|
655
|
+
function applyStrictArrayBounds(schema, builder, itemSchema, enforceBounds) {
|
656
|
+
if (!enforceBounds) {
|
657
|
+
return builder;
|
658
|
+
}
|
659
|
+
if (typeof schema.minItems === "number") {
|
660
|
+
builder += `.min(${schema.minItems})`;
|
661
|
+
}
|
662
|
+
if (typeof schema.maxItems === "number") {
|
663
|
+
builder += `.max(${schema.maxItems})`;
|
664
|
+
}
|
665
|
+
if (schema.uniqueItems && isPrimitiveLike(itemSchema)) {
|
666
|
+
builder += '.refine((items) => new Set(items).size === items.length, { message: "Items must be unique" })';
|
667
|
+
}
|
668
|
+
return builder;
|
669
|
+
}
|
670
|
+
function isPrimitiveLike(schema) {
|
671
|
+
if (schema?.$ref) return false;
|
672
|
+
const primitiveTypes = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean"]);
|
673
|
+
return primitiveTypes.has(schema?.type);
|
674
|
+
}
|
675
|
+
function applyNumericBounds(schema, builder) {
|
676
|
+
if (typeof schema.minimum === "number") {
|
677
|
+
if (schema.exclusiveMinimum === true) {
|
678
|
+
builder += `.gt(${schema.minimum})`;
|
679
|
+
} else {
|
680
|
+
builder += `.min(${schema.minimum})`;
|
681
|
+
}
|
682
|
+
} else if (typeof schema.exclusiveMinimum === "number") {
|
683
|
+
builder += `.gt(${schema.exclusiveMinimum})`;
|
684
|
+
}
|
685
|
+
if (typeof schema.maximum === "number") {
|
686
|
+
if (schema.exclusiveMaximum === true) {
|
687
|
+
builder += `.lt(${schema.maximum})`;
|
688
|
+
} else {
|
689
|
+
builder += `.max(${schema.maximum})`;
|
690
|
+
}
|
691
|
+
} else if (typeof schema.exclusiveMaximum === "number") {
|
692
|
+
builder += `.lt(${schema.exclusiveMaximum})`;
|
693
|
+
}
|
694
|
+
return builder;
|
695
|
+
}
|
696
|
+
function capitalize(str) {
|
697
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
698
|
+
}
|
699
|
+
export {
|
700
|
+
generate
|
701
|
+
};
|
702
|
+
//# sourceMappingURL=index.mjs.map
|