zod-to-x 1.6.1-dev.0 → 1.7.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/README.md CHANGED
@@ -26,30 +26,36 @@
26
26
  <img src="https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript">
27
27
  <img src="https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white" alt="Python">
28
28
  <img src="https://img.shields.io/badge/C++-00599C?style=for-the-badge&logo=cplusplus&logoColor=white" alt="C++">
29
+ <img src="https://img.shields.io/badge/Go-00ADD8?style=for-the-badge&logo=go&logoColor=white" alt="Go">
29
30
  <img src="https://img.shields.io/badge/Protobuf-4285F4?style=for-the-badge&logo=google&logoColor=white" alt="Protobuf">
30
31
  </p>
31
32
 
32
33
 
33
34
 
34
35
  ## Table of contents
35
- - [Why use `@zod-to-x`](#why-use-zod-to-x)
36
+ - [Table of contents](#table-of-contents)
37
+ - [Why Use `@zod-to-x`?](#why-use-zod-to-x)
36
38
  - [Installation](#installation)
39
+ - [1) Install `@zod-to-x` and `@zod(*)` dependency.](#1-install-zod-to-x-and-zod-dependency)
40
+ - [2) Extend Zod using the `extendZod` method after the first `@zod` import:](#2-extend-zod-using-the-extendzod-method-after-the-first-zod-import)
37
41
  - [Quick start](#quick-start)
38
42
  - [Intersections and Unions](#intersections-and-unions)
39
43
  - [Expected outputs](#expected-outputs)
40
44
  - [Tips for discriminated unions](#tips-for-discriminated-unions)
41
45
  - [Layered modeling](#layered-modeling)
42
46
  - [Usage example](#usage-example)
43
- - [Custom layers](#custom-layers)
47
+ - [Custom Layers](#custom-layers)
44
48
  - [Generic types](#generic-types)
45
49
  - [Currently supported output languages](#currently-supported-output-languages)
46
- - [Typescript](#1-typescript)
47
- - [Python](#2-python) <sup>*(new)*</sup>
48
- - [C++](#3-c)
50
+ - [0) ASTNode](#0-astnode)
51
+ - [1) Typescript](#1-typescript)
52
+ - [2) Python](#2-python)
53
+ - [3) C++](#3-c)
54
+ - [4) Go](#4-go)
49
55
  - [Additional utils](#additional-utils)
50
- - [JSON Schema definitions](#1-zod2jsonschemadefinitions)
51
- - [Protobuf V3 generation](#2-zod2protov3)
52
- - [Mapping of supported Zod Types by Language](#mapping-of-supported-zod-types-by-langauge)
56
+ - [1) `zod2JsonSchemaDefinitions`](#1-zod2jsonschemadefinitions)
57
+ - [2) `zod2ProtoV3`](#2-zod2protov3)
58
+ - [Mapping of supported Zod Types by Langauge](#mapping-of-supported-zod-types-by-langauge)
53
59
  - [Considerations](#considerations)
54
60
 
55
61
 
@@ -61,7 +67,7 @@ Managing data consistency across multiple layers and languages is a common chall
61
67
  Define your data structures in one place using the powerful [`@zod`](https://github.com/colinhacks/zod) library. This eliminates redundancy, reduces inconsistencies, and simplifies maintenance across your entire codebase, all while allowing you to continue leveraging any npm package in the [`@zod`](https://github.com/colinhacks/zod) ecosystem like [`@zod-to-json-schema`](https://github.com/StefanTerdell/zod-to-json-schema)
62
68
 
63
69
  2. **Multi-Language Compatibility**
64
- Generate data models for TypeScript, Python (Pydantic), C++, and Protobuf V3 (with languages like Golang on the roadmap). No more manually rewriting models for different platforms.
70
+ Generate data models for TypeScript, Python (Pydantic), C++, Go, and Protobuf V3. No more manually rewriting models for different platforms.
65
71
 
66
72
  3. **Enhanced Productivity**
67
73
  Automate the transpilation of data models to save time, reduce errors, and let your team focus on business logic instead of boilerplate code.
@@ -647,6 +653,14 @@ Common options:
647
653
  - **keepKeys**: Specifies whether property names should follow the C++ naming convention (false) or remain as originally defined (true). The default is `false`.
648
654
  - [Examples](https://github.com/rroumenov/zod-to-x/blob/main/test/test_zod2cpp)
649
655
 
656
+ ### 4) Go
657
+ Generates idiomatic Go structs with `encoding/json` struct tags. Requires Go 1.18+ for generic type support.
658
+ - Options:
659
+ - **packageName**: The Go package name for the generated file. Defaults to `"models"`.
660
+ - **keepKeys**: Specifies whether field names should remain as originally defined (true) or be converted to exported PascalCase with the original name used in the JSON tag (false). The default is `false`.
661
+ - **useJsonTags**: Whether to emit `json:"fieldName"` struct tags. Defaults to `true`.
662
+ - [Examples](https://github.com/rroumenov/zod-to-x/blob/main/test/test_zod2go)
663
+
650
664
 
651
665
 
652
666
  ## Additional utils
@@ -82,7 +82,7 @@ export declare class Zod2Ast {
82
82
  private _intersectAstNodes;
83
83
  /**
84
84
  * Merges multiple AST definitions into a single AST object containing combined properties.
85
- * - Equal properties mush have the same type and array dimension.
85
+ * - Equal properties must have the same type and array dimension.
86
86
  * - If a property is optional in one definition and required in another, it will be considered
87
87
  * optional in the merged object.
88
88
  * - If a property is nullable in one definition and non-nullable in another, it will be
@@ -160,19 +160,25 @@ class Zod2Ast {
160
160
  for (const schema of [left, right]) {
161
161
  const shape = schema._def.shape();
162
162
  for (const key in shape) {
163
+ const prevWasRequired = properties[key] !== undefined && !properties[key].isOptional;
163
164
  if (zod_helpers_1.ZodHelpers.isZodPromise(shape[key]) && zod_helpers_1.ZodHelpers.isZod2XGeneric(shape[key])) {
164
165
  properties[key] = new core_1.ASTGenericType(shape[key]._def.type.value);
165
166
  }
166
167
  else {
167
168
  properties[key] = this._zodToAST(shape[key]);
168
169
  }
170
+ if (prevWasRequired && properties[key].isOptional) {
171
+ // In intersection, if a property is required in one schema and optional in
172
+ // another, it should be considered required.
173
+ properties[key].isOptional = false;
174
+ }
169
175
  }
170
176
  }
171
177
  return { properties };
172
178
  }
173
179
  /**
174
180
  * Merges multiple AST definitions into a single AST object containing combined properties.
175
- * - Equal properties mush have the same type and array dimension.
181
+ * - Equal properties must have the same type and array dimension.
176
182
  * - If a property is optional in one definition and required in another, it will be considered
177
183
  * optional in the merged object.
178
184
  * - If a property is nullable in one definition and non-nullable in another, it will be
@@ -185,42 +191,49 @@ class Zod2Ast {
185
191
  _unionAstNodes(options) {
186
192
  let typeA, typeB;
187
193
  const data = options.map((i) => this.nodes.get(i.name));
188
- return {
189
- properties: data.reduce((acc, i, j) => {
190
- var _a, _b;
191
- for (const key in i.properties) {
192
- if (acc[key]) {
193
- acc[key] = structuredClone(acc[key]);
194
- typeA = acc[key].constructor.name;
195
- typeB = i.properties[key].constructor.name;
196
- if (typeA !== typeB) {
197
- this.warnings.push(`Merging properties with different types: ${typeA} ` +
198
- `(from ${(_a = data[j - 1]) === null || _a === void 0 ? void 0 : _a.name}) and ${typeB} ` +
199
- `(from ${i.name})`);
200
- }
201
- if (acc[key].arrayDimension !== i.properties[key].arrayDimension) {
202
- this.warnings.push(`Merging properties with different array dimensions: ` +
203
- `${acc[key].arrayDimension} (from ${(_b = data[j - 1]) === null || _b === void 0 ? void 0 : _b.name}) and ` +
204
- `${i.properties[key].arrayDimension} (from ${i.name})`);
205
- acc[key].arrayDimension = Math.max(acc[key].arrayDimension || 0, i.properties[key].arrayDimension || 0);
206
- }
207
- if (acc[key].isNullable !== i.properties[key].isNullable) {
208
- acc[key].isNullable = true;
209
- }
210
- if (acc[key].isOptional !== i.properties[key].isOptional) {
211
- acc[key].isOptional = true;
212
- }
213
- if (i.properties[key].description) {
214
- acc[key].description = i.properties[key].description;
215
- }
194
+ const properties = data.reduce((acc, i, j) => {
195
+ for (const key in i.properties) {
196
+ if (acc[key]) {
197
+ acc[key] = Object.assign(Object.create(Object.getPrototypeOf(acc[key])), acc[key]);
198
+ typeA = acc[key].constructor.name;
199
+ typeB = i.properties[key].constructor.name;
200
+ if (typeA !== typeB) {
201
+ this.warnings.push(`Merging properties with different types: ${typeA} ` +
202
+ `(from a previous variant) and ${typeB} ` +
203
+ `(from ${i.name})`);
204
+ }
205
+ if (acc[key].arrayDimension !== i.properties[key].arrayDimension) {
206
+ this.warnings.push(`Merging properties with different array dimensions: ` +
207
+ `${acc[key].arrayDimension} (from a previous variant) and ` +
208
+ `${i.properties[key].arrayDimension} (from ${i.name})`);
209
+ acc[key].arrayDimension = Math.max(acc[key].arrayDimension || 0, i.properties[key].arrayDimension || 0);
216
210
  }
217
- else {
218
- acc[key] = i.properties[key];
211
+ if (acc[key].isNullable !== i.properties[key].isNullable) {
212
+ acc[key].isNullable = true;
213
+ }
214
+ if (acc[key].isOptional !== i.properties[key].isOptional) {
215
+ acc[key].isOptional = true;
216
+ }
217
+ if (i.properties[key].description) {
218
+ acc[key].description = i.properties[key].description;
219
219
  }
220
220
  }
221
- return acc;
222
- }, {}),
223
- };
221
+ else {
222
+ acc[key] = i.properties[key]; // New property, just add it
223
+ }
224
+ }
225
+ return acc;
226
+ }, {});
227
+ for (const key in properties) {
228
+ if (!data.every((variant) => key in variant.properties)) {
229
+ // In Union, if a property is not present in all variants, it should be considered
230
+ // optional. Shallow-clone preserving prototype before mutating, to avoid corrupting
231
+ // the original cached AST node.
232
+ properties[key] = Object.assign(Object.create(Object.getPrototypeOf(properties[key])), properties[key]);
233
+ properties[key].isOptional = true;
234
+ }
235
+ }
236
+ return { properties };
224
237
  }
225
238
  /**
226
239
  * Retrieves the name and associated transpilerable file information for a given Zod schema.
@@ -99,7 +99,9 @@ function Layer(opt) {
99
99
  layer: opt,
100
100
  typeName: name,
101
101
  // Generics associated to parent, but related to current type.
102
- genericTypes: metadata.genericTypes,
102
+ genericTypes: metadata.isGenericChild === false
103
+ ? metadata.genericTypes
104
+ : undefined,
103
105
  isGenericChild: true,
104
106
  };
105
107
  }
@@ -44,5 +44,6 @@ export declare class Zod2XModel {
44
44
  transpile(target: Target<"Zod2Cpp17">, opt?: TargetOpt<"Zod2Cpp17">, astOpt?: AstOpt): string;
45
45
  transpile(target: Target<"Zod2Ts">, opt?: TargetOpt<"Zod2Ts">, astOpt?: AstOpt): string;
46
46
  transpile(target: Target<"Zod2Py">, opt?: TargetOpt<"Zod2Py">, astOpt?: AstOpt): string;
47
+ transpile(target: Target<"Zod2Go">, opt?: TargetOpt<"Zod2Go">, astOpt?: AstOpt): string;
47
48
  }
48
49
  export {};
@@ -28,8 +28,15 @@ export declare class Zod2Cpp extends Zod2X<IZod2CppOpt> {
28
28
  type?: "union" | "alias";
29
29
  isInternal?: boolean;
30
30
  templates?: string;
31
+ templateDefinition?: string;
32
+ templateList?: string;
31
33
  }): void;
32
34
  protected getGenericTemplatesTranslation(data: ASTNode): string | undefined;
35
+ /**
36
+ * Emits an alias/extension declaration early for layered references.
37
+ * It keeps concrete template translations and falls back to declared templates (e.g. <T>)
38
+ * for aliases of generic templates.
39
+ */
33
40
  protected checkExtendedTypeInclusion(data: ASTNode, type?: "union" | "alias"): boolean;
34
41
  protected runAfter(): void;
35
42
  protected getDateType: () => string;
@@ -60,8 +67,14 @@ export declare class Zod2Cpp extends Zod2X<IZod2CppOpt> {
60
67
  protected getMapType(keyType: string, valueType: string): string;
61
68
  protected getRecordType(keyType: string, valueType: string): string;
62
69
  protected _getOptional(type: string): string;
63
- protected _addExtendedTypeSerializer(typeName: string, parentNamespace: string): void;
64
- protected _addExtendedTypeDeserializer(typeName: string, parentNamespace: string): void;
70
+ protected _addExtendedTypeSerializer(typeName: string, parentNamespace: string, opt?: {
71
+ templateDefinition?: string;
72
+ templateList?: string;
73
+ }): void;
74
+ protected _addExtendedTypeDeserializer(typeName: string, parentNamespace: string, opt?: {
75
+ templateDefinition?: string;
76
+ templateList?: string;
77
+ }): void;
65
78
  protected transpileAliasedType(data: ASTAliasedTypes): void;
66
79
  /** Ex:
67
80
  * enum class EnumA: int {
@@ -100,6 +100,9 @@ class Zod2Cpp extends core_1.Zod2X {
100
100
  ? aliasOf
101
101
  : this.getTypeFromExternalNamespace(parentNamespace, aliasOf);
102
102
  const templates = (_a = opt === null || opt === void 0 ? void 0 : opt.templates) !== null && _a !== void 0 ? _a : "";
103
+ if (opt === null || opt === void 0 ? void 0 : opt.templateDefinition) {
104
+ this.push0(opt.templateDefinition);
105
+ }
103
106
  if ((opt === null || opt === void 0 ? void 0 : opt.type) === "union" || (opt === null || opt === void 0 ? void 0 : opt.type) === "alias") {
104
107
  this.push0(`using ${name} = ${extendedType}${templates};\n`);
105
108
  }
@@ -112,8 +115,14 @@ class Zod2Cpp extends core_1.Zod2X {
112
115
  }
113
116
  }
114
117
  if ((opt === null || opt === void 0 ? void 0 : opt.type) !== "alias" && parentNamespace !== undefined) {
115
- this._addExtendedTypeSerializer(name, parentNamespace);
116
- this._addExtendedTypeDeserializer(name, parentNamespace);
118
+ this._addExtendedTypeSerializer(name, parentNamespace, {
119
+ templateDefinition: opt === null || opt === void 0 ? void 0 : opt.templateDefinition,
120
+ templateList: opt === null || opt === void 0 ? void 0 : opt.templateList,
121
+ });
122
+ this._addExtendedTypeDeserializer(name, parentNamespace, {
123
+ templateDefinition: opt === null || opt === void 0 ? void 0 : opt.templateDefinition,
124
+ templateList: opt === null || opt === void 0 ? void 0 : opt.templateList,
125
+ });
117
126
  }
118
127
  }
119
128
  getGenericTemplatesTranslation(data) {
@@ -134,12 +143,28 @@ class Zod2Cpp extends core_1.Zod2X {
134
143
  ">");
135
144
  }
136
145
  }
146
+ /**
147
+ * Emits an alias/extension declaration early for layered references.
148
+ * It keeps concrete template translations and falls back to declared templates (e.g. <T>)
149
+ * for aliases of generic templates.
150
+ */
137
151
  checkExtendedTypeInclusion(data, type) {
152
+ const templatesMeta = data instanceof core_1.ASTObject && data.templates.size > 0
153
+ ? this._getTemplates(data.templates)
154
+ : undefined;
155
+ const translatedTemplates = this.getGenericTemplatesTranslation(data);
156
+ const templates = translatedTemplates || (templatesMeta === null || templatesMeta === void 0 ? void 0 : templatesMeta.templateList);
157
+ const templateList = translatedTemplates ? undefined : templatesMeta === null || templatesMeta === void 0 ? void 0 : templatesMeta.templateList;
158
+ const templateDefinition = translatedTemplates
159
+ ? undefined
160
+ : templatesMeta === null || templatesMeta === void 0 ? void 0 : templatesMeta.templateDefinition;
138
161
  if (this.isExternalTypeImport(data)) {
139
162
  if (data.aliasOf) {
140
163
  this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
141
164
  type,
142
- templates: this.getGenericTemplatesTranslation(data),
165
+ templates,
166
+ templateDefinition,
167
+ templateList,
143
168
  });
144
169
  this.addExternalTypeImport(data);
145
170
  }
@@ -149,7 +174,9 @@ class Zod2Cpp extends core_1.Zod2X {
149
174
  this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
150
175
  type,
151
176
  isInternal: true,
152
- templates: this.getGenericTemplatesTranslation(data),
177
+ templates,
178
+ templateDefinition,
179
+ templateList,
153
180
  });
154
181
  return true;
155
182
  }
@@ -205,13 +232,23 @@ class Zod2Cpp extends core_1.Zod2X {
205
232
  this.imports.add(this.lib.optional);
206
233
  return `boost::optional<${type}>`;
207
234
  }
208
- _addExtendedTypeSerializer(typeName, parentNamespace) {
209
- this._push0(this.serializers, `inline void to_json(${nlohmann_1.NLOHMANN}& j, const ${typeName}& x) {`);
235
+ _addExtendedTypeSerializer(typeName, parentNamespace, opt) {
236
+ var _a;
237
+ if (opt === null || opt === void 0 ? void 0 : opt.templateDefinition) {
238
+ this._push0(this.serializers, opt.templateDefinition);
239
+ }
240
+ const typeNameWithTemplates = `${typeName}${(_a = opt === null || opt === void 0 ? void 0 : opt.templateList) !== null && _a !== void 0 ? _a : ""}`;
241
+ this._push0(this.serializers, `inline void to_json(${nlohmann_1.NLOHMANN}& j, const ${typeNameWithTemplates}& x) {`);
210
242
  this._push1(this.serializers, `${parentNamespace}::to_json(j, x);`);
211
243
  this._push0(this.serializers, "}\n");
212
244
  }
213
- _addExtendedTypeDeserializer(typeName, parentNamespace) {
214
- this._push0(this.serializers, `inline void from_json(const ${nlohmann_1.NLOHMANN}& j, ${typeName}& x) {`);
245
+ _addExtendedTypeDeserializer(typeName, parentNamespace, opt) {
246
+ var _a;
247
+ if (opt === null || opt === void 0 ? void 0 : opt.templateDefinition) {
248
+ this._push0(this.serializers, opt.templateDefinition);
249
+ }
250
+ const typeNameWithTemplates = `${typeName}${(_a = opt === null || opt === void 0 ? void 0 : opt.templateList) !== null && _a !== void 0 ? _a : ""}`;
251
+ this._push0(this.serializers, `inline void from_json(const ${nlohmann_1.NLOHMANN}& j, ${typeNameWithTemplates}& x) {`);
215
252
  this._push1(this.serializers, `${parentNamespace}::from_json(j, x);`);
216
253
  this._push0(this.serializers, "}\n");
217
254
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Go standard library and common package import strings.
3
+ */
4
+ export declare function getLibs(): {
5
+ timePackage: string;
6
+ jsonPackage: string;
7
+ fmtPackage: string;
8
+ };
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getLibs = getLibs;
4
+ /**
5
+ * Go standard library and common package import strings.
6
+ */
7
+ function getLibs() {
8
+ return {
9
+ timePackage: `"time"`,
10
+ jsonPackage: `"encoding/json"`,
11
+ fmtPackage: `"fmt"`,
12
+ };
13
+ }
@@ -0,0 +1,17 @@
1
+ import { IZodToXOpt } from "../../core";
2
+ export interface IZod2GoOpt extends IZodToXOpt {
3
+ /**
4
+ * The Go package name for the generated file. Default: "models"
5
+ */
6
+ packageName?: string;
7
+ /**
8
+ * By default (false), struct field names are exported (PascalCase) and JSON tags use the
9
+ * original property name. If set to true, original property names are preserved as-is.
10
+ */
11
+ keepKeys?: boolean;
12
+ /**
13
+ * Whether to emit `json:"fieldName"` struct tags. Default: true
14
+ */
15
+ useJsonTags?: boolean;
16
+ }
17
+ export declare const defaultOpts: IZod2GoOpt;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultOpts = void 0;
4
+ exports.defaultOpts = {
5
+ includeComments: true,
6
+ indent: 4,
7
+ useImports: true,
8
+ packageName: "models",
9
+ keepKeys: false,
10
+ useJsonTags: true,
11
+ };
@@ -0,0 +1,100 @@
1
+ import { ASTAliasedTypes, ASTEnum, ASTIntersection, ASTNode, ASTObject, ASTUnion, Zod2X } from "../../core";
2
+ import { IZod2GoOpt } from "./options";
3
+ /**
4
+ * Transpiler for Zod schemas to Go structs and types.
5
+ */
6
+ export declare class Zod2Go extends Zod2X<IZod2GoOpt> {
7
+ protected readonly commentKey = "//";
8
+ protected lib: {
9
+ timePackage: string;
10
+ jsonPackage: string;
11
+ fmtPackage: string;
12
+ };
13
+ constructor(opt?: IZod2GoOpt);
14
+ protected runBefore(): void;
15
+ protected runAfter(): void;
16
+ /**
17
+ * Consolidates all collected imports into a proper Go import block.
18
+ * Single import → `import "pkg"`, multiple → `import (\n\t"pkg"\n)`.
19
+ */
20
+ private _consolidateImports;
21
+ protected addImportFromFile(filename: string, namespace: string): string;
22
+ protected getTypeFromExternalNamespace(namespace: string, typeName: string): string;
23
+ protected addExtendedType(name: string, parentNamespace: string, aliasOf: string, opt?: {
24
+ type?: "union" | "alias";
25
+ isInternal?: boolean;
26
+ templates?: string;
27
+ declaredTemplates?: string;
28
+ }): void;
29
+ protected getGenericTemplatesTranslation(data: ASTNode): string | undefined;
30
+ /**
31
+ * Emits an alias/extension declaration early when a node references another layered type.
32
+ */
33
+ protected checkExtendedTypeInclusion(data: ASTNode, type?: "union" | "alias"): boolean;
34
+ protected getStringType: () => string;
35
+ protected getBooleanType: () => string;
36
+ protected getAnyType: () => string;
37
+ protected getDateType: () => string;
38
+ protected getNumberType: (isInt: boolean, range: {
39
+ min?: number;
40
+ max?: number;
41
+ }) => string;
42
+ protected getLiteralStringType(value: string | number | boolean, parentEnumNameKey?: [string, string]): string | number;
43
+ /** Ex: []TypeA, [][]TypeA */
44
+ protected getArrayType(arrayType: string, arrayDeep: number): string;
45
+ /** Ex: map[TypeA]struct{} */
46
+ protected getSetType: (itemType: string) => string;
47
+ /** Ex: map[KeyType]ValueType */
48
+ protected getMapType: (keyType: string, valueType: string) => string;
49
+ /** Ex: map[KeyType]ValueType */
50
+ protected getRecordType: (keyType: string, valueType: string) => string;
51
+ /** Go has no native tuple; use []any */
52
+ protected getTupleType: (_itemsType: string[]) => string;
53
+ /** Go has no native union; use any */
54
+ protected getUnionType: (_itemsType: string[]) => string;
55
+ /** Handled entirely in transpileIntersection via struct embedding */
56
+ protected getIntersectionType: () => string;
57
+ protected transpileAliasedType(data: ASTAliasedTypes): void;
58
+ /**
59
+ * Emit a Go enum using a typed string or int alias + const block.
60
+ *
61
+ * All-string values:
62
+ * type EnumItem string
63
+ * const (
64
+ * EnumItemEnum1 EnumItem = "Enum1"
65
+ * )
66
+ *
67
+ * All-int values:
68
+ * type EnumItem int
69
+ * const (
70
+ * EnumItemNativeEnum1 EnumItem = 1
71
+ * )
72
+ *
73
+ * Mixed (int + string): untyped constants with warning comment.
74
+ */
75
+ protected transpileEnum(data: ASTEnum): void;
76
+ /**
77
+ * Go union: emit `type Name any` with a comment listing possible types.
78
+ *
79
+ * For discriminated unions: emit a marker interface + marker stubs on each
80
+ * member type + an `UnmarshalXxx` helper that dispatches on the discriminant
81
+ * key using a `json.RawMessage` probe (uniform for string, bool, and number
82
+ * discriminant values).
83
+ */
84
+ protected transpileUnion(data: ASTUnion): void;
85
+ /**
86
+ * Go intersection: struct embedding.
87
+ *
88
+ * type IntersectionItem struct {
89
+ * ObjectItem
90
+ * OtherObjectItem
91
+ * }
92
+ */
93
+ protected transpileIntersection(data: ASTIntersection): void;
94
+ protected transpileStruct(data: ASTObject): void;
95
+ /** Render a Go struct body for an ASTObject. */
96
+ private _transpileStructBody;
97
+ /** Render a single struct field: `FieldName Type \`json:"key"\`` */
98
+ private _transpileMember;
99
+ private _buildJsonTag;
100
+ }
@@ -0,0 +1,399 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Zod2Go = void 0;
7
+ const case_1 = __importDefault(require("case"));
8
+ const core_1 = require("../../core");
9
+ const number_limits_1 = require("../../utils/number_limits");
10
+ const libs_1 = require("./libs");
11
+ const options_1 = require("./options");
12
+ /**
13
+ * Transpiler for Zod schemas to Go structs and types.
14
+ */
15
+ class Zod2Go extends core_1.Zod2X {
16
+ constructor(opt = {}) {
17
+ super(Object.assign(Object.assign({}, options_1.defaultOpts), opt));
18
+ this.commentKey = "//";
19
+ // ── Primitive type methods ──────────────────────────────────────────────
20
+ this.getStringType = () => "string";
21
+ this.getBooleanType = () => "bool";
22
+ this.getAnyType = () => "any";
23
+ this.getDateType = () => {
24
+ this.imports.add(this.lib.timePackage);
25
+ return "time.Time";
26
+ };
27
+ this.getNumberType = (isInt, range) => {
28
+ if (!isInt)
29
+ return "float64";
30
+ const min = range.min;
31
+ const max = range.max;
32
+ if (min !== undefined &&
33
+ max !== undefined &&
34
+ min >= number_limits_1.INT32_RANGES[0] &&
35
+ max <= number_limits_1.INT32_RANGES[1]) {
36
+ return "int32";
37
+ }
38
+ return "int64";
39
+ };
40
+ /** Ex: map[TypeA]struct{} */
41
+ this.getSetType = (itemType) => `map[${itemType}]struct{}`;
42
+ /** Ex: map[KeyType]ValueType */
43
+ this.getMapType = (keyType, valueType) => `map[${keyType}]${valueType}`;
44
+ /** Ex: map[KeyType]ValueType */
45
+ this.getRecordType = (keyType, valueType) => `map[${keyType}]${valueType}`;
46
+ /** Go has no native tuple; use []any */
47
+ this.getTupleType = (_itemsType) => "[]any";
48
+ /** Go has no native union; use any */
49
+ this.getUnionType = (_itemsType) => "any";
50
+ /** Handled entirely in transpileIntersection via struct embedding */
51
+ this.getIntersectionType = () => "";
52
+ this.lib = (0, libs_1.getLibs)();
53
+ }
54
+ runBefore() {
55
+ var _a;
56
+ this.preImports.add(`package ${(_a = this.opt.packageName) !== null && _a !== void 0 ? _a : "models"}`);
57
+ }
58
+ runAfter() {
59
+ this._consolidateImports();
60
+ }
61
+ /**
62
+ * Consolidates all collected imports into a proper Go import block.
63
+ * Single import → `import "pkg"`, multiple → `import (\n\t"pkg"\n)`.
64
+ */
65
+ _consolidateImports() {
66
+ if (this.imports.size === 0)
67
+ return;
68
+ const sorted = Array.from(this.imports).sort();
69
+ let block;
70
+ if (sorted.length === 1) {
71
+ block = `import ${sorted[0]}`;
72
+ }
73
+ else {
74
+ block = `import (\n${sorted.map((s) => `\t${s}`).join("\n")}\n)`;
75
+ }
76
+ // Replace the individual entries with the consolidated block
77
+ this.imports.clear();
78
+ this.imports.add(block);
79
+ }
80
+ addImportFromFile(filename, namespace) {
81
+ const base = filename.endsWith(".go") ? filename.slice(0, -3) : filename;
82
+ return `${namespace} "./${base}"`;
83
+ }
84
+ getTypeFromExternalNamespace(namespace, typeName) {
85
+ return `${namespace}.${typeName}`;
86
+ }
87
+ addExtendedType(name, parentNamespace, aliasOf, opt) {
88
+ var _a, _b;
89
+ const extendedType = (opt === null || opt === void 0 ? void 0 : opt.isInternal)
90
+ ? aliasOf
91
+ : this.getTypeFromExternalNamespace(parentNamespace, aliasOf);
92
+ const templates = (_a = opt === null || opt === void 0 ? void 0 : opt.templates) !== null && _a !== void 0 ? _a : "";
93
+ const declaredTemplates = (_b = opt === null || opt === void 0 ? void 0 : opt.declaredTemplates) !== null && _b !== void 0 ? _b : "";
94
+ if ((opt === null || opt === void 0 ? void 0 : opt.type) === "alias" || (opt === null || opt === void 0 ? void 0 : opt.type) === "union") {
95
+ this.push0(`type ${name}${declaredTemplates} = ${extendedType}${templates}\n`);
96
+ }
97
+ else {
98
+ // Struct embedding: type ChildName struct { ParentType }
99
+ this.push0(`type ${name}${declaredTemplates} struct {`);
100
+ this.push1(`${extendedType}${templates}`);
101
+ this.push0(`}\n`);
102
+ }
103
+ }
104
+ getGenericTemplatesTranslation(data) {
105
+ if ((data instanceof core_1.ASTObject || data instanceof core_1.ASTDefinition) &&
106
+ data.templatesTranslation.length > 0) {
107
+ return ("[" +
108
+ data.templatesTranslation
109
+ .map((t) => {
110
+ if (this.isExternalTypeImport(t)) {
111
+ this.addExternalTypeImport(t);
112
+ return this.getTypeFromExternalNamespace(t.parentNamespace, t.aliasOf);
113
+ }
114
+ else {
115
+ return t.aliasOf;
116
+ }
117
+ })
118
+ .join(", ") +
119
+ "]");
120
+ }
121
+ }
122
+ /**
123
+ * Emits an alias/extension declaration early when a node references another layered type.
124
+ */
125
+ checkExtendedTypeInclusion(data, type) {
126
+ const isStruct = data instanceof core_1.ASTObject ||
127
+ (data instanceof core_1.ASTIntersection && data.newObject !== undefined);
128
+ const translatedTemplates = this.getGenericTemplatesTranslation(data);
129
+ const templates = translatedTemplates || undefined;
130
+ // For declared-template fallback on the declared (definition) side
131
+ const declaredTemplates = !translatedTemplates && data instanceof core_1.ASTObject && data.templates.size > 0
132
+ ? `[${[...data.templates].map((t) => `${t} any`).join(", ")}]`
133
+ : undefined;
134
+ if (this.isExternalTypeImport(data)) {
135
+ if (data.aliasOf) {
136
+ this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
137
+ type: isStruct ? undefined : type,
138
+ templates,
139
+ declaredTemplates,
140
+ });
141
+ this.addExternalTypeImport(data);
142
+ }
143
+ return true;
144
+ }
145
+ else if (data.aliasOf) {
146
+ this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
147
+ type: isStruct ? undefined : type,
148
+ isInternal: true,
149
+ templates,
150
+ declaredTemplates,
151
+ });
152
+ return true;
153
+ }
154
+ return false;
155
+ }
156
+ getLiteralStringType(value, parentEnumNameKey) {
157
+ if (parentEnumNameKey) {
158
+ // Go constants cannot be used as types; use the parent enum type name
159
+ return parentEnumNameKey[0];
160
+ }
161
+ // Go has no literal types; return the underlying primitive type
162
+ if (typeof value === "boolean")
163
+ return "bool";
164
+ if (typeof value === "number")
165
+ return isNaN(value) ? "float64" : Number.isInteger(value) ? "int64" : "float64";
166
+ return "string";
167
+ }
168
+ // ── Composite type methods ──────────────────────────────────────────────
169
+ /** Ex: []TypeA, [][]TypeA */
170
+ getArrayType(arrayType, arrayDeep) {
171
+ let output = `[]${arrayType}`;
172
+ for (let i = 0; i < arrayDeep - 1; i++) {
173
+ output = `[]${output}`;
174
+ }
175
+ return output;
176
+ }
177
+ // ── Transpile methods ───────────────────────────────────────────────────
178
+ transpileAliasedType(data) {
179
+ if (this.checkExtendedTypeInclusion(data, "alias")) {
180
+ return;
181
+ }
182
+ this.addComment(data.description);
183
+ let extendedType;
184
+ if (data instanceof core_1.ASTArray) {
185
+ extendedType = this.getAttributeType(data.item);
186
+ }
187
+ else {
188
+ extendedType = this.getAttributeType(data);
189
+ }
190
+ this.push0(`type ${data.name} = ${extendedType}\n`);
191
+ }
192
+ /**
193
+ * Emit a Go enum using a typed string or int alias + const block.
194
+ *
195
+ * All-string values:
196
+ * type EnumItem string
197
+ * const (
198
+ * EnumItemEnum1 EnumItem = "Enum1"
199
+ * )
200
+ *
201
+ * All-int values:
202
+ * type EnumItem int
203
+ * const (
204
+ * EnumItemNativeEnum1 EnumItem = 1
205
+ * )
206
+ *
207
+ * Mixed (int + string): untyped constants with warning comment.
208
+ */
209
+ transpileEnum(data) {
210
+ if (this.checkExtendedTypeInclusion(data, "alias")) {
211
+ return;
212
+ }
213
+ this.addComment(data.description);
214
+ const allStrings = data.values.every(([, v]) => typeof v === "string");
215
+ const allInts = data.values.every(([, v]) => typeof v === "number");
216
+ if (allStrings) {
217
+ this.push0(`type ${data.name} string\n`);
218
+ this.push0(`const (`);
219
+ data.values.forEach(([key, value]) => {
220
+ const constName = `${data.name}${case_1.default.pascal(key)}`;
221
+ this.push1(`${constName} ${data.name} = "${value}"`);
222
+ });
223
+ this.push0(`)\n`);
224
+ }
225
+ else if (allInts) {
226
+ this.push0(`type ${data.name} int\n`);
227
+ this.push0(`const (`);
228
+ data.values.forEach(([key, value]) => {
229
+ const constName = `${data.name}${case_1.default.pascal(key)}`;
230
+ this.push1(`${constName} ${data.name} = ${value}`);
231
+ });
232
+ this.push0(`)\n`);
233
+ }
234
+ else {
235
+ // Mixed types — Go cannot express this as a single typed enum.
236
+ // Declare as `any` so struct fields can reference the type name.
237
+ this.push0(`type ${data.name} = any\n`);
238
+ this.output.push(`// ${data.name}: mixed-type enum — no single Go base type available`);
239
+ this.push0(`const (`);
240
+ data.values.forEach(([key, value]) => {
241
+ const constName = `${data.name}${case_1.default.pascal(key)}`;
242
+ const v = typeof value === "string" ? `"${value}"` : `${value}`;
243
+ this.push1(`${constName} = ${v}`);
244
+ });
245
+ this.push0(`)\n`);
246
+ }
247
+ }
248
+ /**
249
+ * Go union: emit `type Name any` with a comment listing possible types.
250
+ *
251
+ * For discriminated unions: emit a marker interface + marker stubs on each
252
+ * member type + an `UnmarshalXxx` helper that dispatches on the discriminant
253
+ * key using a `json.RawMessage` probe (uniform for string, bool, and number
254
+ * discriminant values).
255
+ */
256
+ transpileUnion(data) {
257
+ if (this.checkExtendedTypeInclusion(data, "union")) {
258
+ return;
259
+ }
260
+ this.addComment(data.description);
261
+ const optionNames = data.options.map(this.getAttributeType.bind(this));
262
+ if (data.discriminantKey) {
263
+ const methodName = `is${data.name}`;
264
+ this.push0(`// ${data.name} is a discriminated union on "${data.discriminantKey}".`);
265
+ this.push0(`// Possible types: ${optionNames.join(", ")}`);
266
+ this.push0(`type ${data.name} interface {`);
267
+ this.push1(`${methodName}()`);
268
+ this.push0(`}\n`);
269
+ // Marker stubs: one no-op method per member type so each satisfies the interface.
270
+ // For generic instantiations (e.g. "HttpSuccessfulResponse[SomeDtoResult]") we emit
271
+ // a generic receiver "func (t Base[T]) isXxx() {}" using the base type name only.
272
+ for (const name of optionNames) {
273
+ const bracketIdx = name.indexOf("[");
274
+ const receiver = bracketIdx !== -1 ? `${name.slice(0, bracketIdx)}[T]` : name;
275
+ this.push0(`func (t ${receiver}) ${methodName}() {}\n`);
276
+ }
277
+ // UnmarshalXxx helper — only when every member has a discriminant value in the AST.
278
+ const optionsData = data.options.map((opt, i) => {
279
+ var _a;
280
+ return ({
281
+ typeName: optionNames[i],
282
+ discriminantValue: (_a = opt.constraints) === null || _a === void 0 ? void 0 : _a.discriminantValue,
283
+ });
284
+ });
285
+ const allHaveDiscriminantValue = optionsData.every((o) => o.discriminantValue !== undefined);
286
+ if (allHaveDiscriminantValue) {
287
+ this.imports.add(this.lib.jsonPackage);
288
+ this.imports.add(this.lib.fmtPackage);
289
+ const probeField = data.discriminantKey.charAt(0).toUpperCase() + data.discriminantKey.slice(1);
290
+ this.push0(`// Unmarshal${data.name} deserializes JSON into the correct ${data.name} concrete type`);
291
+ this.push0(`// by probing the "${data.discriminantKey}" discriminant field.`);
292
+ this.push0(`func Unmarshal${data.name}(data []byte) (${data.name}, error) {`);
293
+ this.push1(`var probe struct {`);
294
+ this.push2(`${probeField} json.RawMessage \`json:"${data.discriminantKey}"\``);
295
+ this.push1(`}`);
296
+ this.push1(`if err := json.Unmarshal(data, &probe); err != nil {`);
297
+ this.push2(`return nil, err`);
298
+ this.push1(`}`);
299
+ this.push1(`switch string(probe.${probeField}) {`);
300
+ for (const opt of optionsData) {
301
+ const dv = opt.discriminantValue;
302
+ // Determine raw JSON representation of the case value.
303
+ // String literals appear in JSON with surrounding quotes ("Enum1" → "Enum1").
304
+ // Bool and number literals appear without quotes (true → true, 42 → 42).
305
+ const isStringLiteral = isNaN(Number(dv)) && dv !== "true" && dv !== "false";
306
+ const caseVal = isStringLiteral ? `\`"${dv}"\`` : `"${dv}"`;
307
+ this.push1(`case ${caseVal}:`);
308
+ this.push2(`var v ${opt.typeName}`);
309
+ this.push2(`if err := json.Unmarshal(data, &v); err != nil {`);
310
+ this.push3(`return nil, err`);
311
+ this.push2(`}`);
312
+ this.push2(`return v, nil`);
313
+ }
314
+ this.push1(`}`);
315
+ this.push1(`return nil, fmt.Errorf("failed to deserialize ${data.name}: unknown discriminator %s", string(probe.${probeField}))`);
316
+ this.push0(`}\n`);
317
+ }
318
+ }
319
+ else {
320
+ this.push0(`// ${data.name} is a union of: ${optionNames.join(", ")}`);
321
+ this.push0(`type ${data.name} = any\n`);
322
+ }
323
+ }
324
+ /**
325
+ * Go intersection: struct embedding.
326
+ *
327
+ * type IntersectionItem struct {
328
+ * ObjectItem
329
+ * OtherObjectItem
330
+ * }
331
+ */
332
+ transpileIntersection(data) {
333
+ if (this.checkExtendedTypeInclusion(data)) {
334
+ return;
335
+ }
336
+ this.addComment(data.description);
337
+ if (data.newObject) {
338
+ // Flatten the merged object into a plain struct
339
+ this._transpileStructBody(data.newObject);
340
+ }
341
+ else {
342
+ // Embed both sides
343
+ const leftType = this.getAttributeType(data.left);
344
+ const rightType = this.getAttributeType(data.right);
345
+ this.push0(`type ${data.name} struct {`);
346
+ this.push1(leftType);
347
+ this.push1(rightType);
348
+ this.push0(`}\n`);
349
+ }
350
+ }
351
+ transpileStruct(data) {
352
+ if (this.checkExtendedTypeInclusion(data)) {
353
+ return;
354
+ }
355
+ this.addComment(data.description);
356
+ this._transpileStructBody(data);
357
+ }
358
+ /** Render a Go struct body for an ASTObject. */
359
+ _transpileStructBody(data) {
360
+ const templateParams = data.templates.size > 0
361
+ ? `[${[...data.templates].map((t) => `${t} any`).join(", ")}]`
362
+ : "";
363
+ this.push0(`type ${data.name}${templateParams} struct {`);
364
+ const hasProperties = Object.keys(data.properties).length > 0;
365
+ if (!hasProperties) {
366
+ this.push0(`}\n`);
367
+ return;
368
+ }
369
+ for (const [key, value] of Object.entries(data.properties)) {
370
+ const fieldName = this.opt.keepKeys === true ? key : case_1.default.pascal(key);
371
+ this._transpileMember(fieldName, key, value);
372
+ }
373
+ this.push0(`}\n`);
374
+ }
375
+ /** Render a single struct field: `FieldName Type \`json:"key"\`` */
376
+ _transpileMember(fieldName, originalKey, memberNode) {
377
+ const isOptionalOrNullable = memberNode.isOptional || memberNode.isNullable;
378
+ let varType = this.getAttributeType(memberNode);
379
+ // Optional/nullable fields use pointer types
380
+ if (isOptionalOrNullable) {
381
+ // Avoid double-pointer for types already expressed as pointers/interfaces
382
+ if (!varType.startsWith("*") && varType !== "any") {
383
+ varType = `*${varType}`;
384
+ }
385
+ }
386
+ if (memberNode.description && !memberNode.name && !this.isTranspilerable(memberNode)) {
387
+ this.addComment(memberNode.description, `\n${this.indent[1]}`);
388
+ }
389
+ const tag = this._buildJsonTag(originalKey, isOptionalOrNullable !== null && isOptionalOrNullable !== void 0 ? isOptionalOrNullable : false);
390
+ this.push1(`${fieldName} ${varType}${tag}`);
391
+ }
392
+ _buildJsonTag(originalKey, omitempty) {
393
+ if (this.opt.useJsonTags === false)
394
+ return "";
395
+ const flags = omitempty ? `,omitempty` : "";
396
+ return ` \`json:"${originalKey}${flags}"\``;
397
+ }
398
+ }
399
+ exports.Zod2Go = Zod2Go;
@@ -1,3 +1,4 @@
1
1
  export { Zod2Ts } from "./typescript/runner";
2
2
  export { Zod2Cpp, Zod2Cpp17 } from "./cpp/runner";
3
3
  export { Zod2Py } from "./python/runner";
4
+ export { Zod2Go } from "./go/runner";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Zod2Py = exports.Zod2Cpp17 = exports.Zod2Cpp = exports.Zod2Ts = void 0;
3
+ exports.Zod2Go = exports.Zod2Py = exports.Zod2Cpp17 = exports.Zod2Cpp = exports.Zod2Ts = void 0;
4
4
  var runner_1 = require("./typescript/runner");
5
5
  Object.defineProperty(exports, "Zod2Ts", { enumerable: true, get: function () { return runner_1.Zod2Ts; } });
6
6
  var runner_2 = require("./cpp/runner");
@@ -8,3 +8,5 @@ Object.defineProperty(exports, "Zod2Cpp", { enumerable: true, get: function () {
8
8
  Object.defineProperty(exports, "Zod2Cpp17", { enumerable: true, get: function () { return runner_2.Zod2Cpp17; } });
9
9
  var runner_3 = require("./python/runner");
10
10
  Object.defineProperty(exports, "Zod2Py", { enumerable: true, get: function () { return runner_3.Zod2Py; } });
11
+ var runner_4 = require("./go/runner");
12
+ Object.defineProperty(exports, "Zod2Go", { enumerable: true, get: function () { return runner_4.Zod2Go; } });
@@ -23,6 +23,7 @@ export declare class Zod2Py extends Zod2X<IZod2PyOpt> {
23
23
  };
24
24
  private baseSchemaAdded;
25
25
  private typeVars;
26
+ private pendingTypeVars;
26
27
  constructor(opt?: IZod2PyOpt);
27
28
  protected runAfter(): void;
28
29
  protected runBefore(): void;
@@ -31,6 +32,7 @@ export declare class Zod2Py extends Zod2X<IZod2PyOpt> {
31
32
  * This is the base class for all Pydantic models with shared configuration.
32
33
  */
33
34
  private _addBaseSchema;
35
+ private _flushPendingTypeVars;
34
36
  /**
35
37
  * Declares TypeVars that haven't been declared yet.
36
38
  * Adds them right before their first usage.
@@ -51,6 +53,11 @@ export declare class Zod2Py extends Zod2X<IZod2PyOpt> {
51
53
  isClass?: boolean;
52
54
  }): void;
53
55
  protected getGenericTemplatesTranslation(data: ASTNode): string | undefined;
56
+ /**
57
+ * Emits an alias/extension declaration early for layered references.
58
+ * It keeps concrete template translations and falls back to declared templates (e.g. [T])
59
+ * for aliases of generic templates.
60
+ */
54
61
  protected checkExtendedTypeInclusion(data: ASTNode, type?: "alias" | "union" | "d-union"): boolean;
55
62
  protected getAnyType: () => string;
56
63
  protected getBooleanType: () => string;
@@ -14,6 +14,7 @@ class Zod2Py extends core_1.Zod2X {
14
14
  this.commentKey = "#";
15
15
  this.baseSchemaAdded = false;
16
16
  this.typeVars = new Set();
17
+ this.pendingTypeVars = new Set();
17
18
  this.getAnyType = () => {
18
19
  this.imports.add(this.lib.anyType);
19
20
  return "Any";
@@ -51,6 +52,9 @@ class Zod2Py extends core_1.Zod2X {
51
52
  this.lib = (0, libs_1.getLibs)();
52
53
  }
53
54
  runAfter() {
55
+ if (!this.baseSchemaAdded) {
56
+ this._flushPendingTypeVars(true);
57
+ }
54
58
  this._consolidateImports();
55
59
  }
56
60
  runBefore() { }
@@ -76,6 +80,22 @@ class Zod2Py extends core_1.Zod2X {
76
80
  this.push2("use_enum_values=True");
77
81
  this.push1(")");
78
82
  this.push0("");
83
+ this._flushPendingTypeVars(false);
84
+ }
85
+ _flushPendingTypeVars(prepend) {
86
+ if (this.pendingTypeVars.size === 0)
87
+ return;
88
+ const pending = Array.from(this.pendingTypeVars);
89
+ const lines = pending.map((typeVar) => `${typeVar} = TypeVar('${typeVar}')`);
90
+ if (prepend) {
91
+ this.output = [...lines, "", ...this.output];
92
+ }
93
+ else {
94
+ lines.forEach((line) => this.push0(line));
95
+ this.push0("");
96
+ }
97
+ pending.forEach((typeVar) => this.typeVars.add(typeVar));
98
+ this.pendingTypeVars.clear();
79
99
  }
80
100
  /**
81
101
  * Declares TypeVars that haven't been declared yet.
@@ -83,10 +103,14 @@ class Zod2Py extends core_1.Zod2X {
83
103
  * Ex: T = TypeVar('T')
84
104
  */
85
105
  _declareNewTypeVars(templates) {
86
- const newTypeVars = Array.from(templates).filter((t) => !this.typeVars.has(t));
106
+ const newTypeVars = Array.from(templates).filter((t) => !this.typeVars.has(t) && !this.pendingTypeVars.has(t));
87
107
  if (newTypeVars.length === 0)
88
108
  return;
89
109
  this.imports.add(this.lib.typeVarType);
110
+ if (!this.baseSchemaAdded) {
111
+ newTypeVars.forEach((typeVar) => this.pendingTypeVars.add(typeVar));
112
+ return;
113
+ }
90
114
  newTypeVars.forEach((typeVar) => {
91
115
  this.push0(`${typeVar} = TypeVar('${typeVar}')`);
92
116
  this.typeVars.add(typeVar);
@@ -183,15 +207,28 @@ class Zod2Py extends core_1.Zod2X {
183
207
  "]");
184
208
  }
185
209
  }
210
+ /**
211
+ * Emits an alias/extension declaration early for layered references.
212
+ * It keeps concrete template translations and falls back to declared templates (e.g. [T])
213
+ * for aliases of generic templates.
214
+ */
186
215
  checkExtendedTypeInclusion(data, type) {
187
216
  // Determine if the aliased type is a class (ASTObject or ASTIntersection with newObject)
188
217
  const isClass = data instanceof core_1.ASTObject ||
189
218
  (data instanceof core_1.ASTIntersection && data.newObject !== undefined);
219
+ const declaredTemplatesFallback = data instanceof core_1.ASTObject && data.templates.size > 0 && this.isExternalTypeImport(data)
220
+ ? `[${[...data.templates].join(", ")}]`
221
+ : undefined;
222
+ const translatedTemplates = this.getGenericTemplatesTranslation(data);
223
+ const templates = translatedTemplates || declaredTemplatesFallback;
224
+ if (!translatedTemplates && data instanceof core_1.ASTObject && data.templates.size > 0) {
225
+ this._declareNewTypeVars(data.templates);
226
+ }
190
227
  if (this.isExternalTypeImport(data)) {
191
228
  if (data.aliasOf) {
192
229
  this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
193
230
  type,
194
- templates: this.getGenericTemplatesTranslation(data),
231
+ templates,
195
232
  isClass,
196
233
  });
197
234
  this.addExternalTypeImport(data);
@@ -202,7 +239,7 @@ class Zod2Py extends core_1.Zod2X {
202
239
  this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
203
240
  type,
204
241
  isInternal: true,
205
- templates: this.getGenericTemplatesTranslation(data),
242
+ templates,
206
243
  isClass,
207
244
  });
208
245
  return true;
@@ -11,8 +11,14 @@ export declare class Zod2Ts extends Zod2X<IZod2TsOpt> {
11
11
  type?: "union" | "d-union" | "alias";
12
12
  isInternal?: boolean;
13
13
  templates?: string;
14
+ declaredTemplates?: string;
14
15
  }): void;
15
16
  protected getGenericTemplatesTranslation(data: ASTNode): string | undefined;
17
+ /**
18
+ * Emits an alias/extension declaration early when a node references another layered type.
19
+ * It preserves concrete template translations and falls back to declared templates (e.g. <T>)
20
+ * for aliases of generic templates.
21
+ */
16
22
  protected checkExtendedTypeInclusion(data: ASTNode, type?: "alias" | "union" | "d-union"): boolean;
17
23
  protected getAnyType: () => string;
18
24
  protected getBooleanType: () => string;
@@ -37,28 +37,29 @@ class Zod2Ts extends core_1.Zod2X {
37
37
  return `${namespace}.${typeName}`;
38
38
  }
39
39
  addExtendedType(name, parentNamespace, aliasOf, opt) {
40
- var _a;
40
+ var _a, _b;
41
41
  const extendedType = (opt === null || opt === void 0 ? void 0 : opt.isInternal)
42
42
  ? aliasOf
43
43
  : this.getTypeFromExternalNamespace(parentNamespace, aliasOf);
44
44
  const templates = (_a = opt === null || opt === void 0 ? void 0 : opt.templates) !== null && _a !== void 0 ? _a : "";
45
+ const declaredName = `${name}${(_b = opt === null || opt === void 0 ? void 0 : opt.declaredTemplates) !== null && _b !== void 0 ? _b : ""}`;
45
46
  if ((opt === null || opt === void 0 ? void 0 : opt.type) === "alias") {
46
- this.push0(`export type ${name} = ${extendedType}${templates};\n`);
47
+ this.push0(`export type ${declaredName} = ${extendedType}${templates};\n`);
47
48
  }
48
49
  else if (this.opt.outType === "class") {
49
50
  if ((opt === null || opt === void 0 ? void 0 : opt.type) === "d-union") {
50
- this.push0(`export type ${name} = ${extendedType}${templates};\n`);
51
+ this.push0(`export type ${declaredName} = ${extendedType}${templates};\n`);
51
52
  }
52
53
  else {
53
- this.push0(`export class ${name} extends ${extendedType}${templates} {}\n`);
54
+ this.push0(`export class ${declaredName} extends ${extendedType}${templates} {}\n`);
54
55
  }
55
56
  }
56
57
  else {
57
58
  if ((opt === null || opt === void 0 ? void 0 : opt.type) === "union" || (opt === null || opt === void 0 ? void 0 : opt.type) === "d-union") {
58
- this.push0(`export type ${name} = ${extendedType}${templates};\n`);
59
+ this.push0(`export type ${declaredName} = ${extendedType}${templates};\n`);
59
60
  }
60
61
  else {
61
- this.push0(`export interface ${name} extends ${extendedType}${templates} {}\n`);
62
+ this.push0(`export interface ${declaredName} extends ${extendedType}${templates} {}\n`);
62
63
  }
63
64
  }
64
65
  }
@@ -80,12 +81,24 @@ class Zod2Ts extends core_1.Zod2X {
80
81
  ">");
81
82
  }
82
83
  }
84
+ /**
85
+ * Emits an alias/extension declaration early when a node references another layered type.
86
+ * It preserves concrete template translations and falls back to declared templates (e.g. <T>)
87
+ * for aliases of generic templates.
88
+ */
83
89
  checkExtendedTypeInclusion(data, type) {
90
+ const declaredTemplatesFallback = data instanceof core_1.ASTObject && data.templates.size > 0
91
+ ? `<${[...data.templates].join(", ")}>`
92
+ : undefined;
93
+ const translatedTemplates = this.getGenericTemplatesTranslation(data);
94
+ const templates = translatedTemplates || declaredTemplatesFallback;
95
+ const declaredTemplates = translatedTemplates ? undefined : declaredTemplatesFallback;
84
96
  if (this.isExternalTypeImport(data)) {
85
97
  if (data.aliasOf) {
86
98
  this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
87
99
  type,
88
- templates: this.getGenericTemplatesTranslation(data),
100
+ templates,
101
+ declaredTemplates,
89
102
  });
90
103
  this.addExternalTypeImport(data);
91
104
  }
@@ -95,7 +108,8 @@ class Zod2Ts extends core_1.Zod2X {
95
108
  this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
96
109
  type,
97
110
  isInternal: true,
98
- templates: this.getGenericTemplatesTranslation(data),
111
+ templates,
112
+ declaredTemplates,
99
113
  });
100
114
  return true;
101
115
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod-to-x",
3
- "version": "1.6.1-dev.0",
3
+ "version": "1.7.0",
4
4
  "description": "Multi language types generation from Zod schemas.",
5
5
  "main": "dist/index.js",
6
6
  "files": [
@@ -11,10 +11,12 @@
11
11
  "build": "npm run clear_dist && tsc && tsc-alias",
12
12
  "build:debug": "npm run clear_dist && tsc --project tsconfig.dev.json && tsc-alias",
13
13
  "format:check": "prettier --check .",
14
+ "swap": "npm install && npm run build",
14
15
  "test": "find ./test -name \"err-*\" -delete && vitest --run",
15
16
  "test:cpp": "bash ./test/test_zod2cpp/test_cpp.sh",
16
17
  "test:py": "bash ./test/test_zod2py/test_py.sh",
17
- "test:all": "npm run test:cpp && npm run test:py",
18
+ "test:go": "bash ./test/test_zod2go/test_go.sh",
19
+ "test:all": "npm run test:cpp && npm run test:py && npm run test:go",
18
20
  "ts-run": "ts-node -r tsconfig-paths/register",
19
21
  "prepare": "husky"
20
22
  },
@@ -29,7 +31,7 @@
29
31
  "type",
30
32
  "generator",
31
33
  "zod-to-cpp",
32
- "zpd-to-c++",
34
+ "zod-to-c++",
33
35
  "zod-to-ts",
34
36
  "zod-to-typescript",
35
37
  "typescript",
@@ -59,7 +61,7 @@
59
61
  "husky": "^9.1.7",
60
62
  "lint-staged": "^15.2.11",
61
63
  "prettier": "^3.4.2",
62
- "protobufjs": "7.4.0",
64
+ "protobufjs": "7.5.5",
63
65
  "ts-node": "^10.9.2",
64
66
  "tsc-alias": "^1.8.10",
65
67
  "tsconfig-paths": "4.2.0",