@cerios/openapi-to-zod 1.1.1 → 1.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
@@ -5241,14 +5241,17 @@ function escapeJSDoc(str) {
5241
5241
  function wrapNullable(validation, isNullable2) {
5242
5242
  return isNullable2 ? `${validation}.nullable()` : validation;
5243
5243
  }
5244
- function isNullable(schema) {
5244
+ function isNullable(schema, defaultNullable = false) {
5245
5245
  if (schema.nullable === true) {
5246
5246
  return true;
5247
5247
  }
5248
+ if (schema.nullable === false) {
5249
+ return false;
5250
+ }
5248
5251
  if (Array.isArray(schema.type)) {
5249
5252
  return schema.type.includes("null");
5250
5253
  }
5251
- return false;
5254
+ return defaultNullable;
5252
5255
  }
5253
5256
  function getPrimaryType(schema) {
5254
5257
  if (Array.isArray(schema.type)) {
@@ -5434,6 +5437,22 @@ function stripPrefix(input, pattern, ensureLeadingChar) {
5434
5437
  }
5435
5438
  return input;
5436
5439
  }
5440
+ function stripPathPrefix(path2, pattern) {
5441
+ if (!pattern) {
5442
+ return path2;
5443
+ }
5444
+ if (!isGlobPattern(pattern)) {
5445
+ let normalizedPattern = pattern.trim();
5446
+ if (!normalizedPattern.startsWith("/")) {
5447
+ normalizedPattern = `/${normalizedPattern}`;
5448
+ }
5449
+ if (normalizedPattern.endsWith("/") && normalizedPattern !== "/") {
5450
+ normalizedPattern = normalizedPattern.slice(0, -1);
5451
+ }
5452
+ return stripPrefix(path2, normalizedPattern, "/");
5453
+ }
5454
+ return stripPrefix(path2, pattern, "/");
5455
+ }
5437
5456
  var init_pattern_utils = __esm({
5438
5457
  "src/utils/pattern-utils.ts"() {
5439
5458
  "use strict";
@@ -5999,6 +6018,26 @@ function configurePatternCache(size) {
5999
6018
  PATTERN_CACHE = new LRUCache(size);
6000
6019
  }
6001
6020
  }
6021
+ function configureDateTimeFormat(pattern) {
6022
+ if (!pattern) {
6023
+ FORMAT_MAP["date-time"] = "z.iso.datetime()";
6024
+ return;
6025
+ }
6026
+ const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
6027
+ if (patternStr === "") {
6028
+ FORMAT_MAP["date-time"] = "z.iso.datetime()";
6029
+ return;
6030
+ }
6031
+ try {
6032
+ new RegExp(patternStr);
6033
+ } catch (error) {
6034
+ throw new Error(
6035
+ `Invalid regular expression pattern for customDateTimeFormatRegex: ${patternStr}. ${error instanceof Error ? error.message : "Pattern is malformed"}`
6036
+ );
6037
+ }
6038
+ const escapedPattern = escapePattern(patternStr);
6039
+ FORMAT_MAP["date-time"] = `z.string().regex(/${escapedPattern}/)`;
6040
+ }
6002
6041
  function generateStringValidation(schema, useDescribe) {
6003
6042
  let validation = FORMAT_MAP[schema.format || ""] || "z.string()";
6004
6043
  if (schema.minLength !== void 0) {
@@ -6064,7 +6103,7 @@ function generateStringValidation(schema, useDescribe) {
6064
6103
  }
6065
6104
  return addDescription(validation, schema.description, useDescribe);
6066
6105
  }
6067
- var PATTERN_CACHE, FORMAT_MAP;
6106
+ var PATTERN_CACHE, DEFAULT_FORMAT_MAP, FORMAT_MAP;
6068
6107
  var init_string_validator = __esm({
6069
6108
  "src/validators/string-validator.ts"() {
6070
6109
  "use strict";
@@ -6072,7 +6111,7 @@ var init_string_validator = __esm({
6072
6111
  init_lru_cache();
6073
6112
  init_string_utils();
6074
6113
  PATTERN_CACHE = new LRUCache(1e3);
6075
- FORMAT_MAP = {
6114
+ DEFAULT_FORMAT_MAP = {
6076
6115
  uuid: "z.uuid()",
6077
6116
  email: "z.email()",
6078
6117
  uri: "z.url()",
@@ -6082,7 +6121,6 @@ var init_string_validator = __esm({
6082
6121
  byte: "z.base64()",
6083
6122
  binary: "z.string()",
6084
6123
  date: "z.iso.date()",
6085
- "date-time": "z.iso.datetime()",
6086
6124
  time: "z.iso.time()",
6087
6125
  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" })',
6088
6126
  ipv4: "z.ipv4()",
@@ -6101,6 +6139,10 @@ var init_string_validator = __esm({
6101
6139
  "json-pointer": 'z.string().refine((val) => val === "" || /^(\\/([^~/]|~0|~1)+)+$/.test(val), { message: "Must be a valid JSON Pointer (RFC 6901)" })',
6102
6140
  "relative-json-pointer": 'z.string().refine((val) => /^(0|[1-9]\\d*)(#|(\\/([^~/]|~0|~1)+)*)$/.test(val), { message: "Must be a valid relative JSON Pointer" })'
6103
6141
  };
6142
+ FORMAT_MAP = {
6143
+ ...DEFAULT_FORMAT_MAP,
6144
+ "date-time": "z.iso.datetime()"
6145
+ };
6104
6146
  }
6105
6147
  });
6106
6148
 
@@ -6341,7 +6383,8 @@ var init_property_generator = __esm({
6341
6383
  if ((this.context.schemaType === "request" || this.context.schemaType === "response") && schema.properties) {
6342
6384
  schema = this.filterNestedProperties(schema);
6343
6385
  }
6344
- const nullable = isNullable(schema);
6386
+ const effectiveDefaultNullable = isTopLevel ? false : this.context.defaultNullable;
6387
+ const nullable = isNullable(schema, effectiveDefaultNullable);
6345
6388
  if (hasMultipleTypes(schema)) {
6346
6389
  const union = this.generateMultiTypeUnion(schema, currentSchema);
6347
6390
  return wrapNullable(union, nullable);
@@ -6685,7 +6728,7 @@ var init_openapi_generator = __esm({
6685
6728
  this.schemaUsageMap = /* @__PURE__ */ new Map();
6686
6729
  this.needsZodImport = true;
6687
6730
  this.filterStats = createFilterStatistics();
6688
- var _a, _b, _c, _d, _e;
6731
+ var _a, _b, _c, _d, _e, _f, _g;
6689
6732
  if (!options.input) {
6690
6733
  throw new ConfigurationError("Input path is required", { providedOptions: options });
6691
6734
  }
@@ -6695,21 +6738,27 @@ var init_openapi_generator = __esm({
6695
6738
  output: options.output,
6696
6739
  includeDescriptions: (_a = options.includeDescriptions) != null ? _a : true,
6697
6740
  useDescribe: (_b = options.useDescribe) != null ? _b : false,
6741
+ defaultNullable: (_c = options.defaultNullable) != null ? _c : false,
6698
6742
  schemaType: options.schemaType || "all",
6699
6743
  prefix: options.prefix,
6700
6744
  suffix: options.suffix,
6701
6745
  stripSchemaPrefix: options.stripSchemaPrefix,
6702
- showStats: (_c = options.showStats) != null ? _c : true,
6746
+ stripPathPrefix: options.stripPathPrefix,
6747
+ showStats: (_d = options.showStats) != null ? _d : true,
6703
6748
  request: options.request,
6704
6749
  response: options.response,
6705
6750
  operationFilters: options.operationFilters,
6706
6751
  ignoreHeaders: options.ignoreHeaders,
6707
- cacheSize: (_d = options.cacheSize) != null ? _d : 1e3,
6708
- batchSize: (_e = options.batchSize) != null ? _e : 10
6752
+ cacheSize: (_e = options.cacheSize) != null ? _e : 1e3,
6753
+ batchSize: (_f = options.batchSize) != null ? _f : 10,
6754
+ customDateTimeFormatRegex: options.customDateTimeFormatRegex
6709
6755
  };
6710
6756
  if (this.options.cacheSize) {
6711
6757
  configurePatternCache(this.options.cacheSize);
6712
6758
  }
6759
+ if (this.options.customDateTimeFormatRegex) {
6760
+ configureDateTimeFormat(this.options.customDateTimeFormatRegex);
6761
+ }
6713
6762
  try {
6714
6763
  const fs = __require("fs");
6715
6764
  if (!fs.existsSync(this.options.input)) {
@@ -6772,6 +6821,7 @@ var init_openapi_generator = __esm({
6772
6821
  mode: this.requestOptions.mode,
6773
6822
  includeDescriptions: this.requestOptions.includeDescriptions,
6774
6823
  useDescribe: this.requestOptions.useDescribe,
6824
+ defaultNullable: (_g = this.options.defaultNullable) != null ? _g : false,
6775
6825
  namingOptions: {
6776
6826
  prefix: this.options.prefix,
6777
6827
  suffix: this.options.suffix
@@ -7125,7 +7175,7 @@ var init_openapi_generator = __esm({
7125
7175
  * Generate schema for a component
7126
7176
  */
7127
7177
  generateComponentSchema(name, schema) {
7128
- var _a, _b;
7178
+ var _a, _b, _c;
7129
7179
  if (!this.schemaDependencies.has(name)) {
7130
7180
  this.schemaDependencies.set(name, /* @__PURE__ */ new Set());
7131
7181
  }
@@ -7157,14 +7207,14 @@ ${typeCode}`;
7157
7207
  mode: resolvedOptions.mode,
7158
7208
  includeDescriptions: resolvedOptions.includeDescriptions,
7159
7209
  useDescribe: resolvedOptions.useDescribe,
7210
+ defaultNullable: (_b = this.options.defaultNullable) != null ? _b : false,
7160
7211
  namingOptions: {
7161
7212
  prefix: this.options.prefix,
7162
7213
  suffix: this.options.suffix
7163
7214
  },
7164
7215
  stripSchemaPrefix: this.options.stripSchemaPrefix
7165
7216
  });
7166
- const isAlias = !!(schema.$ref && !schema.properties && !schema.allOf && !schema.oneOf && !schema.anyOf);
7167
- const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, isAlias);
7217
+ const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, true);
7168
7218
  const zodSchemaCode = `${jsdoc}export const ${schemaName} = ${zodSchema};`;
7169
7219
  if (zodSchema.includes("z.discriminatedUnion(")) {
7170
7220
  const match = zodSchema.match(/z\.discriminatedUnion\([^,]+,\s*\[([^\]]+)\]/);
@@ -7174,7 +7224,7 @@ ${typeCode}`;
7174
7224
  const depMatch = ref.match(/^([a-z][a-zA-Z0-9]*?)Schema$/);
7175
7225
  if (depMatch) {
7176
7226
  const depName = depMatch[1].charAt(0).toUpperCase() + depMatch[1].slice(1);
7177
- (_b = this.schemaDependencies.get(name)) == null ? void 0 : _b.add(depName);
7227
+ (_c = this.schemaDependencies.get(name)) == null ? void 0 : _c.add(depName);
7178
7228
  }
7179
7229
  }
7180
7230
  }
@@ -7198,7 +7248,7 @@ ${typeCode}`;
7198
7248
  if (!shouldIncludeOperation(operation, path2, method, this.options.operationFilters)) {
7199
7249
  continue;
7200
7250
  }
7201
- if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
7251
+ if (!operation.parameters || !Array.isArray(operation.parameters)) {
7202
7252
  continue;
7203
7253
  }
7204
7254
  const queryParams = operation.parameters.filter(
@@ -7207,7 +7257,13 @@ ${typeCode}`;
7207
7257
  if (queryParams.length === 0) {
7208
7258
  continue;
7209
7259
  }
7210
- const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
7260
+ let pascalOperationId;
7261
+ if (operation.operationId) {
7262
+ pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
7263
+ } else {
7264
+ const strippedPath = stripPathPrefix(path2, this.options.stripPathPrefix);
7265
+ pascalOperationId = this.generateMethodNameFromPath(method, strippedPath);
7266
+ }
7211
7267
  const schemaName = `${pascalOperationId}QueryParams`;
7212
7268
  if (!this.schemaDependencies.has(schemaName)) {
7213
7269
  this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
@@ -7255,8 +7311,9 @@ ${propsCode}
7255
7311
  const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
7256
7312
  const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
7257
7313
  const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}QueryParamsSchema`;
7314
+ const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path2}`;
7258
7315
  const jsdoc = `/**
7259
- * Query parameters for ${operation.operationId}
7316
+ * Query parameters for ${jsdocOperationName}
7260
7317
  */
7261
7318
  `;
7262
7319
  const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
@@ -7265,6 +7322,35 @@ ${propsCode}
7265
7322
  }
7266
7323
  }
7267
7324
  }
7325
+ /**
7326
+ * Generate a PascalCase method name from HTTP method and path
7327
+ * Used as fallback when operationId is not available
7328
+ * @internal
7329
+ */
7330
+ generateMethodNameFromPath(method, path2) {
7331
+ const segments = path2.split("/").filter(Boolean).map((segment) => {
7332
+ if (segment.startsWith("{") && segment.endsWith("}")) {
7333
+ const paramName = segment.slice(1, -1);
7334
+ return `By${this.capitalizeSegment(paramName)}`;
7335
+ }
7336
+ return this.capitalizeSegment(segment);
7337
+ }).join("");
7338
+ const capitalizedMethod = method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
7339
+ return `${capitalizedMethod}${segments}`;
7340
+ }
7341
+ /**
7342
+ * Capitalizes a path segment, handling special characters like dashes, underscores, and dots
7343
+ * @internal
7344
+ */
7345
+ capitalizeSegment(str) {
7346
+ if (str.includes("-") || str.includes("_") || str.includes(".")) {
7347
+ return str.split(/[-_.]/).map((part) => {
7348
+ if (!part) return "";
7349
+ return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
7350
+ }).join("");
7351
+ }
7352
+ return str.charAt(0).toUpperCase() + str.slice(1);
7353
+ }
7268
7354
  /**
7269
7355
  * Check if a header should be ignored based on filter patterns
7270
7356
  * @internal
@@ -7301,7 +7387,7 @@ ${propsCode}
7301
7387
  if (!shouldIncludeOperation(operation, path2, method, this.options.operationFilters)) {
7302
7388
  continue;
7303
7389
  }
7304
- if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
7390
+ if (!operation.parameters || !Array.isArray(operation.parameters)) {
7305
7391
  continue;
7306
7392
  }
7307
7393
  const headerParams = operation.parameters.filter(
@@ -7310,7 +7396,13 @@ ${propsCode}
7310
7396
  if (headerParams.length === 0) {
7311
7397
  continue;
7312
7398
  }
7313
- const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
7399
+ let pascalOperationId;
7400
+ if (operation.operationId) {
7401
+ pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
7402
+ } else {
7403
+ const strippedPath = stripPathPrefix(path2, this.options.stripPathPrefix);
7404
+ pascalOperationId = this.generateMethodNameFromPath(method, strippedPath);
7405
+ }
7314
7406
  const schemaName = `${pascalOperationId}HeaderParams`;
7315
7407
  if (!this.schemaDependencies.has(schemaName)) {
7316
7408
  this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
@@ -7347,8 +7439,9 @@ ${propsCode}
7347
7439
  const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
7348
7440
  const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
7349
7441
  const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}HeaderParamsSchema`;
7442
+ const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path2}`;
7350
7443
  const jsdoc = `/**
7351
- * Header parameters for ${operation.operationId}
7444
+ * Header parameters for ${jsdocOperationName}
7352
7445
  */
7353
7446
  `;
7354
7447
  const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
@@ -7529,7 +7622,8 @@ var init_config_schemas = __esm({
7529
7622
  RequestResponseOptionsSchema = z.strictObject({
7530
7623
  mode: z.enum(["strict", "normal", "loose"]).optional(),
7531
7624
  useDescribe: z.boolean().optional(),
7532
- includeDescriptions: z.boolean().optional()
7625
+ includeDescriptions: z.boolean().optional(),
7626
+ defaultNullable: z.boolean().optional()
7533
7627
  });
7534
7628
  OperationFiltersSchema = z.strictObject({
7535
7629
  includeTags: z.array(z.string()).optional(),
@@ -7660,10 +7754,12 @@ function mergeConfigWithDefaults(config) {
7660
7754
  mode: defaults.mode,
7661
7755
  includeDescriptions: defaults.includeDescriptions,
7662
7756
  useDescribe: defaults.useDescribe,
7757
+ defaultNullable: defaults.defaultNullable,
7663
7758
  schemaType: defaults.schemaType,
7664
7759
  prefix: defaults.prefix,
7665
7760
  suffix: defaults.suffix,
7666
7761
  showStats: defaults.showStats,
7762
+ customDateTimeFormatRegex: defaults.customDateTimeFormatRegex,
7667
7763
  // Override with spec-specific values (including required input/output)
7668
7764
  ...spec
7669
7765
  };
@@ -7684,6 +7780,7 @@ var init_config_loader = __esm({
7684
7780
  output: z2.string(),
7685
7781
  includeDescriptions: z2.boolean().optional(),
7686
7782
  useDescribe: z2.boolean().optional(),
7783
+ defaultNullable: z2.boolean().optional(),
7687
7784
  schemaType: z2.enum(["all", "request", "response"]).optional(),
7688
7785
  prefix: z2.string().optional(),
7689
7786
  suffix: z2.string().optional(),
@@ -7694,13 +7791,28 @@ var init_config_loader = __esm({
7694
7791
  name: z2.string().optional(),
7695
7792
  operationFilters: OperationFiltersSchema.optional(),
7696
7793
  cacheSize: z2.number().positive().optional(),
7697
- batchSize: z2.number().positive().optional()
7794
+ batchSize: z2.number().positive().optional(),
7795
+ customDateTimeFormatRegex: z2.union([
7796
+ z2.string().refine(
7797
+ (pattern) => {
7798
+ try {
7799
+ new RegExp(pattern);
7800
+ return true;
7801
+ } catch {
7802
+ return false;
7803
+ }
7804
+ },
7805
+ { message: "Must be a valid regular expression pattern" }
7806
+ ),
7807
+ z2.instanceof(RegExp)
7808
+ ]).optional()
7698
7809
  });
7699
7810
  ConfigFileSchema = z2.strictObject({
7700
7811
  defaults: z2.strictObject({
7701
7812
  mode: z2.enum(["strict", "normal", "loose"]).optional(),
7702
7813
  includeDescriptions: z2.boolean().optional(),
7703
7814
  useDescribe: z2.boolean().optional(),
7815
+ defaultNullable: z2.boolean().optional(),
7704
7816
  schemaType: z2.enum(["all", "request", "response"]).optional(),
7705
7817
  prefix: z2.string().optional(),
7706
7818
  suffix: z2.string().optional(),
@@ -7710,7 +7822,21 @@ var init_config_loader = __esm({
7710
7822
  response: RequestResponseOptionsSchema.optional(),
7711
7823
  operationFilters: OperationFiltersSchema.optional(),
7712
7824
  cacheSize: z2.number().positive().optional(),
7713
- batchSize: z2.number().positive().optional()
7825
+ batchSize: z2.number().positive().optional(),
7826
+ customDateTimeFormatRegex: z2.union([
7827
+ z2.string().refine(
7828
+ (pattern) => {
7829
+ try {
7830
+ new RegExp(pattern);
7831
+ return true;
7832
+ } catch {
7833
+ return false;
7834
+ }
7835
+ },
7836
+ { message: "Must be a valid regular expression pattern" }
7837
+ ),
7838
+ z2.instanceof(RegExp)
7839
+ ]).optional()
7714
7840
  }).optional(),
7715
7841
  specs: z2.array(OpenApiGeneratorOptionsSchema).min(1, {
7716
7842
  message: "Configuration must include at least one specification. Each specification should have 'input' and 'output' paths."