@cerios/openapi-to-zod 0.1.1 → 0.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/dist/cli.mjs CHANGED
@@ -130,7 +130,7 @@ function generateEnum(name, values, options) {
130
130
  const enumCode = `export enum ${enumName} {
131
131
  ${enumEntries}
132
132
  }`;
133
- const schemaCode2 = `export const ${schemaName} = z.enum(${enumName});`;
133
+ const schemaCode2 = `export const ${schemaName} = z.nativeEnum(${enumName});`;
134
134
  const typeCode2 = `export type ${name} = z.infer<typeof ${schemaName}>;`;
135
135
  return { enumCode, schemaCode: schemaCode2, typeCode: typeCode2 };
136
136
  }
@@ -347,20 +347,20 @@ var init_array_validator = __esm({
347
347
  });
348
348
 
349
349
  // src/validators/composition-validator.ts
350
- function generateUnion(schemas, discriminator, isNullable2, context, options) {
350
+ function generateUnion(schemas, discriminator, isNullable2, context, options, currentSchema) {
351
351
  if (discriminator) {
352
352
  let resolvedSchemas = schemas;
353
353
  if ((options == null ? void 0 : options.discriminatorMapping) && context.resolveDiscriminatorMapping) {
354
354
  resolvedSchemas = context.resolveDiscriminatorMapping(options.discriminatorMapping, schemas);
355
355
  }
356
- let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s));
356
+ let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
357
357
  if (options == null ? void 0 : options.passthrough) {
358
358
  schemaStrings2 = schemaStrings2.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
359
359
  }
360
360
  const union2 = `z.discriminatedUnion("${discriminator}", [${schemaStrings2.join(", ")}])`;
361
361
  return wrapNullable(union2, isNullable2);
362
362
  }
363
- let schemaStrings = schemas.map((s) => context.generatePropertySchema(s));
363
+ let schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema));
364
364
  if (options == null ? void 0 : options.passthrough) {
365
365
  schemaStrings = schemaStrings.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
366
366
  }
@@ -1243,7 +1243,8 @@ var init_property_generator = __esm({
1243
1243
  {
1244
1244
  passthrough: needsPassthrough,
1245
1245
  discriminatorMapping: (_c = schema.discriminator) == null ? void 0 : _c.mapping
1246
- }
1246
+ },
1247
+ currentSchema
1247
1248
  );
