@cerios/openapi-to-zod 1.1.1 → 1.3.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.js CHANGED
@@ -190,14 +190,17 @@ function escapeJSDoc(str) {
190
190
  function wrapNullable(validation, isNullable2) {
191
191
  return isNullable2 ? `${validation}.nullable()` : validation;
192
192
  }
193
- function isNullable(schema) {
193
+ function isNullable(schema, defaultNullable = false) {
194
194
  if (schema.nullable === true) {
195
195
  return true;
196
196
  }
197
+ if (schema.nullable === false) {
198
+ return false;
199
+ }
197
200
  if (Array.isArray(schema.type)) {
198
201
  return schema.type.includes("null");
199
202
  }
200
- return false;
203
+ return defaultNullable;
201
204
  }
202
205
  function getPrimaryType(schema) {
203
206
  if (Array.isArray(schema.type)) {
@@ -363,6 +366,22 @@ function stripPrefix(input, pattern, ensureLeadingChar) {
363
366
  }
364
367
  return input;
365
368
  }
369
+ function stripPathPrefix(path, pattern) {
370
+ if (!pattern) {
371
+ return path;
372
+ }
373
+ if (!isGlobPattern(pattern)) {
374
+ let normalizedPattern = pattern.trim();
375
+ if (!normalizedPattern.startsWith("/")) {
376
+ normalizedPattern = `/${normalizedPattern}`;
377
+ }
378
+ if (normalizedPattern.endsWith("/") && normalizedPattern !== "/") {
379
+ normalizedPattern = normalizedPattern.slice(0, -1);
380
+ }
381
+ return stripPrefix(path, normalizedPattern, "/");
382
+ }
383
+ return stripPrefix(path, pattern, "/");
384
+ }
366
385
 
367
386
  // src/validators/array-validator.ts
368
387
  function generateArrayValidation(schema, context) {
@@ -411,12 +430,56 @@ function generateArrayValidation(schema, context) {
411
430
  }
412
431
 
413
432
  // src/validators/composition-validator.ts
433
+ function isDiscriminatorRequired(schemas, discriminator, context) {
434
+ const invalidSchemas = [];
435
+ for (const schema of schemas) {
436
+ const resolved = resolveSchema(schema, context);
437
+ const required = resolved.required || [];
438
+ if (!required.includes(discriminator)) {
439
+ const schemaName = schema.$ref ? schema.$ref.split("/").pop() || "inline" : "inline";
440
+ invalidSchemas.push(schemaName);
441
+ }
442
+ }
443
+ return {
444
+ valid: invalidSchemas.length === 0,
445
+ invalidSchemas
446
+ };
447
+ }
414
448
  function generateUnion(schemas, discriminator, isNullable2, context, options, currentSchema) {
449
+ if (schemas.length === 0) {
450
+ console.warn(
451
+ "[openapi-to-zod] Warning: Empty oneOf/anyOf array encountered. This is likely a malformed OpenAPI spec. Generating z.never() as fallback."
452
+ );
453
+ return wrapNullable(
454
+ 'z.never().describe("Empty oneOf/anyOf in OpenAPI spec - no valid schema defined")',
455
+ isNullable2
456
+ );
457
+ }
458
+ if (schemas.length === 1) {
459
+ let singleSchema = context.generatePropertySchema(schemas[0], currentSchema);
460
+ if ((options == null ? void 0 : options.passthrough) && !singleSchema.includes(".catchall(")) {
461
+ singleSchema = `${singleSchema}.catchall(z.unknown())`;
462
+ }
463
+ return wrapNullable(singleSchema, isNullable2);
464
+ }
415
465
  if (discriminator) {
416
466
  let resolvedSchemas = schemas;
417
467
  if ((options == null ? void 0 : options.discriminatorMapping) && context.resolveDiscriminatorMapping) {
418
468
  resolvedSchemas = context.resolveDiscriminatorMapping(options.discriminatorMapping, schemas);
419
469
  }
470
+ const discriminatorCheck = isDiscriminatorRequired(resolvedSchemas, discriminator, context);
471
+ if (!discriminatorCheck.valid) {
472
+ console.warn(
473
+ `[openapi-to-zod] Warning: Discriminator "${discriminator}" is not required in schemas: ${discriminatorCheck.invalidSchemas.join(", ")}. Falling back to z.union() instead of z.discriminatedUnion().`
474
+ );
475
+ let schemaStrings3 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
476
+ if (options == null ? void 0 : options.passthrough) {
477
+ schemaStrings3 = schemaStrings3.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
478
+ }
479
+ const fallbackDescription = `Discriminator "${discriminator}" is optional in some schemas (${discriminatorCheck.invalidSchemas.join(", ")}), using z.union() instead of z.discriminatedUnion()`;
480
+ const union3 = `z.union([${schemaStrings3.join(", ")}]).describe("${fallbackDescription}")`;
481
+ return wrapNullable(union3, isNullable2);
482
+ }
420
483
  let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
421
484
  if (options == null ? void 0 : options.passthrough) {
422
485
  schemaStrings2 = schemaStrings2.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
@@ -431,25 +494,102 @@ function generateUnion(schemas, discriminator, isNullable2, context, options, cu
431
494
  const union = `z.union([${schemaStrings.join(", ")}])`;
432
495
  return wrapNullable(union, isNullable2);
433
496
  }
497
+ function resolveSchema(schema, context) {
498
+ if (schema.$ref && context.resolveSchemaRef) {
499
+ const resolved = context.resolveSchemaRef(schema.$ref);
500
+ if (resolved) {
501
+ return resolved;
502
+ }
503
+ }
504
+ return schema;
505
+ }
506
+ function collectProperties(schema, context) {
507
+ const resolved = resolveSchema(schema, context);
508
+ const props = /* @__PURE__ */ new Map();
509
+ const sourceName = schema.$ref ? schema.$ref.split("/").pop() || "unknown" : "inline";
510
+ if (resolved.properties) {
511
+ for (const [key, value] of Object.entries(resolved.properties)) {
512
+ props.set(key, { schema: value, source: sourceName });
513
+ }
514
+ }
515
+ if (resolved.allOf) {
516
+ for (const subSchema of resolved.allOf) {
517
+ const subProps = collectProperties(subSchema, context);
518
+ for (const [key, value] of subProps) {
519
+ if (!props.has(key)) {
520
+ props.set(key, value);
521
+ }
522
+ }
523
+ }
524
+ }
525
+ return props;
526
+ }
527
+ function schemasMatch(a, b) {
528
+ return JSON.stringify(a) === JSON.stringify(b);
529
+ }
530
+ function detectConflictingProperties(schemas, context) {
531
+ const conflicts = [];
532
+ const propertyMap = /* @__PURE__ */ new Map();
533
+ for (const schema of schemas) {
534
+ const schemaProps = collectProperties(schema, context);
535
+ for (const [propName, propInfo] of schemaProps) {
536
+ const existing = propertyMap.get(propName);
537
+ if (existing) {
538
+ if (!schemasMatch(existing.schema, propInfo.schema)) {
539
+ conflicts.push(
540
+ `Property "${propName}" has conflicting definitions in ${existing.source} and ${propInfo.source}`
541
+ );
542
+ }
543
+ } else {
544
+ propertyMap.set(propName, propInfo);
545
+ }
546
+ }
547
+ }
548
+ return conflicts;
549
+ }
434
550
  function generateAllOf(schemas, isNullable2, context, currentSchema) {
435
551
  if (schemas.length === 1) {
436
552
  const singleSchema = context.generatePropertySchema(schemas[0], currentSchema, false);
437
553
  return wrapNullable(singleSchema, isNullable2);
438
554
  }
555
+ const conflicts = detectConflictingProperties(schemas, context);
556
+ let conflictDescription = "";
557
+ if (conflicts.length > 0) {
558
+ for (const conflict of conflicts) {
559
+ console.warn(`[openapi-to-zod] Warning: allOf composition conflict - ${conflict}`);
560
+ }
561
+ conflictDescription = `allOf property conflicts detected: ${conflicts.join("; ")}`;
562
+ }
439
563
  const allObjects = schemas.every((s) => s.type === "object" || s.properties || s.$ref || s.allOf);
440
- const schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema, false));
564
+ let result;
441
565
  if (allObjects) {
442
- let merged2 = schemaStrings[0];
566
+ let merged = context.generatePropertySchema(schemas[0], currentSchema, false);
567
+ for (let i = 1; i < schemas.length; i++) {
568
+ const schema = schemas[i];
569
+ if (schema.$ref) {
570
+ const refSchema = context.generatePropertySchema(schema, currentSchema, false);
571
+ merged = `${merged}.extend(${refSchema}.shape)`;
572
+ } else if (context.generateInlineObjectShape && (schema.properties || schema.type === "object")) {
573
+ const inlineShape = context.generateInlineObjectShape(schema, currentSchema);
574
+ merged = `${merged}.extend(${inlineShape})`;
575
+ } else {
576
+ const schemaString = context.generatePropertySchema(schema, currentSchema, false);
577
+ merged = `${merged}.extend(${schemaString}.shape)`;
578
+ }
579
+ }
580
+ result = merged;
581
+ } else {
582
+ const schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema, false));
583
+ let merged = schemaStrings[0];
443
584
  for (let i = 1; i < schemaStrings.length; i++) {
444
- merged2 = `${merged2}.merge(${schemaStrings[i]})`;
585
+ merged = `${merged}.and(${schemaStrings[i]})`;
445
586
  }
446
- return wrapNullable(merged2, isNullable2);
587
+ result = merged;
447
588
  }
448
- let merged = schemaStrings[0];
449
- for (let i = 1; i < schemaStrings.length; i++) {
450
- merged = `${merged}.and(${schemaStrings[i]})`;
589
+ if (conflictDescription) {
590
+ result = `${result}.describe("${conflictDescription}")`;
451
591
  }
452
- return wrapNullable(merged, isNullable2);
592
+ return wrapNullable(result, isNullable2);
453
593
  }
454
594
 
455
595
  // src/validators/number-validator.ts
@@ -888,7 +1028,7 @@ function configurePatternCache(size) {
888
1028
  PATTERN_CACHE = new LRUCache(size);
889
1029
  }
890
1030
  }
891
- var FORMAT_MAP = {
1031
+ var DEFAULT_FORMAT_MAP = {
892
1032
  uuid: "z.uuid()",
893
1033
  email: "z.email()",
894
1034
  uri: "z.url()",
@@ -898,7 +1038,6 @@ var FORMAT_MAP = {
898
1038
  byte: "z.base64()",
899
1039
  binary: "z.string()",
900
1040
  date: "z.iso.date()",
901
- "date-time": "z.iso.datetime()",
902
1041
  time: "z.iso.time()",
903
1042
  duration: 'z.string().refine((val) => /^P(?:(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?:\\d+H)?(?:\\d+M)?(?:\\d+(?:\\.\\d+)?S)?)?|\\d+W)$/.test(val) && !/^PT?$/.test(val), { message: "Must be a valid ISO 8601 duration" })',
904
1043
  ipv4: "z.ipv4()",
@@ -917,6 +1056,30 @@ var FORMAT_MAP = {
917
1056
  "json-pointer": 'z.string().refine((val) => val === "" || /^(\\/([^~/]|~0|~1)+)+$/.test(val), { message: "Must be a valid JSON Pointer (RFC 6901)" })',
918
1057
  "relative-json-pointer": 'z.string().refine((val) => /^(0|[1-9]\\d*)(#|(\\/([^~/]|~0|~1)+)*)$/.test(val), { message: "Must be a valid relative JSON Pointer" })'
919
1058
  };
1059
+ var FORMAT_MAP = {
1060
+ ...DEFAULT_FORMAT_MAP,
1061
+ "date-time": "z.iso.datetime()"
1062
+ };
1063
+ function configureDateTimeFormat(pattern) {
1064
+ if (!pattern) {
1065
+ FORMAT_MAP["date-time"] = "z.iso.datetime()";
1066
+ return;
1067
+ }
1068
+ const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
1069
+ if (patternStr === "") {
1070
+ FORMAT_MAP["date-time"] = "z.iso.datetime()";
1071
+ return;
1072
+ }
1073
+ try {
1074
+ new RegExp(patternStr);
1075
+ } catch (error) {
1076
+ throw new Error(
1077
+ `Invalid regular expression pattern for customDateTimeFormatRegex: ${patternStr}. ${error instanceof Error ? error.message : "Pattern is malformed"}`
1078
+ );
1079
+ }
1080
+ const escapedPattern = escapePattern(patternStr);
1081
+ FORMAT_MAP["date-time"] = `z.string().regex(/${escapedPattern}/)`;
1082
+ }
920
1083
  function generateStringValidation(schema, useDescribe) {
921
1084
  let validation = FORMAT_MAP[schema.format || ""] || "z.string()";
922
1085
  if (schema.minLength !== void 0) {
@@ -1102,6 +1265,15 @@ var _PropertyGenerator = class _PropertyGenerator {
1102
1265
  }
1103
1266
  return mappedSchemas;
1104
1267
  }
1268
+ /**
1269
+ * Resolve a $ref string to the actual schema
1270
+ */
1271
+ resolveSchemaRef(ref) {
1272
+ var _a, _b;
1273
+ const schemaName = ref.split("/").pop();
1274
+ if (!schemaName) return void 0;
1275
+ return (_b = (_a = this.context.spec.components) == null ? void 0 : _a.schemas) == null ? void 0 : _b[schemaName];
1276
+ }
1105
1277
  /**
1106
1278
  * Resolve a schema name through any aliases to get the actual schema name
1107
1279
  * If the schema is an alias (allOf with single $ref), return the target name
@@ -1179,7 +1351,7 @@ var _PropertyGenerator = class _PropertyGenerator {
1179
1351
  let schemaWithCatchall = baseSchema;
1180
1352
  if (baseSchema.includes(".union([") || baseSchema.includes(".discriminatedUnion(")) {
1181
1353
  schemaWithCatchall = baseSchema;
1182
- } else if (baseSchema.includes(".merge(")) {
1354
+ } else if (baseSchema.includes(".extend(")) {
1183
1355
  schemaWithCatchall = `${baseSchema}.catchall(z.unknown())`;
1184
1356
  }
1185
1357
  if (schema.unevaluatedProperties === false) {
@@ -1206,7 +1378,12 @@ var _PropertyGenerator = class _PropertyGenerator {
1206
1378
  if ((this.context.schemaType === "request" || this.context.schemaType === "response") && schema.properties) {
1207
1379
  schema = this.filterNestedProperties(schema);
1208
1380
  }
1209
- const nullable = isNullable(schema);
1381
+ const isSchemaRef = !!schema.$ref;
1382
+ const isEnum = !!schema.enum;
1383
+ const isConst = schema.const !== void 0;
1384
+ const shouldApplyDefaultNullable = !isTopLevel && !isSchemaRef && !isEnum && !isConst;
1385
+ const effectiveDefaultNullable = shouldApplyDefaultNullable ? this.context.defaultNullable : false;
1386
+ const nullable = isNullable(schema, effectiveDefaultNullable);
1210
1387
  if (hasMultipleTypes(schema)) {
1211
1388
  const union = this.generateMultiTypeUnion(schema, currentSchema);
1212
1389
  return wrapNullable(union, nullable);
@@ -1258,7 +1435,11 @@ var _PropertyGenerator = class _PropertyGenerator {
1258
1435
  let composition = generateAllOf(
1259
1436
  schema.allOf,
1260
1437
  nullable,
1261
- { generatePropertySchema: this.generatePropertySchema.bind(this) },
1438
+ {
1439
+ generatePropertySchema: this.generatePropertySchema.bind(this),
1440
+ generateInlineObjectShape: this.generateInlineObjectShape.bind(this),
1441
+ resolveSchemaRef: this.resolveSchemaRef.bind(this)
1442
+ },
1262
1443
  currentSchema
1263
1444
  );
1264
1445
  if (schema.unevaluatedProperties !== void 0) {
@@ -1274,7 +1455,8 @@ var _PropertyGenerator = class _PropertyGenerator {
1274
1455
  nullable,
1275
1456
  {
1276
1457
  generatePropertySchema: this.generatePropertySchema.bind(this),
1277
- resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this)
1458
+ resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
1459
+ resolveSchemaRef: this.resolveSchemaRef.bind(this)
1278
1460
  },
1279
1461
  {
1280
1462
  passthrough: needsPassthrough,
@@ -1295,7 +1477,8 @@ var _PropertyGenerator = class _PropertyGenerator {
1295
1477
  nullable,
1296
1478
  {
1297
1479
  generatePropertySchema: this.generatePropertySchema.bind(this),
1298
- resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this)
1480
+ resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
1481
+ resolveSchemaRef: this.resolveSchemaRef.bind(this)
1299
1482
  },
1300
1483
  {
1301
1484
  passthrough: needsPassthrough,
@@ -1358,7 +1541,17 @@ var _PropertyGenerator = class _PropertyGenerator {
1358
1541
  );
1359
1542
  validation = addDescription(validation, schema.description, this.context.useDescribe);
1360
1543
  } else {
1361
- validation = "z.record(z.string(), z.unknown())";
1544
+ switch (this.context.emptyObjectBehavior) {
1545
+ case "strict":
1546
+ validation = "z.strictObject({})";
1547
+ break;
1548
+ case "loose":
1549
+ validation = "z.looseObject({})";
1550
+ break;
1551
+ default:
1552
+ validation = "z.record(z.string(), z.unknown())";
1553
+ break;
1554
+ }
1362
1555
  validation = addDescription(validation, schema.description, this.context.useDescribe);
1363
1556
  }
1364
1557
  break;
@@ -1373,6 +1566,44 @@ var _PropertyGenerator = class _PropertyGenerator {
1373
1566
  }
1374
1567
  return result;
1375
1568
  }
1569
+ /**
1570
+ * Generate inline object shape for use with .extend()
1571
+ * Returns just the shape object literal: { prop1: z.string(), prop2: z.number() }
1572
+ *
1573
+ * This method is specifically for allOf compositions where we need to pass
1574
+ * the shape directly to .extend() instead of using z.object({...}).shape.
1575
+ * This avoids the .nullable().shape bug when inline objects have nullable: true.
1576
+ *
1577
+ * According to Zod docs (https://zod.dev/api?id=extend):
1578
+ * - .extend() accepts an object of shape definitions
1579
+ * - e.g., baseSchema.extend({ prop: z.string() })
1580
+ */
1581
+ generateInlineObjectShape(schema, currentSchema) {
1582
+ const required = new Set(schema.required || []);
1583
+ const properties = [];
1584
+ if (schema.properties) {
1585
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
1586
+ if (!this.shouldIncludeProperty(propSchema)) {
1587
+ continue;
1588
+ }
1589
+ const isRequired = required.has(propName);
1590
+ const zodSchema = this.generatePropertySchema(propSchema, currentSchema);
1591
+ const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
1592
+ const quotedPropName = validIdentifier.test(propName) ? propName : `"${propName}"`;
1593
+ let propertyDef = `${quotedPropName}: ${zodSchema}`;
1594
+ if (!isRequired) {
1595
+ propertyDef += ".optional()";
1596
+ }
1597
+ properties.push(propertyDef);
1598
+ }
1599
+ }
1600
+ if (properties.length === 0) {
1601
+ return "{}";
1602
+ }
1603
+ return `{
1604
+ ${properties.map((p) => ` ${p}`).join(",\n")}
1605
+ }`;
1606
+ }
1376
1607
  };
1377
1608
  // Performance optimization: Lookup table for faster inclusion checks
1378
1609
  _PropertyGenerator.INCLUSION_RULES = {
@@ -1516,6 +1747,59 @@ function formatFilterStatistics(stats) {
1516
1747
  return lines.join("\n");
1517
1748
  }
1518
1749
 
1750
+ // src/utils/ref-resolver.ts
1751
+ function resolveRef2(obj, spec, maxDepth = 10) {
1752
+ var _a, _b, _c, _d;
1753
+ if (!obj || typeof obj !== "object" || maxDepth <= 0) return obj;
1754
+ if (!obj.$ref) return obj;
1755
+ const ref = obj.$ref;
1756
+ let resolved = null;
1757
+ const paramMatch = ref.match(/^#\/components\/parameters\/(.+)$/);
1758
+ const requestBodyMatch = ref.match(/^#\/components\/requestBodies\/(.+)$/);
1759
+ const responseMatch = ref.match(/^#\/components\/responses\/(.+)$/);
1760
+ const schemaMatch = ref.match(/^#\/components\/schemas\/(.+)$/);
1761
+ if (paramMatch && ((_a = spec.components) == null ? void 0 : _a.parameters)) {
1762
+ const name = paramMatch[1];
1763
+ resolved = spec.components.parameters[name];
1764
+ } else if (requestBodyMatch && ((_b = spec.components) == null ? void 0 : _b.requestBodies)) {
1765
+ const name = requestBodyMatch[1];
1766
+ resolved = spec.components.requestBodies[name];
1767
+ } else if (responseMatch && ((_c = spec.components) == null ? void 0 : _c.responses)) {
1768
+ const name = responseMatch[1];
1769
+ resolved = spec.components.responses[name];
1770
+ } else if (schemaMatch && ((_d = spec.components) == null ? void 0 : _d.schemas)) {
1771
+ const name = schemaMatch[1];
1772
+ resolved = spec.components.schemas[name];
1773
+ }
1774
+ if (resolved) {
1775
+ if (resolved.$ref) {
1776
+ return resolveRef2(resolved, spec, maxDepth - 1);
1777
+ }
1778
+ return resolved;
1779
+ }
1780
+ return obj;
1781
+ }
1782
+ function resolveParameterRef(param, spec) {
1783
+ return resolveRef2(param, spec);
1784
+ }
1785
+ function mergeParameters(pathParams, operationParams, spec) {
1786
+ const resolvedPathParams = (pathParams || []).map((p) => resolveParameterRef(p, spec));
1787
+ const resolvedOperationParams = (operationParams || []).map((p) => resolveParameterRef(p, spec));
1788
+ const merged = [...resolvedPathParams];
1789
+ for (const opParam of resolvedOperationParams) {
1790
+ if (!opParam || typeof opParam !== "object") continue;
1791
+ const existingIndex = merged.findIndex(
1792
+ (p) => p && typeof p === "object" && p.name === opParam.name && p.in === opParam.in
1793
+ );
1794
+ if (existingIndex >= 0) {
1795
+ merged[existingIndex] = opParam;
1796
+ } else {
1797
+ merged.push(opParam);
1798
+ }
1799
+ }
1800
+ return merged;
1801
+ }
1802
+
1519
1803
  // src/openapi-generator.ts
1520
1804
  var OpenApiGenerator = class {
1521
1805
  constructor(options) {
@@ -1525,7 +1809,7 @@ var OpenApiGenerator = class {
1525
1809
  this.schemaUsageMap = /* @__PURE__ */ new Map();
1526
1810
  this.needsZodImport = true;
1527
1811
  this.filterStats = createFilterStatistics();
1528
- var _a, _b, _c, _d, _e;
1812
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1529
1813
  if (!options.input) {
1530
1814
  throw new ConfigurationError("Input path is required", { providedOptions: options });
1531
1815
  }
@@ -1535,21 +1819,28 @@ var OpenApiGenerator = class {
1535
1819
  output: options.output,
1536
1820
  includeDescriptions: (_a = options.includeDescriptions) != null ? _a : true,
1537
1821
  useDescribe: (_b = options.useDescribe) != null ? _b : false,
1822
+ defaultNullable: (_c = options.defaultNullable) != null ? _c : false,
1823
+ emptyObjectBehavior: (_d = options.emptyObjectBehavior) != null ? _d : "loose",
1538
1824
  schemaType: options.schemaType || "all",
1539
1825
  prefix: options.prefix,
1540
1826
  suffix: options.suffix,
1541
1827
  stripSchemaPrefix: options.stripSchemaPrefix,
1542
- showStats: (_c = options.showStats) != null ? _c : true,
1828
+ stripPathPrefix: options.stripPathPrefix,
1829
+ showStats: (_e = options.showStats) != null ? _e : true,
1543
1830
  request: options.request,
1544
1831
  response: options.response,
1545
1832
  operationFilters: options.operationFilters,
1546
1833
  ignoreHeaders: options.ignoreHeaders,
1547
- cacheSize: (_d = options.cacheSize) != null ? _d : 1e3,
1548
- batchSize: (_e = options.batchSize) != null ? _e : 10
1834
+ cacheSize: (_f = options.cacheSize) != null ? _f : 1e3,
1835
+ batchSize: (_g = options.batchSize) != null ? _g : 10,
1836
+ customDateTimeFormatRegex: options.customDateTimeFormatRegex
1549
1837
  };
1550
1838
  if (this.options.cacheSize) {
1551
1839
  configurePatternCache(this.options.cacheSize);
1552
1840
  }
1841
+ if (this.options.customDateTimeFormatRegex) {
1842
+ configureDateTimeFormat(this.options.customDateTimeFormatRegex);
1843
+ }
1553
1844
  try {
1554
1845
  const fs = require("fs");
1555
1846
  if (!fs.existsSync(this.options.input)) {
@@ -1612,6 +1903,8 @@ var OpenApiGenerator = class {
1612
1903
  mode: this.requestOptions.mode,
1613
1904
  includeDescriptions: this.requestOptions.includeDescriptions,
1614
1905
  useDescribe: this.requestOptions.useDescribe,
1906
+ defaultNullable: (_h = this.options.defaultNullable) != null ? _h : false,
1907
+ emptyObjectBehavior: (_i = this.options.emptyObjectBehavior) != null ? _i : "loose",
1615
1908
  namingOptions: {
1616
1909
  prefix: this.options.prefix,
1617
1910
  suffix: this.options.suffix
@@ -1965,7 +2258,7 @@ var OpenApiGenerator = class {
1965
2258
  * Generate schema for a component
1966
2259
  */
1967
2260
  generateComponentSchema(name, schema) {
1968
- var _a, _b;
2261
+ var _a, _b, _c, _d;
1969
2262
  if (!this.schemaDependencies.has(name)) {
1970
2263
  this.schemaDependencies.set(name, /* @__PURE__ */ new Set());
1971
2264
  }
@@ -1997,14 +2290,15 @@ ${typeCode}`;
1997
2290
  mode: resolvedOptions.mode,
1998
2291
  includeDescriptions: resolvedOptions.includeDescriptions,
1999
2292
  useDescribe: resolvedOptions.useDescribe,
2293
+ defaultNullable: (_b = this.options.defaultNullable) != null ? _b : false,
2294
+ emptyObjectBehavior: (_c = this.options.emptyObjectBehavior) != null ? _c : "loose",
2000
2295
  namingOptions: {
2001
2296
  prefix: this.options.prefix,
2002
2297
  suffix: this.options.suffix
2003
2298
  },
2004
2299
  stripSchemaPrefix: this.options.stripSchemaPrefix
2005
2300
  });
2006
- const isAlias = !!(schema.$ref && !schema.properties && !schema.allOf && !schema.oneOf && !schema.anyOf);
2007
- const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, isAlias);
2301
+ const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, true);
2008
2302
  const zodSchemaCode = `${jsdoc}export const ${schemaName} = ${zodSchema};`;
2009
2303
  if (zodSchema.includes("z.discriminatedUnion(")) {
2010
2304
  const match = zodSchema.match(/z\.discriminatedUnion\([^,]+,\s*\[([^\]]+)\]/);
@@ -2014,7 +2308,7 @@ ${typeCode}`;
2014
2308
  const depMatch = ref.match(/^([a-z][a-zA-Z0-9]*?)Schema$/);
2015
2309
  if (depMatch) {
2016
2310
  const depName = depMatch[1].charAt(0).toUpperCase() + depMatch[1].slice(1);
2017
- (_b = this.schemaDependencies.get(name)) == null ? void 0 : _b.add(depName);
2311
+ (_d = this.schemaDependencies.get(name)) == null ? void 0 : _d.add(depName);
2018
2312
  }
2019
2313
  }
2020
2314
  }
@@ -2038,16 +2332,20 @@ ${typeCode}`;
2038
2332
  if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
2039
2333
  continue;
2040
2334
  }
2041
- if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
2042
- continue;
2043
- }
2044
- const queryParams = operation.parameters.filter(
2335
+ const allParams = mergeParameters(pathItem.parameters, operation.parameters, this.spec);
2336
+ const queryParams = allParams.filter(
2045
2337
  (param) => param && typeof param === "object" && param.in === "query"
2046
2338
  );
2047
2339
  if (queryParams.length === 0) {
2048
2340
  continue;
2049
2341
  }
2050
- const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
2342
+ let pascalOperationId;
2343
+ if (operation.operationId) {
2344
+ pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
2345
+ } else {
2346
+ const strippedPath = stripPathPrefix(path, this.options.stripPathPrefix);
2347
+ pascalOperationId = this.generateMethodNameFromPath(method, strippedPath);
2348
+ }
2051
2349
  const schemaName = `${pascalOperationId}QueryParams`;
2052
2350
  if (!this.schemaDependencies.has(schemaName)) {
2053
2351
  this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
@@ -2095,8 +2393,9 @@ ${propsCode}
2095
2393
  const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
2096
2394
  const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
2097
2395
  const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}QueryParamsSchema`;
2396
+ const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path}`;
2098
2397
  const jsdoc = `/**
2099
- * Query parameters for ${operation.operationId}
2398
+ * Query parameters for ${jsdocOperationName}
2100
2399
  */
2101
2400
  `;
2102
2401
  const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
@@ -2105,6 +2404,35 @@ ${propsCode}
2105
2404
  }
2106
2405
  }
2107
2406
  }
2407
+ /**
2408
+ * Generate a PascalCase method name from HTTP method and path
2409
+ * Used as fallback when operationId is not available
2410
+ * @internal
2411
+ */
2412
+ generateMethodNameFromPath(method, path) {
2413
+ const segments = path.split("/").filter(Boolean).map((segment) => {
2414
+ if (segment.startsWith("{") && segment.endsWith("}")) {
2415
+ const paramName = segment.slice(1, -1);
2416
+ return `By${this.capitalizeSegment(paramName)}`;
2417
+ }
2418
+ return this.capitalizeSegment(segment);
2419
+ }).join("");
2420
+ const capitalizedMethod = method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
2421
+ return `${capitalizedMethod}${segments}`;
2422
+ }
2423
+ /**
2424
+ * Capitalizes a path segment, handling special characters like dashes, underscores, and dots
2425
+ * @internal
2426
+ */
2427
+ capitalizeSegment(str) {
2428
+ if (str.includes("-") || str.includes("_") || str.includes(".")) {
2429
+ return str.split(/[-_.]/).map((part) => {
2430
+ if (!part) return "";
2431
+ return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
2432
+ }).join("");
2433
+ }
2434
+ return str.charAt(0).toUpperCase() + str.slice(1);
2435
+ }
2108
2436
  /**
2109
2437
  * Check if a header should be ignored based on filter patterns
2110
2438
  * @internal
@@ -2141,16 +2469,20 @@ ${propsCode}
2141
2469
  if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
2142
2470
  continue;
2143
2471
  }
2144
- if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
2145
- continue;
2146
- }
2147
- const headerParams = operation.parameters.filter(
2472
+ const allParams = mergeParameters(pathItem.parameters, operation.parameters, this.spec);
2473
+ const headerParams = allParams.filter(
2148
2474
  (param) => param && typeof param === "object" && param.in === "header" && !this.shouldIgnoreHeader(param.name)
2149
2475
  );
2150
2476
  if (headerParams.length === 0) {
2151
2477
  continue;
2152
2478
  }
2153
- const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
2479
+ let pascalOperationId;
2480
+ if (operation.operationId) {
2481
+ pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
2482
+ } else {
2483
+ const strippedPath = stripPathPrefix(path, this.options.stripPathPrefix);
2484
+ pascalOperationId = this.generateMethodNameFromPath(method, strippedPath);
2485
+ }
2154
2486
  const schemaName = `${pascalOperationId}HeaderParams`;
2155
2487
  if (!this.schemaDependencies.has(schemaName)) {
2156
2488
  this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
@@ -2187,8 +2519,9 @@ ${propsCode}
2187
2519
  const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
2188
2520
  const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
2189
2521
  const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}HeaderParamsSchema`;
2522
+ const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path}`;
2190
2523
  const jsdoc = `/**
2191
- * Header parameters for ${operation.operationId}
2524
+ * Header parameters for ${jsdocOperationName}
2192
2525
  */
2193
2526
  `;
2194
2527
  const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
@@ -2227,13 +2560,23 @@ ${propsCode}
2227
2560
  }
2228
2561
  const type = schema.type;
2229
2562
  if (type === "string") {
2563
+ const formatMap = {
2564
+ email: "z.email()",
2565
+ uri: "z.url()",
2566
+ url: "z.url()",
2567
+ uuid: "z.uuid()"
2568
+ };
2569
+ if (schema.format && formatMap[schema.format]) {
2570
+ let zodType2 = formatMap[schema.format];
2571
+ if (schema.minLength !== void 0) zodType2 = `${zodType2}.min(${schema.minLength})`;
2572
+ if (schema.maxLength !== void 0) zodType2 = `${zodType2}.max(${schema.maxLength})`;
2573
+ if (schema.pattern) zodType2 = `${zodType2}.regex(/${schema.pattern}/)`;
2574
+ return zodType2;
2575
+ }
2230
2576
  let zodType = "z.string()";
2231
2577
  if (schema.minLength !== void 0) zodType = `${zodType}.min(${schema.minLength})`;
2232
2578
  if (schema.maxLength !== void 0) zodType = `${zodType}.max(${schema.maxLength})`;
2233
2579
  if (schema.pattern) zodType = `${zodType}.regex(/${schema.pattern}/)`;
2234
- if (schema.format === "email") zodType = `${zodType}.email()`;
2235
- if (schema.format === "uri" || schema.format === "url") zodType = `${zodType}.url()`;
2236
- if (schema.format === "uuid") zodType = `${zodType}.uuid()`;
2237
2580
  return zodType;
2238
2581
  }
2239
2582
  if (type === "number" || type === "integer") {
@@ -2258,11 +2601,6 @@ ${propsCode}
2258
2601
  }
2259
2602
  return "z.unknown()";
2260
2603
  }
2261
- // REMOVED: generateNativeEnum method - no longer needed as we only generate Zod schemas
2262
- // REMOVED: toEnumKey method - was only used by generateNativeEnum
2263
- // REMOVED: addConstraintsToJSDoc method - was only used for native TypeScript types
2264
- // REMOVED: generateNativeTypeDefinition method - was only used for native TypeScript types
2265
- // REMOVED: generateObjectType method - was only used for native TypeScript types
2266
2604
  /**
2267
2605
  * Topological sort for schema dependencies
2268
2606
  * Returns schemas in the order they should be declared