@alt-stack/zod-openapi 1.1.3 → 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
@@ -231,6 +231,9 @@ function isStringRegistration(reg) {
231
231
  function isStringsRegistration(reg) {
232
232
  return reg.type === "string" && "formats" in reg;
233
233
  }
234
+ function isSupportedStringFormat(format) {
235
+ return Object.prototype.hasOwnProperty.call(SUPPORTED_STRING_FORMATS_MAP, format);
236
+ }
234
237
  function getTypeFormatPairs(reg) {
235
238
  if (isStringRegistration(reg)) {
236
239
  return [{ type: "string", format: reg.format }];
@@ -281,6 +284,7 @@ var ZodSchemaRegistry = class {
281
284
  * Reverse-lookup helper: given a string format, return the registered schema's exported variable name
282
285
  */
283
286
  getSchemaExportedVariableNameForStringFormat(format) {
287
+ if (!isSupportedStringFormat(format)) return void 0;
284
288
  for (const registration of this.map.values()) {
285
289
  if (registration.type !== "string") continue;
286
290
  if (isStringRegistration(registration) && registration.format === format) {
@@ -292,6 +296,17 @@ var ZodSchemaRegistry = class {
292
296
  }
293
297
  return void 0;
294
298
  }
299
+ /**
300
+ * Reverse-lookup helper: given a primitive type, return the registered schema's exported variable name
301
+ */
302
+ getSchemaExportedVariableNameForPrimitiveType(type) {
303
+ for (const registration of this.map.values()) {
304
+ if (registration.type === type) {
305
+ return registration.schemaExportedVariableName;
306
+ }
307
+ }
308
+ return void 0;
309
+ }
295
310
  };
296
311
  var schemaRegistry = new ZodSchemaRegistry();
297
312
  function registerZodSchemaToOpenApiSchema(schema, openApiSchema) {
@@ -300,6 +315,9 @@ function registerZodSchemaToOpenApiSchema(schema, openApiSchema) {
300
315
  function getSchemaExportedVariableNameForStringFormat(format) {
301
316
  return schemaRegistry.getSchemaExportedVariableNameForStringFormat(format);
302
317
  }
318
+ function getSchemaExportedVariableNameForPrimitiveType(type) {
319
+ return schemaRegistry.getSchemaExportedVariableNameForPrimitiveType(type);
320
+ }
303
321
  function clearZodSchemaToOpenApiSchemaRegistry() {
304
322
  schemaRegistry.clear();
305
323
  }
@@ -669,11 +687,188 @@ function generateRouteSchemaNames(route) {
669
687
  return result;
670
688
  }
671
689
 
672
- // src/to-typescript.ts
690
+ // src/interface-generator.ts
673
691
  var validIdentifierRegex2 = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
674
692
  function quotePropertyName2(name) {
675
693
  return validIdentifierRegex2.test(name) ? name : `'${name}'`;
676
694
  }
695
+ function toPascalCase2(name) {
696
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[^a-zA-Z0-9]/g, " ").split(" ").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
697
+ }
698
+ function schemaExportNameToOutputAlias(name) {
699
+ return `${toPascalCase2(name)}Output`;
700
+ }
701
+ function registerOutputSchemaName(schemaName, options) {
702
+ options?.outputSchemaNames?.add(schemaName);
703
+ return schemaExportNameToOutputAlias(schemaName);
704
+ }
705
+ function getRegisteredOutputAlias(schema, options) {
706
+ if (!schema || typeof schema !== "object") return void 0;
707
+ if (schema["type"] === "string" && typeof schema["format"] === "string") {
708
+ const customSchemaName = getSchemaExportedVariableNameForStringFormat(
709
+ schema["format"]
710
+ );
711
+ if (customSchemaName) {
712
+ return registerOutputSchemaName(customSchemaName, options);
713
+ }
714
+ }
715
+ if (schema["type"] === "number" || schema["type"] === "integer" || schema["type"] === "boolean") {
716
+ const customSchemaName = getSchemaExportedVariableNameForPrimitiveType(
717
+ schema["type"]
718
+ );
719
+ if (customSchemaName) {
720
+ return registerOutputSchemaName(customSchemaName, options);
721
+ }
722
+ }
723
+ return void 0;
724
+ }
725
+ function schemaToTypeString(schema, options) {
726
+ if (!schema || typeof schema !== "object") return "unknown";
727
+ if (schema["$ref"] && typeof schema["$ref"] === "string") {
728
+ const match = schema["$ref"].match(
729
+ /#\/components\/schemas\/(.+)/
730
+ );
731
+ let result2 = "unknown";
732
+ if (match && match[1]) {
733
+ result2 = decodeURIComponent(match[1]);
734
+ }
735
+ if (schema["nullable"] === true) {
736
+ result2 = `(${result2} | null)`;
737
+ }
738
+ return result2;
739
+ }
740
+ let result = "unknown";
741
+ if ("oneOf" in schema && Array.isArray(schema["oneOf"])) {
742
+ const unionMembers = schema["oneOf"].map(
743
+ (s) => schemaToTypeString(s, options)
744
+ );
745
+ result = unionMembers.length > 1 ? `(${unionMembers.join(" | ")})` : unionMembers[0] ?? "unknown";
746
+ } else if ("allOf" in schema && Array.isArray(schema["allOf"])) {
747
+ const intersectionMembers = schema["allOf"].map(
748
+ (s) => schemaToTypeString(s, options)
749
+ );
750
+ result = intersectionMembers.length > 1 ? `(${intersectionMembers.join(" & ")})` : intersectionMembers[0] ?? "unknown";
751
+ } else if ("anyOf" in schema && Array.isArray(schema["anyOf"])) {
752
+ const unionMembers = schema["anyOf"].map(
753
+ (s) => schemaToTypeString(s, options)
754
+ );
755
+ result = unionMembers.length > 1 ? `(${unionMembers.join(" | ")})` : unionMembers[0] ?? "unknown";
756
+ } else {
757
+ switch (schema["type"]) {
758
+ case "string": {
759
+ const registeredAlias = getRegisteredOutputAlias(schema, options);
760
+ if (registeredAlias) {
761
+ result = registeredAlias;
762
+ } else if (schema["enum"] && Array.isArray(schema["enum"])) {
763
+ result = schema["enum"].map((v) => JSON.stringify(v)).join(" | ");
764
+ } else {
765
+ result = "string";
766
+ }
767
+ break;
768
+ }
769
+ case "number":
770
+ case "integer": {
771
+ const registeredAlias = getRegisteredOutputAlias(schema, options);
772
+ if (registeredAlias) {
773
+ result = registeredAlias;
774
+ } else if (schema["enum"] && Array.isArray(schema["enum"])) {
775
+ result = schema["enum"].map((v) => String(v)).join(" | ");
776
+ } else {
777
+ result = "number";
778
+ }
779
+ break;
780
+ }
781
+ case "boolean":
782
+ result = getRegisteredOutputAlias(schema, options) ?? "boolean";
783
+ break;
784
+ case "null":
785
+ result = "null";
786
+ break;
787
+ case "array":
788
+ if (schema["items"]) {
789
+ const itemType = schemaToTypeString(schema["items"], options);
790
+ result = `Array<${itemType}>`;
791
+ } else {
792
+ result = "unknown[]";
793
+ }
794
+ break;
795
+ case "object":
796
+ result = objectSchemaToTypeString(schema, options);
797
+ break;
798
+ default:
799
+ if (schema["properties"]) {
800
+ result = objectSchemaToTypeString(schema, options);
801
+ } else if (schema["enum"] && Array.isArray(schema["enum"])) {
802
+ result = schema["enum"].map((v) => JSON.stringify(v)).join(" | ");
803
+ } else {
804
+ result = "unknown";
805
+ }
806
+ break;
807
+ }
808
+ }
809
+ if (schema["nullable"] === true) {
810
+ result = `(${result} | null)`;
811
+ }
812
+ return result;
813
+ }
814
+ function objectSchemaToTypeString(schema, options) {
815
+ const properties = schema["properties"];
816
+ const required = new Set(schema["required"] ?? []);
817
+ const additionalProperties = schema["additionalProperties"];
818
+ if (!properties && !additionalProperties) {
819
+ return "Record<string, unknown>";
820
+ }
821
+ const propertyStrings = [];
822
+ if (properties) {
823
+ for (const [propName, propSchema] of Object.entries(properties)) {
824
+ const isRequired = required.has(propName);
825
+ const propType = schemaToTypeString(propSchema, options);
826
+ const quotedName = quotePropertyName2(propName);
827
+ propertyStrings.push(
828
+ `${quotedName}${isRequired ? "" : "?"}: ${propType}`
829
+ );
830
+ }
831
+ }
832
+ if (additionalProperties === true) {
833
+ propertyStrings.push("[key: string]: unknown");
834
+ } else if (typeof additionalProperties === "object" && additionalProperties !== null) {
835
+ const additionalType = schemaToTypeString(additionalProperties, options);
836
+ propertyStrings.push(`[key: string]: ${additionalType}`);
837
+ }
838
+ return `{ ${propertyStrings.join("; ")} }`;
839
+ }
840
+ function generateInterface(name, schema, options) {
841
+ const properties = schema["properties"];
842
+ const required = new Set(schema["required"] ?? []);
843
+ if (schema["type"] !== "object" && !properties) {
844
+ return `export type ${name} = ${schemaToTypeString(schema, options)};`;
845
+ }
846
+ const lines = [];
847
+ lines.push(`export interface ${name} {`);
848
+ if (properties) {
849
+ for (const [propName, propSchema] of Object.entries(properties)) {
850
+ const isRequired = required.has(propName);
851
+ const propType = schemaToTypeString(propSchema, options);
852
+ const quotedName = quotePropertyName2(propName);
853
+ lines.push(` ${quotedName}${isRequired ? "" : "?"}: ${propType};`);
854
+ }
855
+ }
856
+ const additionalProperties = schema["additionalProperties"];
857
+ if (additionalProperties === true) {
858
+ lines.push(" [key: string]: unknown;");
859
+ } else if (typeof additionalProperties === "object" && additionalProperties !== null) {
860
+ const additionalType = schemaToTypeString(additionalProperties, options);
861
+ lines.push(` [key: string]: ${additionalType};`);
862
+ }
863
+ lines.push("}");
864
+ return lines.join("\n");
865
+ }
866
+
867
+ // src/to-typescript.ts
868
+ var validIdentifierRegex3 = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
869
+ function quotePropertyName3(name) {
870
+ return validIdentifierRegex3.test(name) ? name : `'${name}'`;
871
+ }
677
872
  function generateRouteSchemaName2(path, method, suffix) {
678
873
  const pathParts = path.split("/").filter((p) => p).map((p) => {
679
874
  if (p.startsWith("{") && p.endsWith("}")) {
@@ -715,7 +910,7 @@ function generateRouteSchemas(routes, convertSchema, registry) {
715
910
  const properties = [];
716
911
  for (const param of pathParams) {
717
912
  const zodExpr = convertSchema(param.schema);
718
- properties.push(`${quotePropertyName2(param.name)}: ${zodExpr}`);
913
+ properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
719
914
  }
720
915
  declarations.push(
721
916
  `export const ${names.paramsSchemaName} = z.object({ ${properties.join(", ")} });`
@@ -751,7 +946,7 @@ function generateRouteSchemas(routes, convertSchema, registry) {
751
946
  if (!param.required) {
752
947
  zodExpr += ".optional()";
753
948
  }
754
- properties.push(`${quotePropertyName2(param.name)}: ${zodExpr}`);
949
+ properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
755
950
  }
756
951
  declarations.push(
757
952
  `export const ${names.querySchemaName} = z.object({ ${properties.join(", ")} });`
@@ -787,7 +982,7 @@ function generateRouteSchemas(routes, convertSchema, registry) {
787
982
  if (!param.required) {
788
983
  zodExpr += ".optional()";
789
984
  }
790
- properties.push(`${quotePropertyName2(param.name)}: ${zodExpr}`);
985
+ properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
791
986
  }
792
987
  declarations.push(
793
988
  `export const ${names.headersSchemaName} = z.object({ ${properties.join(", ")} });`
@@ -982,21 +1177,42 @@ var openApiToZodTsCode = (openapi, customImportLines, options) => {
982
1177
  lines.push("import { z } from 'zod';");
983
1178
  lines.push(...customImportLines ?? []);
984
1179
  lines.push("");
1180
+ lines.push("// Type assertion helper - verifies interface matches schema at compile time");
1181
+ lines.push("type _AssertEqual<T, U> = [T] extends [U] ? ([U] extends [T] ? true : never) : never;");
1182
+ lines.push("");
985
1183
  const registry = createSchemaRegistry();
986
1184
  const sortedSchemaNames = topologicalSortSchemas(schemas);
1185
+ const typeAssertions = [];
1186
+ const outputSchemaNames = /* @__PURE__ */ new Set();
1187
+ const schemaBlocks = [];
987
1188
  for (const name of sortedSchemaNames) {
988
1189
  const schema = schemas[name];
989
1190
  if (schema) {
990
1191
  const zodExpr = convertSchemaToZodString(schema);
991
1192
  const schemaName = `${name}Schema`;
992
1193
  const typeName = name;
993
- lines.push(`export const ${schemaName} = ${zodExpr};`);
994
- lines.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
995
- lines.push("");
1194
+ schemaBlocks.push(generateInterface(typeName, schema, { outputSchemaNames }));
1195
+ schemaBlocks.push(`export const ${schemaName}: z.ZodType<${typeName}> = ${zodExpr};`);
1196
+ schemaBlocks.push("");
1197
+ typeAssertions.push(`type _Assert${typeName} = _AssertEqual<${typeName}, z.infer<typeof ${schemaName}>>;`);
996
1198
  const fingerprint = getSchemaFingerprint(schema);
997
1199
  preRegisterSchema(registry, schemaName, fingerprint);
998
1200
  }
999
1201
  }
1202
+ if (outputSchemaNames.size > 0) {
1203
+ lines.push("// Zod output aliases for registered schemas");
1204
+ for (const schemaName of outputSchemaNames) {
1205
+ const aliasName = schemaExportNameToOutputAlias(schemaName);
1206
+ lines.push(`type ${aliasName} = z.output<typeof ${schemaName}>;`);
1207
+ }
1208
+ lines.push("");
1209
+ }
1210
+ lines.push(...schemaBlocks);
1211
+ if (typeAssertions.length > 0) {
1212
+ lines.push("// Compile-time type assertions - ensure interfaces match schemas");
1213
+ lines.push(typeAssertions.join("\n"));
1214
+ lines.push("");
1215
+ }
1000
1216
  if (options?.includeRoutes) {
1001
1217
  const routes = parseOpenApiPaths(openapi);
1002
1218
  if (routes.length > 0) {
@@ -1034,11 +1250,13 @@ export {
1034
1250
  SUPPORTED_STRING_FORMATS,
1035
1251
  clearZodSchemaToOpenApiSchemaRegistry,
1036
1252
  convertSchemaToZodString,
1253
+ generateInterface,
1037
1254
  generateRouteSchemaNames,
1038
1255
  getSchemaExportedVariableNameForStringFormat,
1039
1256
  openApiToZodTsCode,
1040
1257
  parseOpenApiPaths,
1041
1258
  registerZodSchemaToOpenApiSchema,
1042
- schemaRegistry
1259
+ schemaRegistry,
1260
+ schemaToTypeString
1043
1261
  };
1044
1262
  //# sourceMappingURL=index.js.map