@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.js CHANGED
@@ -73,6 +73,7 @@ var ConfigurationError = class extends GeneratorError {
73
73
 
74
74
  // src/generator.ts
75
75
  var import_node_fs = require("fs");
76
+ var import_node_path = require("path");
76
77
  var import_yaml = require("yaml");
77
78
 
78
79
  // src/utils/name-utils.ts
@@ -126,7 +127,7 @@ function generateEnum(name, values, options) {
126
127
  const enumCode = `export enum ${enumName} {
127
128
  ${enumEntries}
128
129
  }`;
129
- const schemaCode2 = `export const ${schemaName} = z.enum(${enumName});`;
130
+ const schemaCode2 = `export const ${schemaName} = z.nativeEnum(${enumName});`;
130
131
  const typeCode2 = `export type ${name} = z.infer<typeof ${schemaName}>;`;
131
132
  return { enumCode, schemaCode: schemaCode2, typeCode: typeCode2 };
132
133
  }
@@ -309,20 +310,20 @@ function generateArrayValidation(schema, context) {
309
310
  }
310
311
 
311
312
  // src/validators/composition-validator.ts
312
- function generateUnion(schemas, discriminator, isNullable2, context, options) {
313
+ function generateUnion(schemas, discriminator, isNullable2, context, options, currentSchema) {
313
314
  if (discriminator) {
314
315
  let resolvedSchemas = schemas;
315
316
  if ((options == null ? void 0 : options.discriminatorMapping) && context.resolveDiscriminatorMapping) {
316
317
  resolvedSchemas = context.resolveDiscriminatorMapping(options.discriminatorMapping, schemas);
317
318
  }
318
- let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s));
319
+ let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
319
320
  if (options == null ? void 0 : options.passthrough) {
320
321
  schemaStrings2 = schemaStrings2.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
321
322
  }
322
323
  const union2 = `z.discriminatedUnion("${discriminator}", [${schemaStrings2.join(", ")}])`;
323
324
  return wrapNullable(union2, isNullable2);
324
325
  }
325
- let schemaStrings = schemas.map((s) => context.generatePropertySchema(s));
326
+ let schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema));
326
327
  if (options == null ? void 0 : options.passthrough) {
327
328
  schemaStrings = schemaStrings.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
328
329
  }
@@ -1155,7 +1156,8 @@ var _PropertyGenerator = class _PropertyGenerator {
1155
1156
  {
1156
1157
  passthrough: needsPassthrough,
1157
1158
  discriminatorMapping: (_c = schema.discriminator) == null ? void 0 : _c.mapping
1158
- }
1159
+ },
1160
+ currentSchema
1159
1161
  );
1160
1162
  if (schema.unevaluatedProperties !== void 0) {
1161
1163
  composition = this.applyUnevaluatedProperties(composition, schema);
@@ -1175,7 +1177,8 @@ var _PropertyGenerator = class _PropertyGenerator {
1175
1177
  {
1176
1178
  passthrough: needsPassthrough,
1177
1179
  discriminatorMapping: (_e = schema.discriminator) == null ? void 0 : _e.mapping
1178
- }
1180
+ },
1181
+ currentSchema
1179
1182
  );
1180
1183
  if (schema.unevaluatedProperties !== void 0) {
1181
1184
  composition = this.applyUnevaluatedProperties(composition, schema);
@@ -1346,10 +1349,13 @@ var ZodSchemaGenerator = class {
1346
1349
  }
1347
1350
  for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
1348
1351
  if (schema.enum) {
1349
- const typeMode = this.schemaTypeModeMap.get(name) || "inferred";
1350
- if (typeMode === "inferred") {
1352
+ const context = this.schemaUsageMap.get(name);
1353
+ const resolvedOptions = context === "response" ? this.responseOptions : this.requestOptions;
1354
+ if (resolvedOptions.enumType === "typescript") {
1355
+ this.generateNativeEnum(name, schema);
1356
+ } else {
1351
1357
  const { enumCode } = generateEnum(name, schema.enum, {
1352
- enumType: this.options.enumType || "zod",
1358
+ enumType: "zod",
1353
1359
  prefix: this.options.prefix,
1354
1360
  suffix: this.options.suffix
1355
1361
  });
@@ -1357,14 +1363,13 @@ var ZodSchemaGenerator = class {
1357
1363
  this.enums.set(name, enumCode);
1358
1364
  this.needsZodImport = true;
1359
1365
  }
1360
- } else {
1361
- this.generateNativeEnum(name, schema);
1362
1366
  }
1363
1367
  }
1364
1368
  }
1365
1369
  for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
1366
1370
  this.generateComponentSchema(name, schema);
1367
1371
  }