1248
1249
  if (schema.unevaluatedProperties !== void 0) {
1249
1250
  composition = this.applyUnevaluatedProperties(composition, schema);
@@ -1263,7 +1264,8 @@ var init_property_generator = __esm({
1263
1264
  {
1264
1265
  passthrough: needsPassthrough,
1265
1266
  discriminatorMapping: (_e = schema.discriminator) == null ? void 0 : _e.mapping
1266
- }
1267
+ },
1268
+ currentSchema
1267
1269
  );
1268
1270
  if (schema.unevaluatedProperties !== void 0) {
1269
1271
  composition = this.applyUnevaluatedProperties(composition, schema);
@@ -1347,7 +1349,8 @@ var init_property_generator = __esm({
1347
1349
  });
1348
1350
 
1349
1351
  // src/generator.ts
1350
- import { readFileSync, writeFileSync } from "fs";
1352
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
1353
+ import { dirname } from "path";
1351
1354
  import { parse } from "yaml";
1352
1355
  var ZodSchemaGenerator;
1353
1356
  var init_generator = __esm({
@@ -1448,10 +1451,13 @@ var init_generator = __esm({
1448
1451
  }
1449
1452
  for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
1450
1453
  if (schema.enum) {
1451
- const typeMode = this.schemaTypeModeMap.get(name) || "inferred";
1452
- if (typeMode === "inferred") {
1454
+ const context = this.schemaUsageMap.get(name);
1455
+ const resolvedOptions = context === "response" ? this.responseOptions : this.requestOptions;
1456
+ if (resolvedOptions.enumType === "typescript") {
1457
+ this.generateNativeEnum(name, schema);
1458
+ } else {
1453
1459
  const { enumCode } = generateEnum(name, schema.enum, {
1454
- enumType: this.options.enumType || "zod",
1460
+ enumType: "zod",
1455
1461
  prefix: this.options.prefix,
1456
1462
  suffix: this.options.suffix
1457
1463
  });
@@ -1459,14 +1465,13 @@ var init_generator = __esm({
1459
1465
  this.enums.set(name, enumCode);
1460
1466
  this.needsZodImport = true;
1461
1467
  }
1462
- } else {
1463
- this.generateNativeEnum(name, schema);
1464
1468
  }
1465
1469
  }
1466
1470
  }
1467
1471
  for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
1468
1472
  this.generateComponentSchema(name, schema);
1469
1473
  }
1474
+ this.generateQueryParameterSchemas();
1470
1475
  const orderedSchemaNames = this.topologicalSort();
1471
1476
  const output = ["// Auto-generated by @cerios/openapi-to-zod", "// Do not edit this file manually", ""];
1472
1477
  if (this.options.showStats === true) {
@@ -1477,12 +1482,8 @@ var init_generator = __esm({
1477
1482
  output.push('import { z } from "zod";');
1478
1483
  output.push("");
1479
1484
  }
1480
- if (this.enums.size > 0 || this.nativeEnums.size > 0) {
1481
- output.push("// Enums");
1482
- for (const enumCode of this.enums.values()) {
1483
- output.push(enumCode);
1484
- output.push("");
1485
- }
1485
+ if (this.nativeEnums.size > 0) {
1486
+ output.push("// Native Enums");
1486
1487
  for (const enumCode of this.nativeEnums.values()) {
1487
1488
  output.push(enumCode);
1488
1489
  output.push("");
@@ -1490,9 +1491,13 @@ var init_generator = __esm({
1490
1491
  }
1491
1492
  output.push("// Schemas and Types");
1492
1493
  for (const name of orderedSchemaNames) {
1494
+ const enumCode = this.enums.get(name);
1493
1495
  const schemaCode = this.schemas.get(name);
1494
1496
  const typeCode = this.types.get(name);
1495
- if (schemaCode) {
1497
+ if (enumCode) {
1498
+ output.push(enumCode);
1499
+ output.push("");
1500
+ } else if (schemaCode) {
1496
1501
  output.push(schemaCode);
1497
1502
  if (!schemaCode.includes(`export type ${name}`)) {
1498
1503
  const schemaName = `${toCamelCase(name, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
@@ -1506,6 +1511,15 @@ var init_generator = __esm({
1506
1511
  }
1507
1512
  return output.join("\n");
1508
1513
  }
1514
+ /**
1515
+ * Ensure directory exists for a file path
1516
+ */
1517
+ ensureDirectoryExists(filePath) {
1518
+ const dir = dirname(filePath);
1519
+ if (!existsSync(dir)) {
1520
+ mkdirSync(dir, { recursive: true });
1521
+ }
1522
+ }
1509
1523
  /**
1510
1524
  * Generate the complete output file
1511
1525
  */
@@ -1517,6 +1531,7 @@ var init_generator = __esm({
1517
1531
  );
1518
1532
  }
1519
1533
  const output = this.generateString();
1534
+ this.ensureDirectoryExists(this.options.output);
1520
1535
  writeFileSync(this.options.output, output);
1521
1536
  }
1522
1537
  /**
@@ -1820,10 +1835,19 @@ var init_generator = __esm({
1820
1835
  const context = this.schemaUsageMap.get(name);
1821
1836
  const resolvedOptions = context === "response" ? this.responseOptions : this.requestOptions;
1822
1837
  if (schema.enum) {
1823
- if (typeMode === "inferred") {
1824
- const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1838
+ const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1839
+ if (resolvedOptions.enumType === "typescript") {
1840
+ const { schemaCode, typeCode } = generateEnum(name, schema.enum, {
1841
+ enumType: "typescript",
1842
+ prefix: this.options.prefix,
1843
+ suffix: this.options.suffix
1844
+ });
1845
+ const enumSchemaCode = `${jsdoc}${schemaCode}
1846
+ ${typeCode}`;
1847
+ this.schemas.set(name, enumSchemaCode);
1848
+ } else {
1825
1849
  const { enumCode, schemaCode, typeCode } = generateEnum(name, schema.enum, {
1826
- enumType: resolvedOptions.enumType,
1850
+ enumType: "zod",
1827
1851
  prefix: this.options.prefix,
1828
1852
  suffix: this.options.suffix
1829
1853
  });
@@ -1882,6 +1906,133 @@ ${typeCode}`;
1882
1906
  this.schemas.set(name, zodSchemaCode);
1883
1907
  }
1884
1908
  }
1909
+ /**
1910
+ * Generate query parameter schemas for each operation
1911
+ */
1912
+ generateQueryParameterSchemas() {
1913
+ var _a;
1914
+ if (!this.spec.paths) {
1915
+ return;
1916
+ }
1917
+ for (const [_path, pathItem] of Object.entries(this.spec.paths)) {
1918
+ if (!pathItem || typeof pathItem !== "object") continue;
1919
+ const methods = ["get", "post", "put", "patch", "delete", "head", "options"];
1920
+ for (const method of methods) {
1921
+ const operation = pathItem[method];
1922
+ if (!operation) continue;
1923
+ if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
1924
+ continue;
1925
+ }
1926
+ const queryParams = operation.parameters.filter(
1927
+ (param) => param && typeof param === "object" && param.in === "query"
1928
+ );
1929
+ if (queryParams.length === 0) {
1930
+ continue;
1931
+ }
1932
+ const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
1933
+ const schemaName = `${pascalOperationId}QueryParams`;
1934
+ if (!this.schemaDependencies.has(schemaName)) {
1935
+ this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
1936
+ }
1937
+ const properties = {};
1938
+ const required = [];
1939
+ for (const param of queryParams) {
1940
+ const paramName = param.name;
1941
+ const isRequired = param.required === true;
1942
+ const paramSchema = param.schema;
1943
+ if (!paramSchema) continue;
1944
+ let zodType = this.generateQueryParamType(paramSchema, param);
1945
+ if (paramSchema.type === "array" && paramSchema.items) {
1946
+ const itemType = this.generateQueryParamType(paramSchema.items, param);
1947
+ zodType = `z.array(${itemType})`;
1948
+ }
1949
+ if (param.description && this.requestOptions.includeDescriptions) {
1950
+ if (this.requestOptions.useDescribe) {
1951
+ zodType = `${zodType}.describe(${JSON.stringify(param.description)})`;
1952
+ }
1953
+ }
1954
+ if (!isRequired) {
1955
+ zodType = `${zodType}.optional()`;
1956
+ }
1957
+ properties[paramName] = zodType;
1958
+ if (isRequired) {
1959
+ required.push(paramName);
1960
+ }
1961
+ if (paramSchema.$ref) {
1962
+ const refName = resolveRef(paramSchema.$ref);
1963
+ (_a = this.schemaDependencies.get(schemaName)) == null ? void 0 : _a.add(refName);
1964
+ }
1965
+ }
1966
+ const objectMode = this.requestOptions.mode;
1967
+ const zodMethod = objectMode === "strict" ? "strictObject" : objectMode === "loose" ? "looseObject" : "object";
1968
+ const propsCode = Object.entries(properties).map(([key, value]) => {
1969
+ const needsQuotes = !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
1970
+ const quotedKey = needsQuotes ? `"${key}"` : key;
1971
+ return ` ${quotedKey}: ${value}`;
1972
+ }).join(",\n");
1973
+ const schemaCode = `z.${zodMethod}({
1974
+ ${propsCode}
1975
+ })`;
1976
+ const operationName = pascalOperationId;
1977
+ const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
1978
+ const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
1979
+ const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}QueryParamsSchema`;
1980
+ const jsdoc = `/**
1981
+ * Query parameters for ${operation.operationId}
1982
+ */
1983
+ `;
1984
+ const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
1985
+ this.schemas.set(schemaName, fullSchemaCode);
1986
+ this.needsZodImport = true;
1987
+ }
1988
+ }
1989
+ }
1990
+ /**
1991
+ * Generate Zod type for a query parameter schema
1992
+ */
1993
+ generateQueryParamType(schema, param) {
1994
+ if (schema.$ref) {
1995
+ const refName = resolveRef(schema.$ref);
1996
+ const schemaName = toCamelCase(refName, { prefix: this.options.prefix, suffix: this.options.suffix });
1997
+ return `${schemaName}Schema`;
1998
+ }
1999
+ if (schema.enum) {
2000
+ const enumValues = schema.enum.map((v) => typeof v === "string" ? `"${v}"` : v).join(", ");
2001
+ return `z.enum([${enumValues}])`;
2002
+ }
2003
+ const type = schema.type;
2004
+ if (type === "string") {
2005
+ let zodType = "z.string()";
2006
+ if (schema.minLength !== void 0) zodType = `${zodType}.min(${schema.minLength})`;
2007
+ if (schema.maxLength !== void 0) zodType = `${zodType}.max(${schema.maxLength})`;
2008
+ if (schema.pattern) zodType = `${zodType}.regex(/${schema.pattern}/)`;
2009
+ if (schema.format === "email") zodType = `${zodType}.email()`;
2010
+ if (schema.format === "uri" || schema.format === "url") zodType = `${zodType}.url()`;
2011
+ if (schema.format === "uuid") zodType = `${zodType}.uuid()`;
2012
+ return zodType;
2013
+ }
2014
+ if (type === "number" || type === "integer") {
2015
+ let zodType = type === "integer" ? "z.number().int()" : "z.number()";
2016
+ if (schema.minimum !== void 0) {
2017
+ zodType = schema.exclusiveMinimum ? `${zodType}.gt(${schema.minimum})` : `${zodType}.gte(${schema.minimum})`;
2018
+ }
2019
+ if (schema.maximum !== void 0) {
2020
+ zodType = schema.exclusiveMaximum ? `${zodType}.lt(${schema.maximum})` : `${zodType}.lte(${schema.maximum})`;
2021
+ }
2022
+ return zodType;
2023
+ }
2024
+ if (type === "boolean") {
2025
+ return "z.boolean()";
2026
+ }
2027
+ if (type === "array" && schema.items) {
2028
+ const itemType = this.generateQueryParamType(schema.items, param);
2029
+ let arrayType = `z.array(${itemType})`;
2030
+ if (schema.minItems !== void 0) arrayType = `${arrayType}.min(${schema.minItems})`;
2031
+ if (schema.maxItems !== void 0) arrayType = `${arrayType}.max(${schema.maxItems})`;
2032
+ return arrayType;
2033
+ }
2034
+ return "z.unknown()";
2035
+ }
1885
2036
  /**
1886
2037
  * Generate native TypeScript enum
1887
2038
  */
@@ -1892,8 +2043,8 @@ ${typeCode}`;
1892
2043
  const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1893
2044
  if (resolvedOptions.nativeEnumType === "enum") {
1894
2045
  const enumName = `${name}Enum`;
1895
- const members = schema.enum.map((value, index) => {
1896
- const key = typeof value === "string" ? this.toEnumKey(value) : `Value${index}`;
2046
+ const members = schema.enum.map((value) => {
2047
+ const key = typeof value === "string" ? this.toEnumKey(value) : `N${value}`;
1897
2048
  const val = typeof value === "string" ? `"${value}"` : value;
1898
2049
  return ` ${key} = ${val}`;
1899
2050
  }).join(",\n");
@@ -2036,7 +2187,11 @@ ${props.join("\n")}
2036
2187
  const visited = /* @__PURE__ */ new Set();
2037
2188
  const visiting = /* @__PURE__ */ new Set();
2038
2189
  const aliases = [];
2190
+ const circularDeps = /* @__PURE__ */ new Set();
2039
2191
  const codeCache = /* @__PURE__ */ new Map();
2192
+ for (const [name, code] of this.enums) {
2193
+ codeCache.set(name, code);
2194
+ }
2040
2195
  for (const [name, code] of this.schemas) {
2041
2196
  codeCache.set(name, code);
2042
2197
  }
@@ -2046,6 +2201,7 @@ ${props.join("\n")}
2046
2201
  const visit = (name) => {
2047
2202
  if (visited.has(name)) return;
2048
2203
  if (visiting.has(name)) {
2204
+ circularDeps.add(name);
2049
2205
  return;
2050
2206
  }
2051
2207
  visiting.add(name);
@@ -2060,19 +2216,27 @@ ${props.join("\n")}
2060
2216
  const deps = this.schemaDependencies.get(name);
2061
2217
  if (deps && deps.size > 0) {
2062
2218
  for (const dep of deps) {
2063
- if (this.schemas.has(dep) || this.types.has(dep)) {
2219
+ if (this.enums.has(dep) || this.schemas.has(dep) || this.types.has(dep)) {
2064
2220
  visit(dep);
2065
2221
  }
2066
2222
  }
2067
2223
  }
2068
2224
  visiting.delete(name);
2069
2225
  visited.add(name);
2070
- sorted.push(name);
2226
+ if (!circularDeps.has(name)) {
2227
+ sorted.push(name);
2228
+ }
2071
2229
  };
2072
- const allNames = /* @__PURE__ */ new Set([...this.schemas.keys(), ...this.types.keys()]);
2230
+ const allNames = /* @__PURE__ */ new Set([...this.enums.keys(), ...this.schemas.keys(), ...this.types.keys()]);
2073
2231
  for (const name of allNames) {
2074
2232
  visit(name);
2075
2233
  }
2234
+ for (const name of circularDeps) {
2235
+ if (!visited.has(name)) {
2236
+ sorted.push(name);
2237
+ visited.add(name);
2238
+ }
2239
+ }
2076
2240
  return [...sorted, ...aliases];
2077
2241
  }
2078
2242
  /**
@@ -2109,8 +2273,7 @@ ${props.join("\n")}
2109
2273
 
2110
2274
  // src/batch-executor.ts
2111
2275
  async function processSpec(spec, index, total) {
2112
- const specName = spec.name || spec.input;
2113
- console.log(`Processing [${index + 1}/${total}] ${specName}...`);
2276
+ console.log(`Processing [${index + 1}/${total}] ${spec.input}...`);
2114
2277
  try {
2115
2278
  const generator = new ZodSchemaGenerator(spec);
2116
2279
  generator.generate();
@@ -2169,8 +2332,7 @@ ${"=".repeat(50)}`);
2169
2332
  console.log("\nFailed specs:");
2170
2333
  for (const result of summary.results) {
2171
2334
  if (!result.success) {
2172
- const specName = result.spec.name || result.spec.input;
2173
- console.error(` \u2717 ${specName}`);
2335
+ console.error(` \u2717 ${result.spec.input}`);
2174
2336
  console.error(` Error: ${result.error}`);
2175
2337
  }
2176
2338
  }
@@ -2218,19 +2380,6 @@ var init_batch_executor = __esm({
2218
2380
  }
2219
2381
  });
2220
2382
 
2221
- // src/types.ts
2222
- var FilePath;
2223
- var init_types = __esm({
2224
- "src/types.ts"() {
2225
- "use strict";
2226
- init_esm_shims();
2227
- FilePath = {
2228
- from: (path2) => path2,
2229
- is: (value) => typeof value === "string" && value.length > 0
2230
- };
2231
- }
2232
- });
2233
-
2234
2383
  // src/utils/config-loader.ts
2235
2384
  import { cosmiconfig } from "cosmiconfig";
2236
2385
  import { z } from "zod";
@@ -2400,7 +2549,6 @@ var require_cli = __commonJS({
2400
2549
  init_batch_executor();
2401
2550
  init_errors();
2402
2551
  init_generator();
2403
- init_types();
2404
2552
  init_config_loader();
2405
2553
  var CliOptionsSchema = z2.object({
2406
2554
  config: z2.string().optional(),
@@ -2484,8 +2632,8 @@ Examples:
2484
2632
  });
2485
2633
  }
2486
2634
  const generatorOptions = {
2487
- input: FilePath.from(options.input),
2488
- output: FilePath.from(options.output),
2635
+ input: options.input,
2636
+ output: options.output,
2489
2637
  mode: options.mode,
2490
2638
  includeDescriptions: options.descriptions,
2491
2639
  enumType: options.enumType,