@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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @alt-stack/zod-openapi@1.1.3 build /home/runner/work/alt-stack/alt-stack/packages/zod-openapi
2
+ > @alt-stack/zod-openapi@1.3.0 build /home/runner/work/alt-stack/alt-stack/packages/zod-openapi
3
3
  > tsup
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -10,13 +10,13 @@
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
12
  CJS Build start
13
- ESM dist/index.js 32.90 KB
14
- ESM dist/index.js.map 72.42 KB
15
- ESM ⚡️ Build success in 47ms
16
- CJS dist/index.cjs 34.44 KB
17
- CJS dist/index.cjs.map 73.28 KB
18
- CJS ⚡️ Build success in 47ms
13
+ CJS dist/index.cjs 42.75 KB
14
+ CJS dist/index.cjs.map 90.45 KB
15
+ CJS ⚡️ Build success in 33ms
16
+ ESM dist/index.js 41.13 KB
17
+ ESM dist/index.js.map 89.49 KB
18
+ ESM ⚡️ Build success in 33ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 1504ms
21
- DTS dist/index.d.ts 4.83 KB
22
- DTS dist/index.d.cts 4.83 KB
20
+ DTS ⚡️ Build success in 1429ms
21
+ DTS dist/index.d.ts 6.06 KB
22
+ DTS dist/index.d.cts 6.06 KB
package/dist/index.cjs CHANGED
@@ -23,12 +23,14 @@ __export(index_exports, {
23
23
  SUPPORTED_STRING_FORMATS: () => SUPPORTED_STRING_FORMATS,
24
24
  clearZodSchemaToOpenApiSchemaRegistry: () => clearZodSchemaToOpenApiSchemaRegistry,
25
25
  convertSchemaToZodString: () => convertSchemaToZodString,
26
+ generateInterface: () => generateInterface,
26
27
  generateRouteSchemaNames: () => generateRouteSchemaNames,
27
28
  getSchemaExportedVariableNameForStringFormat: () => getSchemaExportedVariableNameForStringFormat,
28
29
  openApiToZodTsCode: () => openApiToZodTsCode,
29
30
  parseOpenApiPaths: () => parseOpenApiPaths,
30
31
  registerZodSchemaToOpenApiSchema: () => registerZodSchemaToOpenApiSchema,
31
- schemaRegistry: () => schemaRegistry
32
+ schemaRegistry: () => schemaRegistry,
33
+ schemaToTypeString: () => schemaToTypeString
32
34
  });
33
35
  module.exports = __toCommonJS(index_exports);
34
36
 
