@cerios/openapi-to-zod 0.1.2 → 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);
@@ -1449,10 +1451,13 @@ var init_generator = __esm({
1449
1451
  }
1450
1452
  for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
1451
1453
  if (schema.enum) {
1452
- const typeMode = this.schemaTypeModeMap.get(name) || "inferred";
1453
- 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 {
1454
1459
  const { enumCode } = generateEnum(name, schema.enum, {
1455
- enumType: this.options.enumType || "zod",
1460
+ enumType: "zod",
1456
1461
  prefix: this.options.prefix,
1457
1462
  suffix: this.options.suffix
1458
1463
  });
@@ -1460,14 +1465,13 @@ var init_generator = __esm({
1460
1465
  this.enums.set(name, enumCode);
1461
1466
  this.needsZodImport = true;
1462
1467
  }
1463
- } else {
1464
- this.generateNativeEnum(name, schema);
1465
1468
  }
1466
1469
  }
1467
1470
  }
1468
1471
  for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
1469
1472
  this.generateComponentSchema(name, schema);
1470
1473
  }
1474
+ this.generateQueryParameterSchemas();
1471
1475
  const orderedSchemaNames = this.topologicalSort();
1472
1476
  const output = ["// Auto-generated by @cerios/openapi-to-zod", "// Do not edit this file manually", ""];
