@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.mjs CHANGED
@@ -163,14 +163,17 @@ function escapeJSDoc(str) {
163
163
  function wrapNullable(validation, isNullable2) {
164
164
  return isNullable2 ? `${validation}.nullable()` : validation;
165
165
  }
166
- function isNullable(schema) {
166
+ function isNullable(schema, defaultNullable = false) {
167
167
  if (schema.nullable === true) {
168
168
  return true;
169
169
  }
170
+ if (schema.nullable === false) {
171
+ return false;
172
+ }
170
173
  if (Array.isArray(schema.type)) {
171
174
  return schema.type.includes("null");
172
175
  }
173
- return false;
176
+ return defaultNullable;
174
177
  }
175
178
  function getPrimaryType(schema) {
176
179
  if (Array.isArray(schema.type)) {
@@ -336,6 +339,22 @@ function stripPrefix(input, pattern, ensureLeadingChar) {
336
339
  }
337
340
  return input;
338
341
  }
342
+ function stripPathPrefix(path, pattern) {
343
+ if (!pattern) {
344
+ return path;
345
+ }
346
+ if (!isGlobPattern(pattern)) {
347
+ let normalizedPattern = pattern.trim();
348
+ if (!normalizedPattern.startsWith("/")) {
349
+ normalizedPattern = `/${normalizedPattern}`;
350
+ }
351
+ if (normalizedPattern.endsWith("/") && normalizedPattern !== "/") {
352
+ normalizedPattern = normalizedPattern.slice(0, -1);
353
+ }
354
+ return stripPrefix(path, normalizedPattern, "/");
355
+ }
356
+ return stripPrefix(path, pattern, "/");
357
+ }
339
358
 
340
359
  // src/validators/array-validator.ts
341
360
  function generateArrayValidation(schema, context) {
@@ -384,12 +403,56 @@ function generateArrayValidation(schema, context) {
384
403
  }
385
404
 
386
405
  // src/validators/composition-validator.ts
406
+ function isDiscriminatorRequired(schemas, discriminator, context) {
407
+ const invalidSchemas = [];
408
+ for (const schema of schemas) {
409
+ const resolved = resolveSchema(schema, context);
410
+ const required = resolved.required || [];
411
+ if (!required.includes(discriminator)) {
412
+ const schemaName = schema.$ref ? schema.$ref.split("/").pop() || "inline" : "inline";
413
+ invalidSchemas.push(schemaName);
414
+ }
415
+ }
416
+ return {
417
+ valid: invalidSchemas.length === 0,
418
+ invalidSchemas
419
+ };
420
+ }
387
421
  function generateUnion(schemas, discriminator, isNullable2, context, options, currentSchema) {
422
+ if (schemas.length === 0) {
423
+ console.warn(
424
+ "[openapi-to-zod] Warning: Empty oneOf/anyOf array encountered. This is likely a malformed OpenAPI spec. Generating z.never() as fallback."
425
+ );
426
+ return wrapNullable(
427
+ 'z.never().describe("Empty oneOf/anyOf in OpenAPI spec - no valid schema defined")',
428
+ isNullable2
429
+ );
430
+ }
431
+ if (schemas.length === 1) {
432
+ let singleSchema = context.generatePropertySchema(schemas[0], currentSchema);
433
+ if ((options == null ? void 0 : options.passthrough) && !singleSchema.includes(".catchall(")) {
434
+ singleSchema = `${singleSchema}.catchall(z.unknown())`;
435
+ }
436
+ return wrapNullable(singleSchema, isNullable2);
437
+ }
388
438
  if (discriminator) {
389
439
  let resolvedSchemas = schemas;
390
440
  if ((options == null ? void 0 : options.discriminatorMapping) && context.resolveDiscriminatorMapping) {
391
441
  resolvedSchemas = context.resolveDiscriminatorMapping(options.discriminatorMapping, schemas);
392
442
  }
443
+ const discriminatorCheck = isDiscriminatorRequired(resolvedSchemas, discriminator, context);
444
+ if (!discriminatorCheck.valid) {
445
+ console.warn(
446
+ `[openapi-to-zod] Warning: Discriminator "${discriminator}" is not required in schemas: ${discriminatorCheck.invalidSchemas.join(", ")}. Falling back to z.union() instead of z.discriminatedUnion().`
447
+ );
448
+ let schemaStrings3 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
449
+ if (options == null ? void 0 : options.passthrough) {
450
+ schemaStrings3 = schemaStrings3.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
451
+ }
452
+ const fallbackDescription = `Discriminator "${discriminator}" is optional in some schemas (${discriminatorCheck.invalidSchemas.join(", ")}), using z.union() instead of z.discriminatedUnion()`;
453
+ const union3 = `z.union([${schemaStrings3.join(", ")}]).describe("${fallbackDescription}")`;
454
+ return wrapNullable(union3, isNullable2);
455
+ }
393
456
  let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
394
457
  if (options == null ? void 0 : options.passthrough) {
395
458
  schemaStrings2 = schemaStrings2.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
@@ -404,25 +467,102 @@ function generateUnion(schemas, discriminator, isNullable2, context, options, cu
404
467
  const union = `z.union([${schemaStrings.join(", ")}])`;
405
468
  return wrapNullable(union, isNullable2);
406
469
  }
470
+ function resolveSchema(schema, context) {
471
+ if (schema.$ref && context.resolveSchemaRef) {
472
+ const resolved = context.resolveSchemaRef(schema.$ref);
473
+ if (resolved) {
474
+ return resolved;
475
+ }
476
+ }
477
+ return schema;
478
+ }
479
+ function collectProperties(schema, context) {
480
+ const resolved = resolveSchema(schema, context);
481
+ const props = /* @__PURE__ */ new Map();
482
+ const sourceName = schema.$ref ? schema.$ref.split("/").pop() || "unknown" : "inline";
483
+ if (resolved.properties) {
484
+ for (const [key, value] of Object.entries(resolved.properties)) {
485
+ props.set(key, { schema: value, source: sourceName });
486
+ }
487
+ }
488
+ if (resolved.allOf) {
489
+ for (const subSchema of resolved.allOf) {
490
+ const subProps = collectProperties(subSchema, context);
491
+ for (const [key, value] of subProps) {
492
+ if (!props.has(key)) {
493
+ props.set(key, value);
494
+ }
495
+ }
496
+ }
497
+ }
498
+ return props;
499
+ }
500
+ function schemasMatch(a, b) {
501
+ return JSON.stringify(a) === JSON.stringify(b);
502
+ }
503
+ function detectConflictingProperties(schemas, context) {
504
+ const conflicts = [];
505
+ const propertyMap = /* @__PURE__ */ new Map();
506
+ for (const schema of schemas) {
507
+ const schemaProps = collectProperties(schema, context);
508
+ for (const [propName, propInfo] of schemaProps) {
509
+ const existing = propertyMap.get(propName);
510
+ if (existing) {
511
+ if (!schemasMatch(existing.schema, propInfo.schema)) {
512
+ conflicts.push(
513
+ `Property "${propName}" has conflicting definitions in ${existing.source} and ${propInfo.source}`
514
+ );
515
+ }
516
+ } else {
517
+ propertyMap.set(propName, propInfo);
518
+ }
519
+ }
520
+ }
521
+ return conflicts;
522
+ }
407
523
  function generateAllOf(schemas, isNullable2, context, currentSchema) {
408
524
  if (schemas.length === 1) {
409
525
  const singleSchema = context.generatePropertySchema(schemas[0], currentSchema, false);
410
526
  return wrapNullable(singleSchema, isNullable2);
411
527
  }
528
+ const conflicts = detectConflictingProperties(schemas, context);
529
+ let conflictDescription = "";
530
+ if (conflicts.length > 0) {
531
+ for (const conflict of conflicts) {
532
+ console.warn(`[openapi-to-zod] Warning: allOf composition conflict - ${conflict}`);
533
+ }
534
+ conflictDescription = `allOf property conflicts detected: ${conflicts.join("; ")}`;
535
+ }
412
536
  const allObjects = schemas.every((s) => s.type === "object" || s.properties || s.$ref || s.allOf);
413
- const schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema, false));
537
+ let result;
414
538
  if (allObjects) {
415
- let merged2 = schemaStrings[0];
539
+ let merged = context.generatePropertySchema(schemas[0], currentSchema, false);
540
+ for (let i = 1; i < schemas.length; i++) {
541
+ const schema = schemas[i];
542
+ if (schema.$ref) {
543
+ const refSchema = context.generatePropertySchema(schema, currentSchema, false);
544
+ merged = `${merged}.extend(${refSchema}.shape)`;
545
+ } else if (context.generateInlineObjectShape && (schema.properties || schema.type === "object")) {
546
+ const inlineShape = context.generateInlineObjectShape(schema, currentSchema);
547
+ merged = `${merged}.extend(${inlineShape})`;
548
+ } else {
549
+ const schemaString = context.generatePropertySchema(schema, currentSchema, false);
550
+ merged = `${merged}.extend(${schemaString}.shape)`;
551
+ }
552
+ }
553
+ result = merged;
554
+ } else {
555
+ const schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema, false));
556
+ let merged = schemaStrings[0];
416
557
  for (let i = 1; i < schemaStrings.length; i++) {
417
- merged2 = `${merged2}.merge(${schemaStrings[i]})`;
558
+ merged = `${merged}.and(${schemaStrings[i]})`;
418
559
  }
419
- return wrapNullable(merged2, isNullable2);
560
+ result = merged;
420
561
  }
421
- let merged = schemaStrings[0];
422
- for (let i = 1; i < schemaStrings.length; i++) {
423
- merged = `${merged}.and(${schemaStrings[i]})`;
562
+ if (conflictDescription) {
563
+ result = `${result}.describe("${conflictDescription}")`;
424
564
  }
425
- return wrapNullable(merged, isNullable2);
565
+ return wrapNullable(result, isNullable2);
426
566
  }
427
567
 
428
568
  // src/validators/number-validator.ts
@@ -861,7 +1001,7 @@ function configurePatternCache(size) {
861
1001
  PATTERN_CACHE = new LRUCache(size);
862
1002
  }
863
1003
  }
864
- var FORMAT_MAP = {
1004
+ var DEFAULT_FORMAT_MAP = {
865
1005
  uuid: "z.uuid()",
866
1006
  email: "z.email()",
867
1007
  uri: "z.url()",
@@ -871,7 +1011,6 @@ var FORMAT_MAP = {
871
1011
  byte: "z.base64()",
872
1012
  binary: "z.string()",
873
1013
  date: "z.iso.date()",
874
- "date-time": "z.iso.datetime()",
875
1014
  time: "z.iso.time()",
876
1015
  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" })',
877
1016
  ipv4: "z.ipv4()",
@@ -890,6 +1029,30 @@ var FORMAT_MAP = {
890
1029
  "json-pointer": 'z.string().refine((val) => val === "" || /^(\\/([^~/]|~0|~1)+)+$/.test(val), { message: "Must be a valid JSON Pointer (RFC 6901)" })',
891
1030
  "relative-json-pointer": 'z.string().refine((val) => /^(0|[1-9]\\d*)(#|(\\/([^~/]|~0|~1)+)*)$/.test(val), { message: "Must be a valid relative JSON Pointer" })'
892
1031
  };
1032
+ var FORMAT_MAP = {
1033
+ ...DEFAULT_FORMAT_MAP,
1034
+ "date-time": "z.iso.datetime()"
1035
+ };
1036
+ function configureDateTimeFormat(pattern) {
1037
+ if (!pattern) {
1038
+ FORMAT_MAP["date-time"] = "z.iso.datetime()";
1039
+ return;
1040
+ }
1041
+ const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
1042
+ if (patternStr === "") {
1043
+ FORMAT_MAP["date-time"] = "z.iso.datetime()";
1044
+ return;
1045
+ }
1046
+ try {
1047
+ new RegExp(patternStr);
1048
+ } catch (error) {
1049
+ throw new Error(
1050
+ `Invalid regular expression pattern for customDateTimeFormatRegex: ${patternStr}. ${error instanceof Error ? error.message : "Pattern is malformed"}`
1051
+ );
1052
+ }
1053
+ const escapedPattern = escapePattern(patternStr);
1054
+ FORMAT_MAP["date-time"] = `z.string().regex(/${escapedPattern}/)`;
1055
+ }
893
1056
  function generateStringValidation(schema, useDescribe) {
894
1057
  let validation = FORMAT_MAP[schema.format || ""] || "z.string()";
895
1058
  if (schema.minLength !== void 0) {
@@ -1075,6 +1238,15 @@ var _PropertyGenerator = class _PropertyGenerator {
1075
1238
  }
1076
1239
  return mappedSchemas;
1077
1240
  }
1241
+ /**
1242
+ * Resolve a $ref string to the actual schema
1243
+ */
1244
+ resolveSchemaRef(ref) {
1245
+ var _a, _b;
1246
+ const schemaName = ref.split("/").pop();
1247
+ if (!schemaName) return void 0;
1248
+ return (_b = (_a = this.context.spec.components) == null ? void 0 : _a.schemas) == null ? void 0 : _b[schemaName];
1249
+ }
1078
1250
  /**
1079
1251
  * Resolve a schema name through any aliases to get the actual schema name
1080
1252
  * If the schema is an alias (allOf with single $ref), return the target name
@@ -1152,7 +1324,7 @@ var _PropertyGenerator = class _PropertyGenerator {
1152
1324
  let schemaWithCatchall = baseSchema;
1153
1325
  if (baseSchema.includes(".union([") || baseSchema.includes(".discriminatedUnion(")) {
1154
1326
  schemaWithCatchall = baseSchema;
1155
- } else if (baseSchema.includes(".merge(")) {
1327
+ } else if (baseSchema.includes(".extend(")) {
1156
1328
  schemaWithCatchall = `${baseSchema}.catchall(z.unknown())`;
1157
1329
  }
1158
1330
  if (schema.unevaluatedProperties === false) {
@@ -1179,7 +1351,12 @@ var _PropertyGenerator = class _PropertyGenerator {
1179
1351
  if ((this.context.schemaType === "request" || this.context.schemaType === "response") && schema.properties) {
1180
1352
  schema = this.filterNestedProperties(schema);
1181
1353
  }
1182
- const nullable = isNullable(schema);
1354
+ const isSchemaRef = !!schema.$ref;
1355
+ const isEnum = !!schema.enum;
1356
+ const isConst = schema.const !== void 0;
1357
+ const shouldApplyDefaultNullable = !isTopLevel && !isSchemaRef && !isEnum && !isConst;
1358
+ const effectiveDefaultNullable = shouldApplyDefaultNullable ? this.context.defaultNullable : false;
1359
+ const nullable = isNullable(schema, effectiveDefaultNullable);
1183
1360
  if (hasMultipleTypes(schema)) {
1184
1361
  const union = this.generateMultiTypeUnion(schema, currentSchema);
1185
1362
  return wrapNullable(union, nullable);
@@ -1231,7 +1408,11 @@ var _PropertyGenerator = class _PropertyGenerator {
1231
1408
  let composition = generateAllOf(
1232
1409
  schema.allOf,
1233
1410
  nullable,
1234
- { generatePropertySchema: this.generatePropertySchema.bind(this) },
1411
+ {
1412
+ generatePropertySchema: this.generatePropertySchema.bind(this),
1413
+ generateInlineObjectShape: this.generateInlineObjectShape.bind(this),
1414
+ resolveSchemaRef: this.resolveSchemaRef.bind(this)
1415
+ },
1235
1416
  currentSchema
1236
1417
  );
1237
1418
  if (schema.unevaluatedProperties !== void 0) {
@@ -1247,7 +1428,8 @@ var _PropertyGenerator = class _PropertyGenerator {
1247
1428
  nullable,
1248
1429
  {
1249
1430
  generatePropertySchema: this.generatePropertySchema.bind(this),
1250
- resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this)
1431
+ resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
1432
+ resolveSchemaRef: this.resolveSchemaRef.bind(this)
1251
1433
  },
1252
1434
  {
1253
1435
  passthrough: needsPassthrough,
@@ -1268,7 +1450,8 @@ var _PropertyGenerator = class _PropertyGenerator {
1268
1450
  nullable,
1269
1451
  {
1270
1452
  generatePropertySchema: this.generatePropertySchema.bind(this),
1271
- resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this)
1453
+ resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
1454
+ resolveSchemaRef: this.resolveSchemaRef.bind(this)
1272
1455
  },
1273
1456
  {
1274
1457
  passthrough: needsPassthrough,
@@ -1331,7 +1514,17 @@ var _PropertyGenerator = class _PropertyGenerator {
1331
1514
  );
1332
1515
  validation = addDescription(validation, schema.description, this.context.useDescribe);
1333
1516
  } else {
1334
- validation = "z.record(z.string(), z.unknown())";
1517
+ switch (this.context.emptyObjectBehavior) {
1518
+ case "strict":
1519
+ validation = "z.strictObject({})";
1520
+ break;
1521
+ case "loose":
1522
+ validation = "z.looseObject({})";
1523
+ break;
1524
+ default:
1525
+ validation = "z.record(z.string(), z.unknown())";
1526
+ break;
1527
+ }
1335
1528
  validation = addDescription(validation, schema.description, this.context.useDescribe);
1336
1529
  }
1337
1530
  break;
@@ -1346,6 +1539,44 @@ var _PropertyGenerator = class _PropertyGenerator {
1346
1539
  }
1347
1540
  return result;
1348
1541
  }
1542
+ /**
1543
+ * Generate inline object shape for use with .extend()
1544
+ * Returns just the shape object literal: { prop1: z.string(), prop2: z.number() }
1545
+ *
1546
+ * This method is specifically for allOf compositions where we need to pass
1547
+ * the shape directly to .extend() instead of using z.object({...}).shape.
1548
+ * This avoids the .nullable().shape bug when inline objects have nullable: true.
1549
+ *
1550
+ * According to Zod docs (https://zod.dev/api?id=extend):
1551
+ * - .extend() accepts an object of shape definitions
1552
+ * - e.g., baseSchema.extend({ prop: z.string() })
1553
+ */
1554
+ generateInlineObjectShape(schema, currentSchema) {
1555
+ const required = new Set(schema.required || []);
1556
+ const properties = [];
1557
+ if (schema.properties) {
1558
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
1559
+ if (!this.shouldIncludeProperty(propSchema)) {
1560
+ continue;
1561
+ }
1562
+ const isRequired = required.has(propName);
1563
+ const zodSchema = this.generatePropertySchema(propSchema, currentSchema);
1564
+ const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
1565
+ const quotedPropName = validIdentifier.test(propName) ? propName : `"${propName}"`;
1566
+ let propertyDef = `${quotedPropName}: ${zodSchema}`;
1567
+ if (!isRequired) {
1568
+ propertyDef += ".optional()";
1569
+ }
1570
+ properties.push(propertyDef);
1571
+ }
1572
+ }
1573
+ if (properties.length === 0) {
1574
+ return "{}";
1575
+ }
1576
+ return `{
1577
+ ${properties.map((p) => ` ${p}`).join(",\n")}
1578
+ }`;
1579
+ }
1349
1580
  };
1350
1581
  // Performance optimization: Lookup table for faster inclusion checks
1351
1582
  _PropertyGenerator.INCLUSION_RULES = {
@@ -1489,6 +1720,59 @@ function formatFilterStatistics(stats) {
1489
1720
  return lines.join("\n");
1490
1721
  }
1491
1722
 
1723
+ // src/utils/ref-resolver.ts
1724
+ function resolveRef2(obj, spec, maxDepth = 10) {
1725
+ var _a, _b, _c, _d;
1726
+ if (!obj || typeof obj !== "object" || maxDepth <= 0) return obj;
1727
+ if (!obj.$ref) return obj;
1728
+ const ref = obj.$ref;
1729
+ let resolved = null;
1730
+ const paramMatch = ref.match(/^#\/components\/parameters\/(.+)$/);
1731
+ const requestBodyMatch = ref.match(/^#\/components\/requestBodies\/(.+)$/);
1732
+ const responseMatch = ref.match(/^#\/components\/responses\/(.+)$/);
1733
+ const schemaMatch = ref.match(/^#\/components\/schemas\/(.+)$/);
1734
+ if (paramMatch && ((_a = spec.components) == null ? void 0 : _a.parameters)) {
1735
+ const name = paramMatch[1];
1736
+ resolved = spec.components.parameters[name];
1737
+ } else if (requestBodyMatch && ((_b = spec.components) == null ? void 0 : _b.requestBodies)) {
1738
+ const name = requestBodyMatch[1];
1739
+ resolved = spec.components.requestBodies[name];
1740
+ } else if (responseMatch && ((_c = spec.components) == null ? void 0 : _c.responses)) {
1741
+ const name = responseMatch[1];
1742
+ resolved = spec.components.responses[name];
1743
+ } else if (schemaMatch && ((_d = spec.components) == null ? void 0 : _d.schemas)) {
1744
+ const name = schemaMatch[1];
1745
+ resolved = spec.components.schemas[name];
1746
+ }
1747
+ if (resolved) {
1748
+ if (resolved.$ref) {
1749
+ return resolveRef2(resolved, spec, maxDepth - 1);
1750
+ }
1751
+ return resolved;
1752
+ }
1753
+ return obj;
1754
+ }
1755
+ function resolveParameterRef(param, spec) {
1756
+ return resolveRef2(param, spec);
1757
+ }
1758
+ function mergeParameters(pathParams, operationParams, spec) {
1759
+ const resolvedPathParams = (pathParams || []).map((p) => resolveParameterRef(p, spec));
1760
+ const resolvedOperationParams = (operationParams || []).map((p) => resolveParameterRef(p, spec));
1761
+ const merged = [...resolvedPathParams];
1762
+ for (const opParam of resolvedOperationParams) {
1763
+ if (!opParam || typeof opParam !== "object") continue;
1764
+ const existingIndex = merged.findIndex(
1765
+ (p) => p && typeof p === "object" && p.name === opParam.name && p.in === opParam.in
1766
+ );
1767
+ if (existingIndex >= 0) {
1768
+ merged[existingIndex] = opParam;
1769
+ } else {
1770
+ merged.push(opParam);
1771
+ }
1772
+ }
1773
+ return merged;
1774
+ }
1775
+
1492
1776
  // src/openapi-generator.ts
1493
1777
  var OpenApiGenerator = class {
1494
1778
  constructor(options) {
@@ -1498,7 +1782,7 @@ var OpenApiGenerator = class {
1498
1782
  this.schemaUsageMap = /* @__PURE__ */ new Map();
1499
1783
  this.needsZodImport = true;
1500
1784
  this.filterStats = createFilterStatistics();
1501
- var _a, _b, _c, _d, _e;
1785
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1502
1786
  if (!options.input) {
1503
1787
  throw new ConfigurationError("Input path is required", { providedOptions: options });
1504
1788
  }
@@ -1508,21 +1792,28 @@ var OpenApiGenerator = class {
1508
1792
  output: options.output,
1509
1793
  includeDescriptions: (_a = options.includeDescriptions) != null ? _a : true,
1510
1794
  useDescribe: (_b = options.useDescribe) != null ? _b : false,
1795
+ defaultNullable: (_c = options.defaultNullable) != null ? _c : false,
1796
+ emptyObjectBehavior: (_d = options.emptyObjectBehavior) != null ? _d : "loose",
1511
1797
  schemaType: options.schemaType || "all",
1512
1798
  prefix: options.prefix,
1513
1799
  suffix: options.suffix,
1514
1800
  stripSchemaPrefix: options.stripSchemaPrefix,
1515
- showStats: (_c = options.showStats) != null ? _c : true,
1801
+ stripPathPrefix: options.stripPathPrefix,
1802
+ showStats: (_e = options.showStats) != null ? _e : true,
1516
1803
  request: options.request,
1517
1804
  response: options.response,
1518
1805
  operationFilters: options.operationFilters,
1519
1806
  ignoreHeaders: options.ignoreHeaders,
1520
- cacheSize: (_d = options.cacheSize) != null ? _d : 1e3,
1521
- batchSize: (_e = options.batchSize) != null ? _e : 10
1807
+ cacheSize: (_f = options.cacheSize) != null ? _f : 1e3,
1808
+ batchSize: (_g = options.batchSize) != null ? _g : 10,
1809
+ customDateTimeFormatRegex: options.customDateTimeFormatRegex
1522
1810
  };
1523
1811
  if (this.options.cacheSize) {
1524
1812
  configurePatternCache(this.options.cacheSize);
1525
1813
  }
1814
+ if (this.options.customDateTimeFormatRegex) {
1815
+ configureDateTimeFormat(this.options.customDateTimeFormatRegex);
1816
+ }
1526
1817
  try {
1527
1818
  const fs = __require("fs");
1528
1819
  if (!fs.existsSync(this.options.input)) {
@@ -1585,6 +1876,8 @@ var OpenApiGenerator = class {
1585
1876
  mode: this.requestOptions.mode,
1586
1877
  includeDescriptions: this.requestOptions.includeDescriptions,
1587
1878
  useDescribe: this.requestOptions.useDescribe,
1879
+ defaultNullable: (_h = this.options.defaultNullable) != null ? _h : false,
1880
+ emptyObjectBehavior: (_i = this.options.emptyObjectBehavior) != null ? _i : "loose",
1588
1881
  namingOptions: {
1589
1882
  prefix: this.options.prefix,
1590
1883
  suffix: this.options.suffix
@@ -1938,7 +2231,7 @@ var OpenApiGenerator = class {
1938
2231
  * Generate schema for a component
1939
2232
  */
1940
2233
  generateComponentSchema(name, schema) {
1941
- var _a, _b;
2234
+ var _a, _b, _c, _d;
1942
2235
  if (!this.schemaDependencies.has(name)) {
1943
2236
  this.schemaDependencies.set(name, /* @__PURE__ */ new Set());
1944
2237
  }
@@ -1970,14 +2263,15 @@ ${typeCode}`;
1970
2263
  mode: resolvedOptions.mode,
1971
2264
  includeDescriptions: resolvedOptions.includeDescriptions,
1972
2265
  useDescribe: resolvedOptions.useDescribe,
2266
+ defaultNullable: (_b = this.options.defaultNullable) != null ? _b : false,
2267
+ emptyObjectBehavior: (_c = this.options.emptyObjectBehavior) != null ? _c : "loose",
1973
2268
  namingOptions: {
1974
2269
  prefix: this.options.prefix,
1975
2270
  suffix: this.options.suffix
1976
2271
  },
1977
2272
  stripSchemaPrefix: this.options.stripSchemaPrefix
1978
2273
  });
1979
- const isAlias = !!(schema.$ref && !schema.properties && !schema.allOf && !schema.oneOf && !schema.anyOf);
1980
- const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, isAlias);
2274
+ const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, true);
1981
2275
  const zodSchemaCode = `${jsdoc}export const ${schemaName} = ${zodSchema};`;
1982
2276
  if (zodSchema.includes("z.discriminatedUnion(")) {
1983
2277
  const match = zodSchema.match(/z\.discriminatedUnion\([^,]+,\s*\[([^\]]+)\]/);
@@ -1987,7 +2281,7 @@ ${typeCode}`;
1987
2281
  const depMatch = ref.match(/^([a-z][a-zA-Z0-9]*?)Schema$/);
1988
2282
  if (depMatch) {
1989
2283
  const depName = depMatch[1].charAt(0).toUpperCase() + depMatch[1].slice(1);
1990
- (_b = this.schemaDependencies.get(name)) == null ? void 0 : _b.add(depName);
2284
+ (_d = this.schemaDependencies.get(name)) == null ? void 0 : _d.add(depName);
1991
2285
  }
1992
2286
  }
1993
2287
  }
@@ -2011,16 +2305,20 @@ ${typeCode}`;
2011
2305
  if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
2012
2306
  continue;
2013
2307
  }
2014
- if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
2015
- continue;
2016
- }
2017
- const queryParams = operation.parameters.filter(
2308
+ const allParams = mergeParameters(pathItem.parameters, operation.parameters, this.spec);
2309
+ const queryParams = allParams.filter(
2018
2310
  (param) => param && typeof param === "object" && param.in === "query"
2019
2311
  );
2020
2312
  if (queryParams.length === 0) {
2021
2313
  continue;
2022
2314
  }
2023
- const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
2315
+ let pascalOperationId;
2316
+ if (operation.operationId) {
2317
+ pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
2318
+ } else {
2319
+ const strippedPath = stripPathPrefix(path, this.options.stripPathPrefix);
2320
+ pascalOperationId = this.generateMethodNameFromPath(method, strippedPath);
2321
+ }
2024
2322
  const schemaName = `${pascalOperationId}QueryParams`;
2025
2323
  if (!this.schemaDependencies.has(schemaName)) {
2026
2324
  this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
@@ -2068,8 +2366,9 @@ ${propsCode}
2068
2366
  const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
2069
2367
  const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
2070
2368
  const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}QueryParamsSchema`;
2369
+ const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path}`;
2071
2370
  const jsdoc = `/**
2072
- * Query parameters for ${operation.operationId}
2371
+ * Query parameters for ${jsdocOperationName}
2073
2372
  */
2074
2373
  `;
2075
2374
  const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
@@ -2078,6 +2377,35 @@ ${propsCode}
2078
2377
  }
2079
2378
  }
2080
2379
  }
2380
+ /**
2381
+ * Generate a PascalCase method name from HTTP method and path
2382
+ * Used as fallback when operationId is not available
2383
+ * @internal
2384
+ */
2385
+ generateMethodNameFromPath(method, path) {
2386
+ const segments = path.split("/").filter(Boolean).map((segment) => {
2387
+ if (segment.startsWith("{") && segment.endsWith("}")) {
2388
+ const paramName = segment.slice(1, -1);
2389
+ return `By${this.capitalizeSegment(paramName)}`;
2390
+ }
2391
+ return this.capitalizeSegment(segment);
2392
+ }).join("");
2393
+ const capitalizedMethod = method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
2394
+ return `${capitalizedMethod}${segments}`;
2395
+ }
2396
+ /**
2397
+ * Capitalizes a path segment, handling special characters like dashes, underscores, and dots
2398
+ * @internal
2399
+ */
2400
+ capitalizeSegment(str) {
2401
+ if (str.includes("-") || str.includes("_") || str.includes(".")) {
2402
+ return str.split(/[-_.]/).map((part) => {
2403
+ if (!part) return "";
2404
+ return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
2405
+ }).join("");
2406
+ }
2407
+ return str.charAt(0).toUpperCase() + str.slice(1);
2408
+ }
2081
2409
  /**
2082
2410
  * Check if a header should be ignored based on filter patterns
2083
2411
  * @internal
@@ -2114,16 +2442,20 @@ ${propsCode}
2114
2442
  if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
2115
2443
  continue;
2116
2444
  }
2117
- if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
2118
- continue;
2119
- }
2120
- const headerParams = operation.parameters.filter(
2445
+ const allParams = mergeParameters(pathItem.parameters, operation.parameters, this.spec);
2446
+ const headerParams = allParams.filter(
2121
2447
  (param) => param && typeof param === "object" && param.in === "header" && !this.shouldIgnoreHeader(param.name)
2122
2448
  );
2123
2449
  if (headerParams.length === 0) {
2124
2450
  continue;
2125
2451
  }
2126
- const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
2452
+ let pascalOperationId;
2453
+ if (operation.operationId) {
2454
+ pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
2455
+ } else {
2456
+ const strippedPath = stripPathPrefix(path, this.options.stripPathPrefix);
2457
+ pascalOperationId = this.generateMethodNameFromPath(method, strippedPath);
2458
+ }
2127
2459
  const schemaName = `${pascalOperationId}HeaderParams`;
2128
2460
  if (!this.schemaDependencies.has(schemaName)) {
2129
2461
  this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
@@ -2160,8 +2492,9 @@ ${propsCode}
2160
2492
  const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
2161
2493
  const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
2162
2494
  const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}HeaderParamsSchema`;
2495
+ const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path}`;
2163
2496
  const jsdoc = `/**
2164
- * Header parameters for ${operation.operationId}
2497
+ * Header parameters for ${jsdocOperationName}
2165
2498
  */
2166
2499
  `;
2167
2500
  const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
@@ -2200,13 +2533,23 @@ ${propsCode}
2200
2533
  }
2201
2534
  const type = schema.type;
2202
2535
  if (type === "string") {
2536
+ const formatMap = {
2537
+ email: "z.email()",
2538
+ uri: "z.url()",
2539
+ url: "z.url()",
2540
+ uuid: "z.uuid()"
2541
+ };
2542
+ if (schema.format && formatMap[schema.format]) {
2543
+ let zodType2 = formatMap[schema.format];
2544
+ if (schema.minLength !== void 0) zodType2 = `${zodType2}.min(${schema.minLength})`;
2545
+ if (schema.maxLength !== void 0) zodType2 = `${zodType2}.max(${schema.maxLength})`;
2546
+ if (schema.pattern) zodType2 = `${zodType2}.regex(/${schema.pattern}/)`;
2547
+ return zodType2;
2548
+ }
2203
2549
  let zodType = "z.string()";
2204
2550
  if (schema.minLength !== void 0) zodType = `${zodType}.min(${schema.minLength})`;
2205
2551
  if (schema.maxLength !== void 0) zodType = `${zodType}.max(${schema.maxLength})`;
2206
2552
  if (schema.pattern) zodType = `${zodType}.regex(/${schema.pattern}/)`;
2207
- if (schema.format === "email") zodType = `${zodType}.email()`;
2208
- if (schema.format === "uri" || schema.format === "url") zodType = `${zodType}.url()`;
2209
- if (schema.format === "uuid") zodType = `${zodType}.uuid()`;
2210
2553
  return zodType;
2211
2554
  }
2212
2555
  if (type === "number" || type === "integer") {
@@ -2231,11 +2574,6 @@ ${propsCode}
2231
2574
  }
2232
2575
  return "z.unknown()";
2233
2576
  }
2234
- // REMOVED: generateNativeEnum method - no longer needed as we only generate Zod schemas
2235
- // REMOVED: toEnumKey method - was only used by generateNativeEnum
2236
- // REMOVED: addConstraintsToJSDoc method - was only used for native TypeScript types
2237
- // REMOVED: generateNativeTypeDefinition method - was only used for native TypeScript types
2238
- // REMOVED: generateObjectType method - was only used for native TypeScript types
2239
2577
  /**
2240
2578
  * Topological sort for schema dependencies
2241
2579
  * Returns schemas in the order they should be declared