@@ -265,6 +267,9 @@ function isStringRegistration(reg) {
265
267
  function isStringsRegistration(reg) {
266
268
  return reg.type === "string" && "formats" in reg;
267
269
  }
270
+ function isSupportedStringFormat(format) {
271
+ return Object.prototype.hasOwnProperty.call(SUPPORTED_STRING_FORMATS_MAP, format);
272
+ }
268
273
  function getTypeFormatPairs(reg) {
269
274
  if (isStringRegistration(reg)) {
270
275
  return [{ type: "string", format: reg.format }];
@@ -315,6 +320,7 @@ var ZodSchemaRegistry = class {
315
320
  * Reverse-lookup helper: given a string format, return the registered schema's exported variable name
316
321
  */
317
322
  getSchemaExportedVariableNameForStringFormat(format) {
323
+ if (!isSupportedStringFormat(format)) return void 0;
318
324
  for (const registration of this.map.values()) {
319
325
  if (registration.type !== "string") continue;
320
326
  if (isStringRegistration(registration) && registration.format === format) {
@@ -326,6 +332,17 @@ var ZodSchemaRegistry = class {
326
332
  }
327
333
  return void 0;
328
334
  }
335
+ /**
336
+ * Reverse-lookup helper: given a primitive type, return the registered schema's exported variable name
337
+ */
338
+ getSchemaExportedVariableNameForPrimitiveType(type) {
339
+ for (const registration of this.map.values()) {
340
+ if (registration.type === type) {
341
+ return registration.schemaExportedVariableName;
342
+ }
343
+ }
344
+ return void 0;
345
+ }
329
346
  };
330
347
  var schemaRegistry = new ZodSchemaRegistry();
331
348
  function registerZodSchemaToOpenApiSchema(schema, openApiSchema) {
@@ -334,6 +351,9 @@ function registerZodSchemaToOpenApiSchema(schema, openApiSchema) {
334
351
  function getSchemaExportedVariableNameForStringFormat(format) {
335
352
  return schemaRegistry.getSchemaExportedVariableNameForStringFormat(format);
336
353
  }
354
+ function getSchemaExportedVariableNameForPrimitiveType(type) {
355
+ return schemaRegistry.getSchemaExportedVariableNameForPrimitiveType(type);
356
+ }
337
357
  function clearZodSchemaToOpenApiSchemaRegistry() {
338
358
  schemaRegistry.clear();
339
359
  }
@@ -703,11 +723,188 @@ function generateRouteSchemaNames(route) {
703
723
  return result;
704
724
  }
705
725
 
706
- // src/to-typescript.ts
726
+ // src/interface-generator.ts
707
727
  var validIdentifierRegex2 = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
708
728
  function quotePropertyName2(name) {
709
729
  return validIdentifierRegex2.test(name) ? name : `'${name}'`;
710
730
  }
731
+ function toPascalCase2(name) {
732
+ 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("");
733
+ }
734
+ function schemaExportNameToOutputAlias(name) {
735
+ return `${toPascalCase2(name)}Output`;
736
+ }
737
+ function registerOutputSchemaName(schemaName, options) {
738
+ options?.outputSchemaNames?.add(schemaName);
739
+ return schemaExportNameToOutputAlias(schemaName);
740
+ }
741
+ function getRegisteredOutputAlias(schema, options) {
742
+ if (!schema || typeof schema !== "object") return void 0;
743
+ if (schema["type"] === "string" && typeof schema["format"] === "string") {
744
+ const customSchemaName = getSchemaExportedVariableNameForStringFormat(
745
+ schema["format"]
746
+ );
747
+ if (customSchemaName) {
748
+ return registerOutputSchemaName(customSchemaName, options);
749
+ }
750
+ }
751
+ if (schema["type"] === "number" || schema["type"] === "integer" || schema["type"] === "boolean") {
752
+ const customSchemaName = getSchemaExportedVariableNameForPrimitiveType(
753
+ schema["type"]
754
+ );
755
+ if (customSchemaName) {
756
+ return registerOutputSchemaName(customSchemaName, options);
757
+ }
758
+ }
759
+ return void 0;
760
+ }
761
+ function schemaToTypeString(schema, options) {
762
+ if (!schema || typeof schema !== "object") return "unknown";
763
+ if (schema["$ref"] && typeof schema["$ref"] === "string") {
764
+ const match = schema["$ref"].match(
765
+ /#\/components\/schemas\/(.+)/
766
+ );
767
+ let result2 = "unknown";
768
+ if (match && match[1]) {
769
+ result2 = decodeURIComponent(match[1]);
770
+ }
771
+ if (schema["nullable"] === true) {
772
+ result2 = `(${result2} | null)`;
773
+ }
774
+ return result2;
775
+ }
776
+ let result = "unknown";
777
+ if ("oneOf" in schema && Array.isArray(schema["oneOf"])) {
778
+ const unionMembers = schema["oneOf"].map(
779
+ (s) => schemaToTypeString(s, options)
780
+ );
781
+ result = unionMembers.length > 1 ? `(${unionMembers.join(" | ")})` : unionMembers[0] ?? "unknown";
782
+ } else if ("allOf" in schema && Array.isArray(schema["allOf"])) {
783
+ const intersectionMembers = schema["allOf"].map(
784
+ (s) => schemaToTypeString(s, options)
785
+ );
786
+ result = intersectionMembers.length > 1 ? `(${intersectionMembers.join(" & ")})` : intersectionMembers[0] ?? "unknown";
787
+ } else if ("anyOf" in schema && Array.isArray(schema["anyOf"])) {
788
+ const unionMembers = schema["anyOf"].map(
789
+ (s) => schemaToTypeString(s, options)
790
+ );
791
+ result = unionMembers.length > 1 ? `(${unionMembers.join(" | ")})` : unionMembers[0] ?? "unknown";
792
+ } else {
793
+ switch (schema["type"]) {
794
+ case "string": {
795
+ const registeredAlias = getRegisteredOutputAlias(schema, options);
796
+ if (registeredAlias) {
797
+ result = registeredAlias;
798
+ } else if (schema["enum"] && Array.isArray(schema["enum"])) {
799
+ result = schema["enum"].map((v) => JSON.stringify(v)).join(" | ");
800
+ } else {
801
+ result = "string";
802
+ }
803
+ break;
804
+ }
805
+ case "number":
806
+ case "integer": {
807
+ const registeredAlias = getRegisteredOutputAlias(schema, options);
808
+ if (registeredAlias) {
809
+ result = registeredAlias;
810
+ } else if (schema["enum"] && Array.isArray(schema["enum"])) {
811
+ result = schema["enum"].map((v) => String(v)).join(" | ");
812
+ } else {
813
+ result = "number";
814
+ }
815
+ break;
816
+ }
817
+ case "boolean":
818
+ result = getRegisteredOutputAlias(schema, options) ?? "boolean";
819
+ break;
820
+ case "null":
821
+ result = "null";
822
+ break;
823
+ case "array":
824
+ if (schema["items"]) {
825
+ const itemType = schemaToTypeString(schema["items"], options);
826
+ result = `Array<${itemType}>`;
827
+ } else {
828
+ result = "unknown[]";
829
+ }
830
+ break;
831
+ case "object":
832
+ result = objectSchemaToTypeString(schema, options);
833
+ break;
834
+ default:
835
+ if (schema["properties"]) {
836
+ result = objectSchemaToTypeString(schema, options);
837
+ } else if (schema["enum"] && Array.isArray(schema["enum"])) {
838
+ result = schema["enum"].map((v) => JSON.stringify(v)).join(" | ");
839
+ } else {
840
+ result = "unknown";
841
+ }
842
+ break;
843
+ }
844
+ }
845
+ if (schema["nullable"] === true) {
846
+ result = `(${result} | null)`;
847
+ }
848
+ return result;
849
+ }
850
+ function objectSchemaToTypeString(schema, options) {
851
+ const properties = schema["properties"];
852
+ const required = new Set(schema["required"] ?? []);
853
+ const additionalProperties = schema["additionalProperties"];
854
+ if (!properties && !additionalProperties) {
855
+ return "Record<string, unknown>";
856
+ }
857
+ const propertyStrings = [];
858
+ if (properties) {
859
+ for (const [propName, propSchema] of Object.entries(properties)) {
860
+ const isRequired = required.has(propName);
861
+ const propType = schemaToTypeString(propSchema, options);
862
+ const quotedName = quotePropertyName2(propName);
863
+ propertyStrings.push(
864
+ `${quotedName}${isRequired ? "" : "?"}: ${propType}`
865
+ );
866
+ }
867
+ }
868
+ if (additionalProperties === true) {
869
+ propertyStrings.push("[key: string]: unknown");
870
+ } else if (typeof additionalProperties === "object" && additionalProperties !== null) {
871
+ const additionalType = schemaToTypeString(additionalProperties, options);
872
+ propertyStrings.push(`[key: string]: ${additionalType}`);
873
+ }
874
+ return `{ ${propertyStrings.join("; ")} }`;
875
+ }
876
+ function generateInterface(name, schema, options) {
877
+ const properties = schema["properties"];
878
+ const required = new Set(schema["required"] ?? []);
879
+ if (schema["type"] !== "object" && !properties) {
880
+ return `export type ${name} = ${schemaToTypeString(schema, options)};`;
881
+ }
882
+ const lines = [];
883
+ lines.push(`export interface ${name} {`);
884
+ if (properties) {
885
+ for (const [propName, propSchema] of Object.entries(properties)) {
886
+ const isRequired = required.has(propName);
887
+ const propType = schemaToTypeString(propSchema, options);
888
+ const quotedName = quotePropertyName2(propName);
889
+ lines.push(` ${quotedName}${isRequired ? "" : "?"}: ${propType};`);
890
+ }
891
+ }
892
+ const additionalProperties = schema["additionalProperties"];
893
+ if (additionalProperties === true) {
894
+ lines.push(" [key: string]: unknown;");
895
+ } else if (typeof additionalProperties === "object" && additionalProperties !== null) {
896
+ const additionalType = schemaToTypeString(additionalProperties, options);
897
+ lines.push(` [key: string]: ${additionalType};`);
898
+ }
899
+ lines.push("}");
900
+ return lines.join("\n");
901
+ }
902
+
903
+ // src/to-typescript.ts
904
+ var validIdentifierRegex3 = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
905
+ function quotePropertyName3(name) {
906
+ return validIdentifierRegex3.test(name) ? name : `'${name}'`;
907
+ }
711
908
  function generateRouteSchemaName2(path, method, suffix) {
712
909
  const pathParts = path.split("/").filter((p) => p).map((p) => {
713
910
  if (p.startsWith("{") && p.endsWith("}")) {
@@ -749,7 +946,7 @@ function generateRouteSchemas(routes, convertSchema, registry) {
749
946
  const properties = [];
750
947
  for (const param of pathParams) {
751
948
  const zodExpr = convertSchema(param.schema);
752
- properties.push(`${quotePropertyName2(param.name)}: ${zodExpr}`);
949
+ properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
753
950
  }
754
951
  declarations.push(
755
952
  `export const ${names.paramsSchemaName} = z.object({ ${properties.join(", ")} });`
@@ -785,7 +982,7 @@ function generateRouteSchemas(routes, convertSchema, registry) {
785
982
  if (!param.required) {
786
983
  zodExpr += ".optional()";
787
984
  }
788
- properties.push(`${quotePropertyName2(param.name)}: ${zodExpr}`);
985
+ properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
789
986
  }
790
987
  declarations.push(
791
988
  `export const ${names.querySchemaName} = z.object({ ${properties.join(", ")} });`
@@ -821,7 +1018,7 @@ function generateRouteSchemas(routes, convertSchema, registry) {
821
1018
  if (!param.required) {
822
1019
  zodExpr += ".optional()";
823
1020
  }
824
- properties.push(`${quotePropertyName2(param.name)}: ${zodExpr}`);
1021
+ properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
825
1022
  }
826
1023
  declarations.push(
827
1024
  `export const ${names.headersSchemaName} = z.object({ ${properties.join(", ")} });`
@@ -1016,21 +1213,42 @@ var openApiToZodTsCode = (openapi, customImportLines, options) => {
1016
1213
  lines.push("import { z } from 'zod';");
1017
1214
  lines.push(...customImportLines ?? []);
1018
1215
  lines.push("");
1216
+ lines.push("// Type assertion helper - verifies interface matches schema at compile time");
1217
+ lines.push("type _AssertEqual<T, U> = [T] extends [U] ? ([U] extends [T] ? true : never) : never;");
1218
+ lines.push("");
1019
1219
  const registry = createSchemaRegistry();
1020
1220
  const sortedSchemaNames = topologicalSortSchemas(schemas);
1221
+ const typeAssertions = [];
1222
+ const outputSchemaNames = /* @__PURE__ */ new Set();
1223
+ const schemaBlocks = [];
1021
1224
  for (const name of sortedSchemaNames) {
1022
1225
  const schema = schemas[name];
1023
1226
  if (schema) {
1024
1227
  const zodExpr = convertSchemaToZodString(schema);
1025
1228
  const schemaName = `${name}Schema`;
1026
1229
  const typeName = name;
1027
- lines.push(`export const ${schemaName} = ${zodExpr};`);
1028
- lines.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
1029
- lines.push("");
1230
+ schemaBlocks.push(generateInterface(typeName, schema, { outputSchemaNames }));
1231
+ schemaBlocks.push(`export const ${schemaName}: z.ZodType<${typeName}> = ${zodExpr};`);
1232
+ schemaBlocks.push("");
1233
+ typeAssertions.push(`type _Assert${typeName} = _AssertEqual<${typeName}, z.infer<typeof ${schemaName}>>;`);
1030
1234
  const fingerprint = getSchemaFingerprint(schema);
1031
1235
  preRegisterSchema(registry, schemaName, fingerprint);
1032
1236
  }
1033
1237
  }
1238
+ if (outputSchemaNames.size > 0) {
1239
+ lines.push("// Zod output aliases for registered schemas");
1240
+ for (const schemaName of outputSchemaNames) {
1241
+ const aliasName = schemaExportNameToOutputAlias(schemaName);
1242
+ lines.push(`type ${aliasName} = z.output<typeof ${schemaName}>;`);
1243
+ }
1244
+ lines.push("");
1245
+ }
1246
+ lines.push(...schemaBlocks);
1247
+ if (typeAssertions.length > 0) {
1248
+ lines.push("// Compile-time type assertions - ensure interfaces match schemas");
1249
+ lines.push(typeAssertions.join("\n"));
1250
+ lines.push("");
1251
+ }
1034
1252
  if (options?.includeRoutes) {
1035
1253
  const routes = parseOpenApiPaths(openapi);
1036
1254
  if (routes.length > 0) {
@@ -1069,11 +1287,13 @@ var openApiToZodTsCode = (openapi, customImportLines, options) => {
1069
1287
  SUPPORTED_STRING_FORMATS,
1070
1288
  clearZodSchemaToOpenApiSchemaRegistry,
1071
1289
  convertSchemaToZodString,
1290
+ generateInterface,
1072
1291
  generateRouteSchemaNames,
1073
1292
  getSchemaExportedVariableNameForStringFormat,
1074
1293
  openApiToZodTsCode,
1075
1294
  parseOpenApiPaths,
1076
1295
  registerZodSchemaToOpenApiSchema,
1077
- schemaRegistry
1296
+ schemaRegistry,
1297
+ schemaToTypeString
1078
1298
  });
1079
1299
  //# sourceMappingURL=index.cjs.map