1473
1477
  if (this.options.showStats === true) {
@@ -1478,12 +1482,8 @@ var init_generator = __esm({
1478
1482
  output.push('import { z } from "zod";');
1479
1483
  output.push("");
1480
1484
  }
1481
- if (this.enums.size > 0 || this.nativeEnums.size > 0) {
1482
- output.push("// Enums");
1483
- for (const enumCode of this.enums.values()) {
1484
- output.push(enumCode);
1485
- output.push("");
1486
- }
1485
+ if (this.nativeEnums.size > 0) {
1486
+ output.push("// Native Enums");
1487
1487
  for (const enumCode of this.nativeEnums.values()) {
1488
1488
  output.push(enumCode);
1489
1489
  output.push("");
@@ -1491,9 +1491,13 @@ var init_generator = __esm({
1491
1491
  }
1492
1492
  output.push("// Schemas and Types");
1493
1493
  for (const name of orderedSchemaNames) {
1494
+ const enumCode = this.enums.get(name);
1494
1495
  const schemaCode = this.schemas.get(name);
1495
1496
  const typeCode = this.types.get(name);
1496
- if (schemaCode) {
1497
+ if (enumCode) {
1498
+ output.push(enumCode);
1499
+ output.push("");
1500
+ } else if (schemaCode) {
1497
1501
  output.push(schemaCode);
1498
1502
  if (!schemaCode.includes(`export type ${name}`)) {
1499
1503
  const schemaName = `${toCamelCase(name, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
@@ -1831,10 +1835,19 @@ var init_generator = __esm({
1831
1835
  const context = this.schemaUsageMap.get(name);
1832
1836
  const resolvedOptions = context === "response" ? this.responseOptions : this.requestOptions;
1833
1837
  if (schema.enum) {
1834
- if (typeMode === "inferred") {
1835
- 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 {
1836
1849
  const { enumCode, schemaCode, typeCode } = generateEnum(name, schema.enum, {
1837
- enumType: resolvedOptions.enumType,
1850
+ enumType: "zod",
1838
1851
  prefix: this.options.prefix,
1839
1852
  suffix: this.options.suffix
1840
1853
  });
@@ -1893,6 +1906,133 @@ ${typeCode}`;
1893
1906
  this.schemas.set(name, zodSchemaCode);
1894
1907
  }
1895
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
+ }
1896
2036
  /**
1897
2037
  * Generate native TypeScript enum
1898
2038
  */
@@ -1903,8 +2043,8 @@ ${typeCode}`;
1903
2043
  const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1904
2044
  if (resolvedOptions.nativeEnumType === "enum") {
1905
2045
  const enumName = `${name}Enum`;
1906
- const members = schema.enum.map((value, index) => {
1907
- 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}`;
1908
2048
  const val = typeof value === "string" ? `"${value}"` : value;
1909
2049
  return ` ${key} = ${val}`;
1910
2050
  }).join(",\n");
@@ -2047,7 +2187,11 @@ ${props.join("\n")}
2047
2187
  const visited = /* @__PURE__ */ new Set();
2048
2188
  const visiting = /* @__PURE__ */ new Set();
2049
2189
  const aliases = [];
2190
+ const circularDeps = /* @__PURE__ */ new Set();
2050
2191
  const codeCache = /* @__PURE__ */ new Map();
2192
+ for (const [name, code] of this.enums) {
2193
+ codeCache.set(name, code);
2194
+ }
2051
2195
  for (const [name, code] of this.schemas) {
2052
2196
  codeCache.set(name, code);
2053
2197
  }
@@ -2057,6 +2201,7 @@ ${props.join("\n")}
2057
2201
  const visit = (name) => {
2058
2202
  if (visited.has(name)) return;
2059
2203
  if (visiting.has(name)) {
2204
+ circularDeps.add(name);
2060
2205
  return;
2061
2206
  }
2062
2207
  visiting.add(name);
@@ -2071,19 +2216,27 @@ ${props.join("\n")}
2071
2216
  const deps = this.schemaDependencies.get(name);
2072
2217
  if (deps && deps.size > 0) {
2073
2218
  for (const dep of deps) {
2074
- if (this.schemas.has(dep) || this.types.has(dep)) {
2219
+ if (this.enums.has(dep) || this.schemas.has(dep) || this.types.has(dep)) {
2075
2220
  visit(dep);
2076
2221
  }
2077
2222
  }
2078
2223
  }
2079
2224
  visiting.delete(name);
2080
2225
  visited.add(name);
2081
- sorted.push(name);
2226
+ if (!circularDeps.has(name)) {
2227
+ sorted.push(name);
2228
+ }
2082
2229
  };
2083
- 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()]);
2084
2231
  for (const name of allNames) {
2085
2232
  visit(name);
2086
2233
  }
2234
+ for (const name of circularDeps) {
2235
+ if (!visited.has(name)) {
2236
+ sorted.push(name);
2237
+ visited.add(name);
2238
+ }
2239
+ }
2087
2240
  return [...sorted, ...aliases];
2088
2241
  }
2089
2242
  /**
@@ -2120,8 +2273,7 @@ ${props.join("\n")}
2120
2273
 
2121
2274
  // src/batch-executor.ts
2122
2275
  async function processSpec(spec, index, total) {
2123
- const specName = spec.name || spec.input;
2124
- console.log(`Processing [${index + 1}/${total}] ${specName}...`);
2276
+ console.log(`Processing [${index + 1}/${total}] ${spec.input}...`);
2125
2277
  try {
2126
2278
  const generator = new ZodSchemaGenerator(spec);
2127
2279
  generator.generate();
@@ -2180,8 +2332,7 @@ ${"=".repeat(50)}`);
2180
2332
  console.log("\nFailed specs:");
2181
2333
  for (const result of summary.results) {
2182
2334
  if (!result.success) {
2183
- const specName = result.spec.name || result.spec.input;
2184
- console.error(` \u2717 ${specName}`);
2335
+ console.error(` \u2717 ${result.spec.input}`);
2185
2336
  console.error(` Error: ${result.error}`);
2186
2337
  }
2187
2338
  }
@@ -2229,19 +2380,6 @@ var init_batch_executor = __esm({
2229
2380
  }
2230
2381
  });
2231
2382
 
2232
- // src/types.ts
2233
- var FilePath;
2234
- var init_types = __esm({
2235
- "src/types.ts"() {
2236
- "use strict";
2237
- init_esm_shims();
2238
- FilePath = {
2239
- from: (path2) => path2,
2240
- is: (value) => typeof value === "string" && value.length > 0
2241
- };
2242
- }
2243
- });
2244
-
2245
2383
  // src/utils/config-loader.ts
2246
2384
  import { cosmiconfig } from "cosmiconfig";
2247
2385
  import { z } from "zod";
@@ -2411,7 +2549,6 @@ var require_cli = __commonJS({
2411
2549
  init_batch_executor();
2412
2550
  init_errors();
2413
2551
  init_generator();
2414
- init_types();
2415
2552
  init_config_loader();
2416
2553
  var CliOptionsSchema = z2.object({
2417
2554
  config: z2.string().optional(),
@@ -2495,8 +2632,8 @@ Examples:
2495
2632
  });
2496
2633
  }
2497
2634
  const generatorOptions = {
2498
- input: FilePath.from(options.input),
2499
- output: FilePath.from(options.output),
2635
+ input: options.input,
2636
+ output: options.output,
2500
2637
  mode: options.mode,
2501
2638
  includeDescriptions: options.descriptions,
2502
2639
  enumType: options.enumType,