@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/index.d.mts CHANGED
@@ -243,16 +243,6 @@ interface OpenAPISpec {
243
243
  * - 'sequential': Process specs one at a time (safer for resource constraints)
244
244
  */
245
245
  type ExecutionMode = "parallel" | "sequential";
246
- /**
247
- * Configuration for a single OpenAPI spec
248
- * Extends GeneratorOptions with all the same properties
249
- */
250
- interface SpecConfig extends GeneratorOptions {
251
- /**
252
- * Optional name/identifier for this spec (for logging purposes)
253
- */
254
- name?: string;
255
- }
256
246
  /**
257
247
  * Root configuration file structure
258
248
  */
@@ -266,7 +256,7 @@ interface ConfigFile {
266
256
  * Array of OpenAPI specifications to process
267
257
  * Each spec must have input and output paths
268
258
  */
269
- specs: SpecConfig[];
259
+ specs: GeneratorOptions[];
270
260
  /**
271
261
  * Execution mode for batch processing
272
262
  * @default "parallel"
@@ -315,6 +305,10 @@ declare class ZodSchemaGenerator {
315
305
  * @returns The generated TypeScript code as a string
316
306
  */
317
307
  generateString(): string;
308
+ /**
309
+ * Ensure directory exists for a file path
310
+ */
311
+ private ensureDirectoryExists;
318
312
  /**
319
313
  * Generate the complete output file
320
314
  */
@@ -367,6 +361,14 @@ declare class ZodSchemaGenerator {
367
361
  * Generate schema for a component
368
362
  */
369
363
  private generateComponentSchema;
364
+ /**
365
+ * Generate query parameter schemas for each operation
366
+ */
367
+ private generateQueryParameterSchemas;
368
+ /**
369
+ * Generate Zod type for a query parameter schema
370
+ */
371
+ private generateQueryParamType;
370
372
  /**
371
373
  * Generate native TypeScript enum
372
374
  */
@@ -398,4 +400,4 @@ declare class ZodSchemaGenerator {
398
400
  private generateStats;
399
401
  }
400
402
 
401
- export { CircularReferenceError, CliOptionsError, type ConfigFile, ConfigValidationError, type ExecutionMode, FileOperationError, GeneratorError, type GeneratorOptions, type OpenAPISpec, SchemaGenerationError, type SpecConfig, SpecValidationError, ZodSchemaGenerator, defineConfig };
403
+ export { CircularReferenceError, CliOptionsError, type ConfigFile, ConfigValidationError, type ExecutionMode, FileOperationError, GeneratorError, type GeneratorOptions, type OpenAPISpec, SchemaGenerationError, SpecValidationError, ZodSchemaGenerator, defineConfig };
package/dist/index.d.ts CHANGED
@@ -243,16 +243,6 @@ interface OpenAPISpec {
243
243
  * - 'sequential': Process specs one at a time (safer for resource constraints)
244
244
  */
245
245
  type ExecutionMode = "parallel" | "sequential";
246
- /**
247
- * Configuration for a single OpenAPI spec
248
- * Extends GeneratorOptions with all the same properties
249
- */
250
- interface SpecConfig extends GeneratorOptions {
251
- /**
252
- * Optional name/identifier for this spec (for logging purposes)
253
- */
254
- name?: string;
255
- }
256
246
  /**
257
247
  * Root configuration file structure
258
248
  */
@@ -266,7 +256,7 @@ interface ConfigFile {
266
256
  * Array of OpenAPI specifications to process
267
257
  * Each spec must have input and output paths
268
258
  */
269
- specs: SpecConfig[];
259
+ specs: GeneratorOptions[];
270
260
  /**
271
261
  * Execution mode for batch processing
272
262
  * @default "parallel"
@@ -315,6 +305,10 @@ declare class ZodSchemaGenerator {
315
305
  * @returns The generated TypeScript code as a string
316
306
  */
317
307
  generateString(): string;
308
+ /**
309
+ * Ensure directory exists for a file path
310
+ */
311
+ private ensureDirectoryExists;
318
312
  /**
319
313
  * Generate the complete output file
320
314
  */
@@ -367,6 +361,14 @@ declare class ZodSchemaGenerator {
367
361
  * Generate schema for a component
368
362
  */
369
363
  private generateComponentSchema;
364
+ /**
365
+ * Generate query parameter schemas for each operation
366
+ */
367
+ private generateQueryParameterSchemas;
368
+ /**
369
+ * Generate Zod type for a query parameter schema
370
+ */
371
+ private generateQueryParamType;
370
372
  /**
371
373
  * Generate native TypeScript enum
372
374
  */
@@ -398,4 +400,4 @@ declare class ZodSchemaGenerator {
398
400
  private generateStats;
399
401
  }
400
402
 
401
- export { CircularReferenceError, CliOptionsError, type ConfigFile, ConfigValidationError, type ExecutionMode, FileOperationError, GeneratorError, type GeneratorOptions, type OpenAPISpec, SchemaGenerationError, type SpecConfig, SpecValidationError, ZodSchemaGenerator, defineConfig };
403
+ export { CircularReferenceError, CliOptionsError, type ConfigFile, ConfigValidationError, type ExecutionMode, FileOperationError, GeneratorError, type GeneratorOptions, type OpenAPISpec, SchemaGenerationError, SpecValidationError, ZodSchemaGenerator, defineConfig };
package/dist/index.js CHANGED
@@ -93,6 +93,7 @@ var ConfigurationError = class extends GeneratorError {
93
93
 
94
94
  // src/generator.ts
95
95
  var import_node_fs = require("fs");
96
+ var import_node_path = require("path");
96
97
  var import_yaml = require("yaml");
97
98
 
98
99
  // src/utils/name-utils.ts
@@ -146,7 +147,7 @@ function generateEnum(name, values, options) {
146
147
  const enumCode = `export enum ${enumName} {
147
148
  ${enumEntries}
148
149
  }`;
149
- const schemaCode2 = `export const ${schemaName} = z.enum(${enumName});`;
150
+ const schemaCode2 = `export const ${schemaName} = z.nativeEnum(${enumName});`;
150
151
  const typeCode2 = `export type ${name} = z.infer<typeof ${schemaName}>;`;
151
152
  return { enumCode, schemaCode: schemaCode2, typeCode: typeCode2 };
152
153
  }
@@ -329,20 +330,20 @@ function generateArrayValidation(schema, context) {
329
330
  }
330
331
 
331
332
  // src/validators/composition-validator.ts
332
- function generateUnion(schemas, discriminator, isNullable2, context, options) {
333
+ function generateUnion(schemas, discriminator, isNullable2, context, options, currentSchema) {
333
334
  if (discriminator) {
334
335
  let resolvedSchemas = schemas;
335
336
  if ((options == null ? void 0 : options.discriminatorMapping) && context.resolveDiscriminatorMapping) {
336
337
  resolvedSchemas = context.resolveDiscriminatorMapping(options.discriminatorMapping, schemas);
337
338
  }
338
- let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s));
339
+ let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
339
340
  if (options == null ? void 0 : options.passthrough) {
340
341
  schemaStrings2 = schemaStrings2.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
341
342
  }
342
343
  const union2 = `z.discriminatedUnion("${discriminator}", [${schemaStrings2.join(", ")}])`;
343
344
  return wrapNullable(union2, isNullable2);
344
345
  }
345
- let schemaStrings = schemas.map((s) => context.generatePropertySchema(s));
346
+ let schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema));
346
347
  if (options == null ? void 0 : options.passthrough) {
347
348
  schemaStrings = schemaStrings.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
348
349
  }
@@ -1175,7 +1176,8 @@ var _PropertyGenerator = class _PropertyGenerator {
1175
1176
  {
1176
1177
  passthrough: needsPassthrough,
1177
1178
  discriminatorMapping: (_c = schema.discriminator) == null ? void 0 : _c.mapping
1178
- }
1179
+ },
1180
+ currentSchema
1179
1181
  );
1180
1182
  if (schema.unevaluatedProperties !== void 0) {
1181
1183
  composition = this.applyUnevaluatedProperties(composition, schema);
@@ -1195,7 +1197,8 @@ var _PropertyGenerator = class _PropertyGenerator {
1195
1197
  {
1196
1198
  passthrough: needsPassthrough,
1197
1199
  discriminatorMapping: (_e = schema.discriminator) == null ? void 0 : _e.mapping
1198
- }
1200
+ },
1201
+ currentSchema
1199
1202
  );
1200
1203
  if (schema.unevaluatedProperties !== void 0) {
1201
1204
  composition = this.applyUnevaluatedProperties(composition, schema);
@@ -1366,10 +1369,13 @@ var ZodSchemaGenerator = class {
1366
1369
  }
1367
1370
  for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
1368
1371
  if (schema.enum) {
1369
- const typeMode = this.schemaTypeModeMap.get(name) || "inferred";
1370
- if (typeMode === "inferred") {
1372
+ const context = this.schemaUsageMap.get(name);
1373
+ const resolvedOptions = context === "response" ? this.responseOptions : this.requestOptions;
1374
+ if (resolvedOptions.enumType === "typescript") {
1375
+ this.generateNativeEnum(name, schema);
1376
+ } else {
1371
1377
  const { enumCode } = generateEnum(name, schema.enum, {
1372
- enumType: this.options.enumType || "zod",
1378
+ enumType: "zod",
1373
1379
  prefix: this.options.prefix,
1374
1380
  suffix: this.options.suffix
1375
1381
  });
@@ -1377,14 +1383,13 @@ var ZodSchemaGenerator = class {
1377
1383
  this.enums.set(name, enumCode);
1378
1384
  this.needsZodImport = true;
1379
1385
  }
1380
- } else {
1381
- this.generateNativeEnum(name, schema);
1382
1386
  }
1383
1387
  }
1384
1388
  }
1385
1389
  for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
1386
1390
  this.generateComponentSchema(name, schema);
1387
1391
  }
1392
+ this.generateQueryParameterSchemas();
1388
1393
  const orderedSchemaNames = this.topologicalSort();
1389
1394
  const output = ["// Auto-generated by @cerios/openapi-to-zod", "// Do not edit this file manually", ""];
1390
1395
  if (this.options.showStats === true) {
@@ -1395,12 +1400,8 @@ var ZodSchemaGenerator = class {
1395
1400
  output.push('import { z } from "zod";');
1396
1401
  output.push("");
1397
1402
  }
1398
- if (this.enums.size > 0 || this.nativeEnums.size > 0) {
1399
- output.push("// Enums");
1400
- for (const enumCode of this.enums.values()) {
1401
- output.push(enumCode);
1402
- output.push("");
1403
- }
1403
+ if (this.nativeEnums.size > 0) {
1404
+ output.push("// Native Enums");
1404
1405
  for (const enumCode of this.nativeEnums.values()) {
1405
1406
  output.push(enumCode);
1406
1407
  output.push("");
@@ -1408,9 +1409,13 @@ var ZodSchemaGenerator = class {
1408
1409
  }
1409
1410
  output.push("// Schemas and Types");
1410
1411
  for (const name of orderedSchemaNames) {
1412
+ const enumCode = this.enums.get(name);
1411
1413
  const schemaCode = this.schemas.get(name);
1412
1414
  const typeCode = this.types.get(name);
1413
- if (schemaCode) {
1415
+ if (enumCode) {
1416
+ output.push(enumCode);
1417
+ output.push("");
1418
+ } else if (schemaCode) {
1414
1419
  output.push(schemaCode);
1415
1420
  if (!schemaCode.includes(`export type ${name}`)) {
1416
1421
  const schemaName = `${toCamelCase(name, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
@@ -1424,6 +1429,15 @@ var ZodSchemaGenerator = class {
1424
1429
  }
1425
1430
  return output.join("\n");
1426
1431
  }
1432
+ /**
1433
+ * Ensure directory exists for a file path
1434
+ */
1435
+ ensureDirectoryExists(filePath) {
1436
+ const dir = (0, import_node_path.dirname)(filePath);
1437
+ if (!(0, import_node_fs.existsSync)(dir)) {
1438
+ (0, import_node_fs.mkdirSync)(dir, { recursive: true });
1439
+ }
1440
+ }
1427
1441
  /**
1428
1442
  * Generate the complete output file
1429
1443
  */
@@ -1435,6 +1449,7 @@ var ZodSchemaGenerator = class {
1435
1449
  );
1436
1450
  }
1437
1451
  const output = this.generateString();
1452
+ this.ensureDirectoryExists(this.options.output);
1438
1453
  (0, import_node_fs.writeFileSync)(this.options.output, output);
1439
1454
  }
1440
1455
  /**
@@ -1738,10 +1753,19 @@ var ZodSchemaGenerator = class {
1738
1753
  const context = this.schemaUsageMap.get(name);
1739
1754
  const resolvedOptions = context === "response" ? this.responseOptions : this.requestOptions;
1740
1755
  if (schema.enum) {
1741
- if (typeMode === "inferred") {
1742
- const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1756
+ const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1757
+ if (resolvedOptions.enumType === "typescript") {
1758
+ const { schemaCode, typeCode } = generateEnum(name, schema.enum, {
1759
+ enumType: "typescript",
1760
+ prefix: this.options.prefix,
1761
+ suffix: this.options.suffix
1762
+ });
1763
+ const enumSchemaCode = `${jsdoc}${schemaCode}
1764
+ ${typeCode}`;
1765
+ this.schemas.set(name, enumSchemaCode);
1766
+ } else {
1743
1767
  const { enumCode, schemaCode, typeCode } = generateEnum(name, schema.enum, {
1744
- enumType: resolvedOptions.enumType,
1768
+ enumType: "zod",
1745
1769
  prefix: this.options.prefix,
1746
1770
  suffix: this.options.suffix
1747
1771
  });
@@ -1800,6 +1824,133 @@ ${typeCode}`;
1800
1824
  this.schemas.set(name, zodSchemaCode);
1801
1825
  }
1802
1826
  }
1827
+ /**
1828
+ * Generate query parameter schemas for each operation
1829
+ */
1830
+ generateQueryParameterSchemas() {
1831
+ var _a;
1832
+ if (!this.spec.paths) {
1833
+ return;
1834
+ }
1835
+ for (const [_path, pathItem] of Object.entries(this.spec.paths)) {
1836
+ if (!pathItem || typeof pathItem !== "object") continue;
1837
+ const methods = ["get", "post", "put", "patch", "delete", "head", "options"];
1838
+ for (const method of methods) {
1839
+ const operation = pathItem[method];
1840
+ if (!operation) continue;
1841
+ if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
1842
+ continue;
1843
+ }
1844
+ const queryParams = operation.parameters.filter(
1845
+ (param) => param && typeof param === "object" && param.in === "query"
1846
+ );
1847
+ if (queryParams.length === 0) {
1848
+ continue;
1849
+ }
1850
+ const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
1851
+ const schemaName = `${pascalOperationId}QueryParams`;
1852
+ if (!this.schemaDependencies.has(schemaName)) {
1853
+ this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
1854
+ }
1855
+ const properties = {};
1856
+ const required = [];
1857
+ for (const param of queryParams) {
1858
+ const paramName = param.name;
1859
+ const isRequired = param.required === true;
1860
+ const paramSchema = param.schema;
1861
+ if (!paramSchema) continue;
1862
+ let zodType = this.generateQueryParamType(paramSchema, param);
1863
+ if (paramSchema.type === "array" && paramSchema.items) {
1864
+ const itemType = this.generateQueryParamType(paramSchema.items, param);
1865
+ zodType = `z.array(${itemType})`;
1866
+ }
1867
+ if (param.description && this.requestOptions.includeDescriptions) {
1868
+ if (this.requestOptions.useDescribe) {
1869
+ zodType = `${zodType}.describe(${JSON.stringify(param.description)})`;
1870
+ }
1871
+ }
1872
+ if (!isRequired) {
1873
+ zodType = `${zodType}.optional()`;
1874
+ }
1875
+ properties[paramName] = zodType;
1876
+ if (isRequired) {
1877
+ required.push(paramName);
1878
+ }
1879
+ if (paramSchema.$ref) {
1880
+ const refName = resolveRef(paramSchema.$ref);
1881
+ (_a = this.schemaDependencies.get(schemaName)) == null ? void 0 : _a.add(refName);
1882
+ }
1883
+ }
1884
+ const objectMode = this.requestOptions.mode;
1885
+ const zodMethod = objectMode === "strict" ? "strictObject" : objectMode === "loose" ? "looseObject" : "object";
1886
+ const propsCode = Object.entries(properties).map(([key, value]) => {
1887
+ const needsQuotes = !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
1888
+ const quotedKey = needsQuotes ? `"${key}"` : key;
1889
+ return ` ${quotedKey}: ${value}`;
1890
+ }).join(",\n");
1891
+ const schemaCode = `z.${zodMethod}({
1892
+ ${propsCode}
1893
+ })`;
1894
+ const operationName = pascalOperationId;
1895
+ const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
1896
+ const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
1897
+ const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}QueryParamsSchema`;
1898
+ const jsdoc = `/**
1899
+ * Query parameters for ${operation.operationId}
1900
+ */
1901
+ `;
1902
+ const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
1903
+ this.schemas.set(schemaName, fullSchemaCode);
1904
+ this.needsZodImport = true;
1905
+ }
1906
+ }
1907
+ }
1908
+ /**
1909
+ * Generate Zod type for a query parameter schema
1910
+ */
1911
+ generateQueryParamType(schema, param) {
1912
+ if (schema.$ref) {
1913
+ const refName = resolveRef(schema.$ref);
1914
+ const schemaName = toCamelCase(refName, { prefix: this.options.prefix, suffix: this.options.suffix });
1915
+ return `${schemaName}Schema`;
1916
+ }
1917
+ if (schema.enum) {
1918
+ const enumValues = schema.enum.map((v) => typeof v === "string" ? `"${v}"` : v).join(", ");
1919
+ return `z.enum([${enumValues}])`;
1920
+ }
1921
+ const type = schema.type;
1922
+ if (type === "string") {
1923
+ let zodType = "z.string()";
1924
+ if (schema.minLength !== void 0) zodType = `${zodType}.min(${schema.minLength})`;
1925
+ if (schema.maxLength !== void 0) zodType = `${zodType}.max(${schema.maxLength})`;
1926
+ if (schema.pattern) zodType = `${zodType}.regex(/${schema.pattern}/)`;
1927
+ if (schema.format === "email") zodType = `${zodType}.email()`;
1928
+ if (schema.format === "uri" || schema.format === "url") zodType = `${zodType}.url()`;
1929
+ if (schema.format === "uuid") zodType = `${zodType}.uuid()`;
1930
+ return zodType;
1931
+ }
1932
+ if (type === "number" || type === "integer") {
1933
+ let zodType = type === "integer" ? "z.number().int()" : "z.number()";
1934
+ if (schema.minimum !== void 0) {
1935
+ zodType = schema.exclusiveMinimum ? `${zodType}.gt(${schema.minimum})` : `${zodType}.gte(${schema.minimum})`;
1936
+ }
1937
+ if (schema.maximum !== void 0) {
1938
+ zodType = schema.exclusiveMaximum ? `${zodType}.lt(${schema.maximum})` : `${zodType}.lte(${schema.maximum})`;
1939
+ }
1940
+ return zodType;
1941
+ }
1942
+ if (type === "boolean") {
1943
+ return "z.boolean()";
1944
+ }
1945
+ if (type === "array" && schema.items) {
1946
+ const itemType = this.generateQueryParamType(schema.items, param);
1947
+ let arrayType = `z.array(${itemType})`;
1948
+ if (schema.minItems !== void 0) arrayType = `${arrayType}.min(${schema.minItems})`;
1949
+ if (schema.maxItems !== void 0) arrayType = `${arrayType}.max(${schema.maxItems})`;
1950
+ return arrayType;
1951
+ }
1952
+ return "z.unknown()";
1953
+ }
1803
1954
  /**
1804
1955
  * Generate native TypeScript enum
1805
1956
  */
@@ -1810,8 +1961,8 @@ ${typeCode}`;
1810
1961
  const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1811
1962
  if (resolvedOptions.nativeEnumType === "enum") {
1812
1963
  const enumName = `${name}Enum`;
1813
- const members = schema.enum.map((value, index) => {
1814
- const key = typeof value === "string" ? this.toEnumKey(value) : `Value${index}`;
1964
+ const members = schema.enum.map((value) => {
1965
+ const key = typeof value === "string" ? this.toEnumKey(value) : `N${value}`;
1815
1966
  const val = typeof value === "string" ? `"${value}"` : value;
1816
1967
  return ` ${key} = ${val}`;
1817
1968
  }).join(",\n");
@@ -1954,7 +2105,11 @@ ${props.join("\n")}
1954
2105
  const visited = /* @__PURE__ */ new Set();
1955
2106
  const visiting = /* @__PURE__ */ new Set();
1956
2107
  const aliases = [];
2108
+ const circularDeps = /* @__PURE__ */ new Set();
1957
2109
  const codeCache = /* @__PURE__ */ new Map();
2110
+ for (const [name, code] of this.enums) {
2111
+ codeCache.set(name, code);
2112
+ }
1958
2113
  for (const [name, code] of this.schemas) {
1959
2114
  codeCache.set(name, code);
1960
2115
  }
@@ -1964,6 +2119,7 @@ ${props.join("\n")}
1964
2119
  const visit = (name) => {
1965
2120
  if (visited.has(name)) return;
1966
2121
  if (visiting.has(name)) {
2122
+ circularDeps.add(name);
1967
2123
  return;
1968
2124
  }
1969
2125
  visiting.add(name);
@@ -1978,19 +2134,27 @@ ${props.join("\n")}
1978
2134
  const deps = this.schemaDependencies.get(name);
1979
2135
  if (deps && deps.size > 0) {
1980
2136
  for (const dep of deps) {
1981
- if (this.schemas.has(dep) || this.types.has(dep)) {
2137
+ if (this.enums.has(dep) || this.schemas.has(dep) || this.types.has(dep)) {
1982
2138
  visit(dep);
1983
2139
  }
1984
2140
  }
1985
2141
  }
1986
2142
  visiting.delete(name);
1987
2143
  visited.add(name);
1988
- sorted.push(name);
2144
+ if (!circularDeps.has(name)) {
2145
+ sorted.push(name);
2146
+ }
1989
2147
  };
1990
- const allNames = /* @__PURE__ */ new Set([...this.schemas.keys(), ...this.types.keys()]);
2148
+ const allNames = /* @__PURE__ */ new Set([...this.enums.keys(), ...this.schemas.keys(), ...this.types.keys()]);
1991
2149
  for (const name of allNames) {
1992
2150
  visit(name);
1993
2151
  }
2152
+ for (const name of circularDeps) {
2153
+ if (!visited.has(name)) {
2154
+ sorted.push(name);
2155
+ visited.add(name);
2156
+ }
2157
+ }
1994
2158
  return [...sorted, ...aliases];
1995
2159
  }
1996
2160
  /**