@alt-stack/zod-openapi 1.1.3 → 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/.turbo/turbo-build.log +10 -10
- package/dist/index.cjs +161 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +158 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/interface-generator.spec.ts +595 -0
- package/src/interface-generator.ts +211 -0
- package/src/to-typescript.ts +25 -2
- package/src/to-zod.spec.ts +10 -6
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @alt-stack/zod-openapi@1.
|
|
2
|
+
> @alt-stack/zod-openapi@1.2.0 build /home/runner/work/alt-stack/alt-stack/packages/zod-openapi
|
|
3
3
|
> tsup
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
14
|
-
[32mESM[39m [1mdist/index.js.map [22m[
|
|
15
|
-
[32mESM[39m ⚡️ Build success in
|
|
16
|
-
[32mCJS[39m [1mdist/index.cjs [22m[
|
|
17
|
-
[32mCJS[39m [1mdist/index.cjs.map [22m[
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in
|
|
13
|
+
[32mESM[39m [1mdist/index.js [22m[32m38.35 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m84.13 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 32ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m39.98 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.cjs.map [22m[32m85.09 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 33ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.cts [22m[
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 1497ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m5.68 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m5.68 KB[39m
|
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
|
|
|
@@ -703,11 +705,150 @@ function generateRouteSchemaNames(route) {
|
|
|
703
705
|
return result;
|
|
704
706
|
}
|
|
705
707
|
|
|
706
|
-
// src/
|
|
708
|
+
// src/interface-generator.ts
|
|
707
709
|
var validIdentifierRegex2 = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
708
710
|
function quotePropertyName2(name) {
|
|
709
711
|
return validIdentifierRegex2.test(name) ? name : `'${name}'`;
|
|
710
712
|
}
|
|
713
|
+
function schemaToTypeString(schema) {
|
|
714
|
+
if (!schema || typeof schema !== "object") return "unknown";
|
|
715
|
+
if (schema["$ref"] && typeof schema["$ref"] === "string") {
|
|
716
|
+
const match = schema["$ref"].match(
|
|
717
|
+
/#\/components\/schemas\/(.+)/
|
|
718
|
+
);
|
|
719
|
+
let result2 = "unknown";
|
|
720
|
+
if (match && match[1]) {
|
|
721
|
+
result2 = decodeURIComponent(match[1]);
|
|
722
|
+
}
|
|
723
|
+
if (schema["nullable"] === true) {
|
|
724
|
+
result2 = `(${result2} | null)`;
|
|
725
|
+
}
|
|
726
|
+
return result2;
|
|
727
|
+
}
|
|
728
|
+
let result = "unknown";
|
|
729
|
+
if ("oneOf" in schema && Array.isArray(schema["oneOf"])) {
|
|
730
|
+
const unionMembers = schema["oneOf"].map(
|
|
731
|
+
(s) => schemaToTypeString(s)
|
|
732
|
+
);
|
|
733
|
+
result = unionMembers.length > 1 ? `(${unionMembers.join(" | ")})` : unionMembers[0] ?? "unknown";
|
|
734
|
+
} else if ("allOf" in schema && Array.isArray(schema["allOf"])) {
|
|
735
|
+
const intersectionMembers = schema["allOf"].map(
|
|
736
|
+
(s) => schemaToTypeString(s)
|
|
737
|
+
);
|
|
738
|
+
result = intersectionMembers.length > 1 ? `(${intersectionMembers.join(" & ")})` : intersectionMembers[0] ?? "unknown";
|
|
739
|
+
} else if ("anyOf" in schema && Array.isArray(schema["anyOf"])) {
|
|
740
|
+
const unionMembers = schema["anyOf"].map(
|
|
741
|
+
(s) => schemaToTypeString(s)
|
|
742
|
+
);
|
|
743
|
+
result = unionMembers.length > 1 ? `(${unionMembers.join(" | ")})` : unionMembers[0] ?? "unknown";
|
|
744
|
+
} else {
|
|
745
|
+
switch (schema["type"]) {
|
|
746
|
+
case "string":
|
|
747
|
+
if (schema["enum"] && Array.isArray(schema["enum"])) {
|
|
748
|
+
result = schema["enum"].map((v) => JSON.stringify(v)).join(" | ");
|
|
749
|
+
} else {
|
|
750
|
+
result = "string";
|
|
751
|
+
}
|
|
752
|
+
break;
|
|
753
|
+
case "number":
|
|
754
|
+
case "integer":
|
|
755
|
+
if (schema["enum"] && Array.isArray(schema["enum"])) {
|
|
756
|
+
result = schema["enum"].map((v) => String(v)).join(" | ");
|
|
757
|
+
} else {
|
|
758
|
+
result = "number";
|
|
759
|
+
}
|
|
760
|
+
break;
|
|
761
|
+
case "boolean":
|
|
762
|
+
result = "boolean";
|
|
763
|
+
break;
|
|
764
|
+
case "null":
|
|
765
|
+
result = "null";
|
|
766
|
+
break;
|
|
767
|
+
case "array":
|
|
768
|
+
if (schema["items"]) {
|
|
769
|
+
const itemType = schemaToTypeString(schema["items"]);
|
|
770
|
+
result = `Array<${itemType}>`;
|
|
771
|
+
} else {
|
|
772
|
+
result = "unknown[]";
|
|
773
|
+
}
|
|
774
|
+
break;
|
|
775
|
+
case "object":
|
|
776
|
+
result = objectSchemaToTypeString(schema);
|
|
777
|
+
break;
|
|
778
|
+
default:
|
|
779
|
+
if (schema["properties"]) {
|
|
780
|
+
result = objectSchemaToTypeString(schema);
|
|
781
|
+
} else if (schema["enum"] && Array.isArray(schema["enum"])) {
|
|
782
|
+
result = schema["enum"].map((v) => JSON.stringify(v)).join(" | ");
|
|
783
|
+
} else {
|
|
784
|
+
result = "unknown";
|
|
785
|
+
}
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
if (schema["nullable"] === true) {
|
|
790
|
+
result = `(${result} | null)`;
|
|
791
|
+
}
|
|
792
|
+
return result;
|
|
793
|
+
}
|
|
794
|
+
function objectSchemaToTypeString(schema) {
|
|
795
|
+
const properties = schema["properties"];
|
|
796
|
+
const required = new Set(schema["required"] ?? []);
|
|
797
|
+
const additionalProperties = schema["additionalProperties"];
|
|
798
|
+
if (!properties && !additionalProperties) {
|
|
799
|
+
return "Record<string, unknown>";
|
|
800
|
+
}
|
|
801
|
+
const propertyStrings = [];
|
|
802
|
+
if (properties) {
|
|
803
|
+
for (const [propName, propSchema] of Object.entries(properties)) {
|
|
804
|
+
const isRequired = required.has(propName);
|
|
805
|
+
const propType = schemaToTypeString(propSchema);
|
|
806
|
+
const quotedName = quotePropertyName2(propName);
|
|
807
|
+
propertyStrings.push(
|
|
808
|
+
`${quotedName}${isRequired ? "" : "?"}: ${propType}`
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (additionalProperties === true) {
|
|
813
|
+
propertyStrings.push("[key: string]: unknown");
|
|
814
|
+
} else if (typeof additionalProperties === "object" && additionalProperties !== null) {
|
|
815
|
+
const additionalType = schemaToTypeString(additionalProperties);
|
|
816
|
+
propertyStrings.push(`[key: string]: ${additionalType}`);
|
|
817
|
+
}
|
|
818
|
+
return `{ ${propertyStrings.join("; ")} }`;
|
|
819
|
+
}
|
|
820
|
+
function generateInterface(name, schema) {
|
|
821
|
+
const properties = schema["properties"];
|
|
822
|
+
const required = new Set(schema["required"] ?? []);
|
|
823
|
+
if (schema["type"] !== "object" && !properties) {
|
|
824
|
+
return `export type ${name} = ${schemaToTypeString(schema)};`;
|
|
825
|
+
}
|
|
826
|
+
const lines = [];
|
|
827
|
+
lines.push(`export interface ${name} {`);
|
|
828
|
+
if (properties) {
|
|
829
|
+
for (const [propName, propSchema] of Object.entries(properties)) {
|
|
830
|
+
const isRequired = required.has(propName);
|
|
831
|
+
const propType = schemaToTypeString(propSchema);
|
|
832
|
+
const quotedName = quotePropertyName2(propName);
|
|
833
|
+
lines.push(` ${quotedName}${isRequired ? "" : "?"}: ${propType};`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
const additionalProperties = schema["additionalProperties"];
|
|
837
|
+
if (additionalProperties === true) {
|
|
838
|
+
lines.push(" [key: string]: unknown;");
|
|
839
|
+
} else if (typeof additionalProperties === "object" && additionalProperties !== null) {
|
|
840
|
+
const additionalType = schemaToTypeString(additionalProperties);
|
|
841
|
+
lines.push(` [key: string]: ${additionalType};`);
|
|
842
|
+
}
|
|
843
|
+
lines.push("}");
|
|
844
|
+
return lines.join("\n");
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/to-typescript.ts
|
|
848
|
+
var validIdentifierRegex3 = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
849
|
+
function quotePropertyName3(name) {
|
|
850
|
+
return validIdentifierRegex3.test(name) ? name : `'${name}'`;
|
|
851
|
+
}
|
|
711
852
|
function generateRouteSchemaName2(path, method, suffix) {
|
|
712
853
|
const pathParts = path.split("/").filter((p) => p).map((p) => {
|
|
713
854
|
if (p.startsWith("{") && p.endsWith("}")) {
|
|
@@ -749,7 +890,7 @@ function generateRouteSchemas(routes, convertSchema, registry) {
|
|
|
749
890
|
const properties = [];
|
|
750
891
|
for (const param of pathParams) {
|
|
751
892
|
const zodExpr = convertSchema(param.schema);
|
|
752
|
-
properties.push(`${
|
|
893
|
+
properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
|
|
753
894
|
}
|
|
754
895
|
declarations.push(
|
|
755
896
|
`export const ${names.paramsSchemaName} = z.object({ ${properties.join(", ")} });`
|
|
@@ -785,7 +926,7 @@ function generateRouteSchemas(routes, convertSchema, registry) {
|
|
|
785
926
|
if (!param.required) {
|
|
786
927
|
zodExpr += ".optional()";
|
|
787
928
|
}
|
|
788
|
-
properties.push(`${
|
|
929
|
+
properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
|
|
789
930
|
}
|
|
790
931
|
declarations.push(
|
|
791
932
|
`export const ${names.querySchemaName} = z.object({ ${properties.join(", ")} });`
|
|
@@ -821,7 +962,7 @@ function generateRouteSchemas(routes, convertSchema, registry) {
|
|
|
821
962
|
if (!param.required) {
|
|
822
963
|
zodExpr += ".optional()";
|
|
823
964
|
}
|
|
824
|
-
properties.push(`${
|
|
965
|
+
properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
|
|
825
966
|
}
|
|
826
967
|
declarations.push(
|
|
827
968
|
`export const ${names.headersSchemaName} = z.object({ ${properties.join(", ")} });`
|
|
@@ -1016,21 +1157,31 @@ var openApiToZodTsCode = (openapi, customImportLines, options) => {
|
|
|
1016
1157
|
lines.push("import { z } from 'zod';");
|
|
1017
1158
|
lines.push(...customImportLines ?? []);
|
|
1018
1159
|
lines.push("");
|
|
1160
|
+
lines.push("// Type assertion helper - verifies interface matches schema at compile time");
|
|
1161
|
+
lines.push("type _AssertEqual<T, U> = [T] extends [U] ? ([U] extends [T] ? true : never) : never;");
|
|
1162
|
+
lines.push("");
|
|
1019
1163
|
const registry = createSchemaRegistry();
|
|
1020
1164
|
const sortedSchemaNames = topologicalSortSchemas(schemas);
|
|
1165
|
+
const typeAssertions = [];
|
|
1021
1166
|
for (const name of sortedSchemaNames) {
|
|
1022
1167
|
const schema = schemas[name];
|
|
1023
1168
|
if (schema) {
|
|
1024
1169
|
const zodExpr = convertSchemaToZodString(schema);
|
|
1025
1170
|
const schemaName = `${name}Schema`;
|
|
1026
1171
|
const typeName = name;
|
|
1027
|
-
lines.push(
|
|
1028
|
-
lines.push(`export
|
|
1172
|
+
lines.push(generateInterface(typeName, schema));
|
|
1173
|
+
lines.push(`export const ${schemaName}: z.ZodType<${typeName}> = ${zodExpr};`);
|
|
1029
1174
|
lines.push("");
|
|
1175
|
+
typeAssertions.push(`type _Assert${typeName} = _AssertEqual<${typeName}, z.infer<typeof ${schemaName}>>;`);
|
|
1030
1176
|
const fingerprint = getSchemaFingerprint(schema);
|
|
1031
1177
|
preRegisterSchema(registry, schemaName, fingerprint);
|
|
1032
1178
|
}
|
|
1033
1179
|
}
|
|
1180
|
+
if (typeAssertions.length > 0) {
|
|
1181
|
+
lines.push("// Compile-time type assertions - ensure interfaces match schemas");
|
|
1182
|
+
lines.push(typeAssertions.join("\n"));
|
|
1183
|
+
lines.push("");
|
|
1184
|
+
}
|
|
1034
1185
|
if (options?.includeRoutes) {
|
|
1035
1186
|
const routes = parseOpenApiPaths(openapi);
|
|
1036
1187
|
if (routes.length > 0) {
|
|
@@ -1069,11 +1220,13 @@ var openApiToZodTsCode = (openapi, customImportLines, options) => {
|
|
|
1069
1220
|
SUPPORTED_STRING_FORMATS,
|
|
1070
1221
|
clearZodSchemaToOpenApiSchemaRegistry,
|
|
1071
1222
|
convertSchemaToZodString,
|
|
1223
|
+
generateInterface,
|
|
1072
1224
|
generateRouteSchemaNames,
|
|
1073
1225
|
getSchemaExportedVariableNameForStringFormat,
|
|
1074
1226
|
openApiToZodTsCode,
|
|
1075
1227
|
parseOpenApiPaths,
|
|
1076
1228
|
registerZodSchemaToOpenApiSchema,
|
|
1077
|
-
schemaRegistry
|
|
1229
|
+
schemaRegistry,
|
|
1230
|
+
schemaToTypeString
|
|
1078
1231
|
});
|
|
1079
1232
|
//# sourceMappingURL=index.cjs.map
|