1372
+ this.generateQueryParameterSchemas();
1368
1373
  const orderedSchemaNames = this.topologicalSort();
1369
1374
  const output = ["// Auto-generated by @cerios/openapi-to-zod", "// Do not edit this file manually", ""];
1370
1375
  if (this.options.showStats === true) {
@@ -1375,12 +1380,8 @@ var ZodSchemaGenerator = class {
1375
1380
  output.push('import { z } from "zod";');
1376
1381
  output.push("");
1377
1382
  }
1378
- if (this.enums.size > 0 || this.nativeEnums.size > 0) {
1379
- output.push("// Enums");
1380
- for (const enumCode of this.enums.values()) {
1381
- output.push(enumCode);
1382
- output.push("");
1383
- }
1383
+ if (this.nativeEnums.size > 0) {
1384
+ output.push("// Native Enums");
1384
1385
  for (const enumCode of this.nativeEnums.values()) {
1385
1386
  output.push(enumCode);
1386
1387
  output.push("");
@@ -1388,9 +1389,13 @@ var ZodSchemaGenerator = class {
1388
1389
  }
1389
1390
  output.push("// Schemas and Types");
1390
1391
  for (const name of orderedSchemaNames) {
1392
+ const enumCode = this.enums.get(name);
1391
1393
  const schemaCode = this.schemas.get(name);
1392
1394
  const typeCode = this.types.get(name);
1393
- if (schemaCode) {
1395
+ if (enumCode) {
1396
+ output.push(enumCode);
1397
+ output.push("");
1398
+ } else if (schemaCode) {
1394
1399
  output.push(schemaCode);
1395
1400
  if (!schemaCode.includes(`export type ${name}`)) {
1396
1401
  const schemaName = `${toCamelCase(name, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
@@ -1404,6 +1409,15 @@ var ZodSchemaGenerator = class {
1404
1409
  }
1405
1410
  return output.join("\n");
1406
1411
  }
1412
+ /**
1413
+ * Ensure directory exists for a file path
1414
+ */
1415
+ ensureDirectoryExists(filePath) {
1416
+ const dir = (0, import_node_path.dirname)(filePath);
1417
+ if (!(0, import_node_fs.existsSync)(dir)) {
1418
+ (0, import_node_fs.mkdirSync)(dir, { recursive: true });
1419
+ }
1420
+ }
1407
1421
  /**
1408
1422
  * Generate the complete output file
1409
1423
  */
@@ -1415,6 +1429,7 @@ var ZodSchemaGenerator = class {
1415
1429
  );
1416
1430
  }
1417
1431
  const output = this.generateString();
1432
+ this.ensureDirectoryExists(this.options.output);
1418
1433
  (0, import_node_fs.writeFileSync)(this.options.output, output);
1419
1434
  }
1420
1435
  /**
@@ -1718,10 +1733,19 @@ var ZodSchemaGenerator = class {
1718
1733
  const context = this.schemaUsageMap.get(name);
1719
1734
  const resolvedOptions = context === "response" ? this.responseOptions : this.requestOptions;
1720
1735
  if (schema.enum) {
1721
- if (typeMode === "inferred") {
1722
- const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1736
+ const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1737
+ if (resolvedOptions.enumType === "typescript") {
1738
+ const { schemaCode, typeCode } = generateEnum(name, schema.enum, {
1739
+ enumType: "typescript",
1740
+ prefix: this.options.prefix,
1741
+ suffix: this.options.suffix
1742
+ });
1743
+ const enumSchemaCode = `${jsdoc}${schemaCode}
1744
+ ${typeCode}`;
1745
+ this.schemas.set(name, enumSchemaCode);
1746
+ } else {
1723
1747
  const { enumCode, schemaCode, typeCode } = generateEnum(name, schema.enum, {
1724
- enumType: resolvedOptions.enumType,
1748
+ enumType: "zod",
1725
1749
  prefix: this.options.prefix,
1726
1750
  suffix: this.options.suffix
1727
1751
  });
@@ -1780,6 +1804,133 @@ ${typeCode}`;
1780
1804
  this.schemas.set(name, zodSchemaCode);
1781
1805
  }
1782
1806
  }
1807
+ /**
1808
+ * Generate query parameter schemas for each operation
1809
+ */
1810
+ generateQueryParameterSchemas() {
1811
+ var _a;
1812
+ if (!this.spec.paths) {
1813
+ return;
1814
+ }
1815
+ for (const [_path, pathItem] of Object.entries(this.spec.paths)) {
1816
+ if (!pathItem || typeof pathItem !== "object") continue;
1817
+ const methods = ["get", "post", "put", "patch", "delete", "head", "options"];
1818
+ for (const method of methods) {
1819
+ const operation = pathItem[method];
1820
+ if (!operation) continue;
1821
+ if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
1822
+ continue;
1823
+ }
1824
+ const queryParams = operation.parameters.filter(
1825
+ (param) => param && typeof param === "object" && param.in === "query"
1826
+ );
1827
+ if (queryParams.length === 0) {
1828
+ continue;
1829
+ }
1830
+ const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
1831
+ const schemaName = `${pascalOperationId}QueryParams`;
1832
+ if (!this.schemaDependencies.has(schemaName)) {
1833
+ this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
1834
+ }
1835
+ const properties = {};
1836
+ const required = [];
1837
+ for (const param of queryParams) {
1838
+ const paramName = param.name;
1839
+ const isRequired = param.required === true;
1840
+ const paramSchema = param.schema;
1841
+ if (!paramSchema) continue;
1842
+ let zodType = this.generateQueryParamType(paramSchema, param);
1843
+ if (paramSchema.type === "array" && paramSchema.items) {
1844
+ const itemType = this.generateQueryParamType(paramSchema.items, param);
1845
+ zodType = `z.array(${itemType})`;
1846
+ }
1847
+ if (param.description && this.requestOptions.includeDescriptions) {
1848
+ if (this.requestOptions.useDescribe) {
1849
+ zodType = `${zodType}.describe(${JSON.stringify(param.description)})`;
1850
+ }
1851
+ }
1852
+ if (!isRequired) {
1853
+ zodType = `${zodType}.optional()`;
1854
+ }
1855
+ properties[paramName] = zodType;
1856
+ if (isRequired) {
1857
+ required.push(paramName);
1858
+ }
1859
+ if (paramSchema.$ref) {
1860
+ const refName = resolveRef(paramSchema.$ref);
1861
+ (_a = this.schemaDependencies.get(schemaName)) == null ? void 0 : _a.add(refName);
1862
+ }
1863
+ }
1864
+ const objectMode = this.requestOptions.mode;
1865
+ const zodMethod = objectMode === "strict" ? "strictObject" : objectMode === "loose" ? "looseObject" : "object";
1866
+ const propsCode = Object.entries(properties).map(([key, value]) => {
1867
+ const needsQuotes = !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
1868
+ const quotedKey = needsQuotes ? `"${key}"` : key;
1869
+ return ` ${quotedKey}: ${value}`;
1870
+ }).join(",\n");
1871
+ const schemaCode = `z.${zodMethod}({
1872
+ ${propsCode}
1873
+ })`;
1874
+ const operationName = pascalOperationId;
1875
+ const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
1876
+ const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
1877
+ const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}QueryParamsSchema`;
1878
+ const jsdoc = `/**
1879
+ * Query parameters for ${operation.operationId}
1880
+ */
1881
+ `;
1882
+ const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
1883
+ this.schemas.set(schemaName, fullSchemaCode);
1884
+ this.needsZodImport = true;
1885
+ }
1886
+ }
1887
+ }
1888
+ /**
1889
+ * Generate Zod type for a query parameter schema
1890
+ */
1891
+ generateQueryParamType(schema, param) {
1892
+ if (schema.$ref) {
1893
+ const refName = resolveRef(schema.$ref);
1894
+ const schemaName = toCamelCase(refName, { prefix: this.options.prefix, suffix: this.options.suffix });
1895
+ return `${schemaName}Schema`;
1896
+ }
1897
+ if (schema.enum) {
1898
+ const enumValues = schema.enum.map((v) => typeof v === "string" ? `"${v}"` : v).join(", ");
1899
+ return `z.enum([${enumValues}])`;
1900
+ }
1901
+ const type = schema.type;
1902
+ if (type === "string") {
1903
+ let zodType = "z.string()";
1904
+ if (schema.minLength !== void 0) zodType = `${zodType}.min(${schema.minLength})`;
1905
+ if (schema.maxLength !== void 0) zodType = `${zodType}.max(${schema.maxLength})`;
1906
+ if (schema.pattern) zodType = `${zodType}.regex(/${schema.pattern}/)`;
1907
+ if (schema.format === "email") zodType = `${zodType}.email()`;
1908
+ if (schema.format === "uri" || schema.format === "url") zodType = `${zodType}.url()`;
1909
+ if (schema.format === "uuid") zodType = `${zodType}.uuid()`;
1910
+ return zodType;
1911
+ }
1912
+ if (type === "number" || type === "integer") {
1913
+ let zodType = type === "integer" ? "z.number().int()" : "z.number()";
1914
+ if (schema.minimum !== void 0) {
1915
+ zodType = schema.exclusiveMinimum ? `${zodType}.gt(${schema.minimum})` : `${zodType}.gte(${schema.minimum})`;
1916
+ }
1917
+ if (schema.maximum !== void 0) {
1918
+ zodType = schema.exclusiveMaximum ? `${zodType}.lt(${schema.maximum})` : `${zodType}.lte(${schema.maximum})`;
1919
+ }
1920
+ return zodType;
1921
+ }
1922
+ if (type === "boolean") {
1923
+ return "z.boolean()";
1924
+ }
1925
+ if (type === "array" && schema.items) {
1926
+ const itemType = this.generateQueryParamType(schema.items, param);
1927
+ let arrayType = `z.array(${itemType})`;
1928
+ if (schema.minItems !== void 0) arrayType = `${arrayType}.min(${schema.minItems})`;
1929
+ if (schema.maxItems !== void 0) arrayType = `${arrayType}.max(${schema.maxItems})`;
1930
+ return arrayType;
1931
+ }
1932
+ return "z.unknown()";
1933
+ }
1783
1934
  /**
1784
1935
  * Generate native TypeScript enum
1785
1936
  */
@@ -1790,8 +1941,8 @@ ${typeCode}`;
1790
1941
  const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1791
1942
  if (resolvedOptions.nativeEnumType === "enum") {
1792
1943
  const enumName = `${name}Enum`;
1793
- const members = schema.enum.map((value, index) => {
1794
- const key = typeof value === "string" ? this.toEnumKey(value) : `Value${index}`;
1944
+ const members = schema.enum.map((value) => {
1945
+ const key = typeof value === "string" ? this.toEnumKey(value) : `N${value}`;
1795
1946
  const val = typeof value === "string" ? `"${value}"` : value;
1796
1947
  return ` ${key} = ${val}`;
1797
1948
  }).join(",\n");
@@ -1934,7 +2085,11 @@ ${props.join("\n")}
1934
2085
  const visited = /* @__PURE__ */ new Set();
1935
2086
  const visiting = /* @__PURE__ */ new Set();
1936
2087
  const aliases = [];
2088
+ const circularDeps = /* @__PURE__ */ new Set();
1937
2089
  const codeCache = /* @__PURE__ */ new Map();
2090
+ for (const [name, code] of this.enums) {
2091
+ codeCache.set(name, code);
2092
+ }
1938
2093
  for (const [name, code] of this.schemas) {
1939
2094
  codeCache.set(name, code);
1940
2095
  }
@@ -1944,6 +2099,7 @@ ${props.join("\n")}
1944
2099
  const visit = (name) => {
1945
2100
  if (visited.has(name)) return;
1946
2101
  if (visiting.has(name)) {
2102
+ circularDeps.add(name);
1947
2103
  return;
1948
2104
  }
1949
2105
  visiting.add(name);
@@ -1958,19 +2114,27 @@ ${props.join("\n")}
1958
2114
  const deps = this.schemaDependencies.get(name);
1959
2115
  if (deps && deps.size > 0) {
1960
2116
  for (const dep of deps) {
1961
- if (this.schemas.has(dep) || this.types.has(dep)) {
2117
+ if (this.enums.has(dep) || this.schemas.has(dep) || this.types.has(dep)) {
1962
2118
  visit(dep);
1963
2119
  }
1964
2120
  }
1965
2121
  }
1966
2122
  visiting.delete(name);
1967
2123
  visited.add(name);
1968
- sorted.push(name);
2124
+ if (!circularDeps.has(name)) {
2125
+ sorted.push(name);
2126
+ }
1969
2127
  };
1970
- const allNames = /* @__PURE__ */ new Set([...this.schemas.keys(), ...this.types.keys()]);
2128
+ const allNames = /* @__PURE__ */ new Set([...this.enums.keys(), ...this.schemas.keys(), ...this.types.keys()]);
1971
2129
  for (const name of allNames) {
1972
2130
  visit(name);
1973
2131
  }
2132
+ for (const name of circularDeps) {
2133
+ if (!visited.has(name)) {
2134
+ sorted.push(name);
2135
+ visited.add(name);
2136
+ }
2137
+ }
1974
2138
  return [...sorted, ...aliases];
1975
2139
  }
1976
2140
  /**
@@ -2005,8 +2169,7 @@ ${props.join("\n")}
2005
2169
 
2006
2170
  // src/batch-executor.ts
2007
2171
  async function processSpec(spec, index, total) {
2008
- const specName = spec.name || spec.input;
2009
- console.log(`Processing [${index + 1}/${total}] ${specName}...`);
2172
+ console.log(`Processing [${index + 1}/${total}] ${spec.input}...`);
2010
2173
  try {
2011
2174
  const generator = new ZodSchemaGenerator(spec);
2012
2175
  generator.generate();
@@ -2065,8 +2228,7 @@ ${"=".repeat(50)}`);
2065
2228
  console.log("\nFailed specs:");
2066
2229
  for (const result of summary.results) {
2067
2230
  if (!result.success) {
2068
- const specName = result.spec.name || result.spec.input;
2069
- console.error(` \u2717 ${specName}`);
2231
+ console.error(` \u2717 ${result.spec.input}`);
2070
2232
  console.error(` Error: ${result.error}`);
2071
2233
  }
2072
2234
  }
@@ -2106,12 +2268,6 @@ function getBatchExitCode(summary) {
2106
2268
  return summary.failed > 0 ? 1 : 0;
2107
2269
  }
2108
2270
 
2109
- // src/types.ts
2110
- var FilePath = {
2111
- from: (path) => path,
2112
- is: (value) => typeof value === "string" && value.length > 0
2113
- };
2114
-
2115
2271
  // src/utils/config-loader.ts
2116
2272
  var import_cosmiconfig = require("cosmiconfig");
2117
2273
  var import_zod = require("zod");
@@ -2348,8 +2504,8 @@ async function executeSingleSpecMode(options) {
2348
2504
  });
2349
2505
  }
2350
2506
  const generatorOptions = {
2351
- input: FilePath.from(options.input),
2352
- output: FilePath.from(options.output),
2507
+ input: options.input,
2508
+ output: options.output,
2353
2509
  mode: options.mode,
2354
2510
  includeDescriptions: options.descriptions,
2355
2511
  enumType: options.enumType,