zod-to-x 2.2.0 → 2.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/README.md CHANGED
@@ -28,29 +28,35 @@
28
28
  <img src="https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript">
29
29
  <img src="https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white" alt="Python">
30
30
  <img src="https://img.shields.io/badge/C++-00599C?style=for-the-badge&logo=cplusplus&logoColor=white" alt="C++">
31
+ <img src="https://img.shields.io/badge/Go-00ADD8?style=for-the-badge&logo=go&logoColor=white" alt="Go">
31
32
  <img src="https://img.shields.io/badge/Protobuf-4285F4?style=for-the-badge&logo=google&logoColor=white" alt="Protobuf">
32
33
  </p>
33
34
 
34
35
 
35
36
 
36
37
  ## Table of contents
37
- - [Why use `@zod-to-x`](#why-use-zod-to-x)
38
+ - [Table of contents](#table-of-contents)
39
+ - [Why Use `@zod-to-x`?](#why-use-zod-to-x)
38
40
  - [Installation](#installation)
41
+ - [1) Install `@zod-to-x` and `@zod(*)` dependency.](#1-install-zod-to-x-and-zod-dependency)
42
+ - [2) Extend Zod using the `extendZod` method after the first `@zod` import:](#2-extend-zod-using-the-extendzod-method-after-the-first-zod-import)
39
43
  - [Quick start](#quick-start)
40
44
  - [Intersections and Unions](#intersections-and-unions)
41
45
  - [Expected outputs](#expected-outputs)
42
46
  - [Tips for discriminated unions](#tips-for-discriminated-unions)
43
47
  - [Layered modeling](#layered-modeling)
44
48
  - [Usage example](#usage-example)
45
- - [Custom layers](#custom-layers)
49
+ - [Custom Layers](#custom-layers)
46
50
  - [Generic types](#generic-types)
47
51
  - [Currently supported output languages](#currently-supported-output-languages)
48
- - [Typescript](#1-typescript)
49
- - [Python](#2-python) <sup>*(new)*</sup>
50
- - [C++](#3-c)
52
+ - [0) ASTNode](#0-astnode)
53
+ - [1) Typescript](#1-typescript)
54
+ - [2) Python](#2-python)
55
+ - [3) C++](#3-c)
56
+ - [4) Go](#4-go)
51
57
  - [Additional utils](#additional-utils)
52
- - [Protobuf V3 generation](#2-zod2protov3)
53
- - [Mapping of supported Zod Types by Language](#mapping-of-supported-zod-types-by-langauge)
58
+ - [1) `zod2ProtoV3`](#1-zod2protov3)
59
+ - [Mapping of supported Zod Types by Langauge](#mapping-of-supported-zod-types-by-langauge)
54
60
  - [Considerations](#considerations)
55
61
 
56
62
 
@@ -62,7 +68,7 @@ Managing data consistency across multiple layers and languages is a common chall
62
68
  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.
63
69
 
64
70
  2. **Multi-Language Compatibility**
65
- 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.
71
+ Generate data models for TypeScript, Python (Pydantic), C++, Go, and Protobuf V3. No more manually rewriting models for different platforms.
66
72
 
67
73
  3. **Enhanced Productivity**
68
74
  Automate the transpilation of data models to save time, reduce errors, and let your team focus on business logic instead of boilerplate code.
@@ -628,7 +634,13 @@ Common options:
628
634
  - **keepKeys**: Specifies whether property names should follow the C++ naming convention (false) or remain as originally defined (true). The default is `false`.
629
635
  - [Examples](https://github.com/rroumenov/zod-to-x/blob/main/test/test_zod2cpp)
630
636
 
631
-
637
+ ### 4) Go
638
+ Generates idiomatic Go structs with `encoding/json` struct tags. Requires Go 1.18+ for generic type support.
639
+ - Options:
640
+ - **packageName**: The Go package name for the generated file. Defaults to `"models"`.
641
+ - **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`.
642
+ - **useJsonTags**: Whether to emit `json:"fieldName"` struct tags. Defaults to `true`.
643
+ - [Examples](https://github.com/rroumenov/zod-to-x/blob/main/test/test_zod2go)
632
644
 
633
645
  ## Additional utils
634
646
  Additional useful tools to convert Zod Schemas into different formats.
@@ -255,6 +255,6 @@ class Zod2ProtoV3 extends core_1.Zod2X {
255
255
  */
256
256
  function zod2ProtoV3(schema, // TODO: fix any to force only ZodObjects
257
257
  opt = {}) {
258
- const astNode = new core_1.Zod2Ast({ strict: opt.strict }).build(schema);
258
+ const astNode = new core_1.Zod2Ast({ strict: opt.strict, skipBasicTypes: true }).build(schema);
259
259
  return new Zod2ProtoV3(opt).transpile(astNode);
260
260
  }
@@ -12,6 +12,13 @@ export interface IZod2AstOpt {
12
12
  * the schema.
13
13
  */
14
14
  layer?: IZod2xLayerMetadata;
15
+ /**
16
+ * If true, aliased basic types (string, number, boolean, etc.) from layered modeling
17
+ * will not be extracted as named AST nodes. Instead, they will be inlined as their
18
+ * underlying type. Useful for targets like Protobuf that don't support type aliases.
19
+ * Default is false.
20
+ */
21
+ skipBasicTypes?: boolean;
15
22
  }
16
23
  /**
17
24
  * This class creates AST nodes used to transpile Zod Schemas to other languages.
@@ -75,7 +82,7 @@ export declare class Zod2Ast {
75
82
  private _intersectAstNodes;
76
83
  /**
77
84
  * Merges multiple AST definitions into a single AST object containing combined properties.
78
- * - Equal properties mush have the same type and array dimension.
85
+ * - Equal properties must have the same type and array dimension.
79
86
  * - If a property is optional in one definition and required in another, it will be considered
80
87
  * optional in the merged object.
81
88
  * - If a property is nullable in one definition and non-nullable in another, it will be
@@ -151,6 +151,7 @@ class Zod2Ast {
151
151
  for (const schema of [left, right]) {
152
152
  const shape = schema.def.shape;
153
153
  for (const key in shape) {
154
+ const prevWasRequired = properties[key] !== undefined && !properties[key].isOptional;
154
155
  if (zod_helpers_1.ZodHelpers.isZodPromise(shape[key]) &&
155
156
  zod_helpers_1.ZodHelpers.isZod2XGeneric(shape[key])) {
156
157
  const templateKey = shape[key].unwrap().def.values[0];
@@ -159,13 +160,18 @@ class Zod2Ast {
159
160
  else {
160
161
  properties[key] = this._zodToAST(shape[key]);
161
162
  }
163
+ if (prevWasRequired && properties[key].isOptional) {
164
+ // In intersection, if a property is required in one schema and optional in
165
+ // another, it should be considered required.
166
+ properties[key].isOptional = false;
167
+ }
162
168
  }
163
169
  }
164
170
  return { properties };
165
171
  }
166
172
  /**
167
173
  * Merges multiple AST definitions into a single AST object containing combined properties.
168
- * - Equal properties mush have the same type and array dimension.
174
+ * - Equal properties must have the same type and array dimension.
169
175
  * - If a property is optional in one definition and required in another, it will be considered
170
176
  * optional in the merged object.
171
177
  * - If a property is nullable in one definition and non-nullable in another, it will be
@@ -178,42 +184,49 @@ class Zod2Ast {
178
184
  _unionAstNodes(options) {
179
185
  let typeA, typeB;
180
186
  const data = options.map((i) => this.nodes.get(i.name));
181
- return {
182
- properties: data.reduce((acc, i, j) => {
183
- var _a, _b;
184
- for (const key in i.properties) {
185
- if (acc[key]) {
186
- acc[key] = structuredClone(acc[key]);
187
- typeA = acc[key].constructor.name;
188
- typeB = i.properties[key].constructor.name;
189
- if (typeA !== typeB) {
190
- this.warnings.push(`Merging properties with different types: ${typeA} ` +
191
- `(from ${(_a = data[j - 1]) === null || _a === void 0 ? void 0 : _a.name}) and ${typeB} ` +
192
- `(from ${i.name})`);
193
- }
194
- if (acc[key].arrayDimension !== i.properties[key].arrayDimension) {
195
- this.warnings.push(`Merging properties with different array dimensions: ` +
196
- `${acc[key].arrayDimension} (from ${(_b = data[j - 1]) === null || _b === void 0 ? void 0 : _b.name}) and ` +
197
- `${i.properties[key].arrayDimension} (from ${i.name})`);
198
- acc[key].arrayDimension = Math.max(acc[key].arrayDimension || 0, i.properties[key].arrayDimension || 0);
199
- }
200
- if (acc[key].isNullable !== i.properties[key].isNullable) {
201
- acc[key].isNullable = true;
202
- }
203
- if (acc[key].isOptional !== i.properties[key].isOptional) {
204
- acc[key].isOptional = true;
205
- }
206
- if (i.properties[key].description) {
207
- acc[key].description = i.properties[key].description;
208
- }
187
+ const properties = data.reduce((acc, i, j) => {
188
+ for (const key in i.properties) {
189
+ if (acc[key]) {
190
+ acc[key] = Object.assign(Object.create(Object.getPrototypeOf(acc[key])), acc[key]);
191
+ typeA = acc[key].constructor.name;
192
+ typeB = i.properties[key].constructor.name;
193
+ if (typeA !== typeB) {
194
+ this.warnings.push(`Merging properties with different types: ${typeA} ` +
195
+ `(from a previous variant) and ${typeB} ` +
196
+ `(from ${i.name})`);
197
+ }
198
+ if (acc[key].arrayDimension !== i.properties[key].arrayDimension) {
199
+ this.warnings.push(`Merging properties with different array dimensions: ` +
200
+ `${acc[key].arrayDimension} (from a previous variant) and ` +
201
+ `${i.properties[key].arrayDimension} (from ${i.name})`);
202
+ acc[key].arrayDimension = Math.max(acc[key].arrayDimension || 0, i.properties[key].arrayDimension || 0);
209
203
  }
210
- else {
211
- acc[key] = i.properties[key];
204
+ if (acc[key].isNullable !== i.properties[key].isNullable) {
205
+ acc[key].isNullable = true;
206
+ }
207
+ if (acc[key].isOptional !== i.properties[key].isOptional) {
208
+ acc[key].isOptional = true;
209
+ }
210
+ if (i.properties[key].description) {
211
+ acc[key].description = i.properties[key].description;
212
212
  }
213
213
  }
214
- return acc;
215
- }, {}),
216
- };
214
+ else {
215
+ acc[key] = i.properties[key]; // New property, just add it
216
+ }
217
+ }
218
+ return acc;
219
+ }, {});
220
+ for (const key in properties) {
221
+ if (!data.every((variant) => key in variant.properties)) {
222
+ // In Union, if a property is not present in all variants, it should be considered
223
+ // optional. Shallow-clone preserving prototype before mutating, to avoid corrupting
224
+ // the original cached AST node.
225
+ properties[key] = Object.assign(Object.create(Object.getPrototypeOf(properties[key])), properties[key]);
226
+ properties[key].isOptional = true;
227
+ }
228
+ }
229
+ return { properties };
217
230
  }
218
231
  /**
219
232
  * Retrieves the name and associated transpilerable file information for a given Zod schema.
@@ -481,7 +494,7 @@ class Zod2Ast {
481
494
  }
482
495
  _getAliasAst(schema, item) {
483
496
  var _a;
484
- if (((_a = schema._zod2x) === null || _a === void 0 ? void 0 : _a.typeName) === undefined) {
497
+ if (((_a = schema._zod2x) === null || _a === void 0 ? void 0 : _a.typeName) === undefined || this.opt.skipBasicTypes === true) {
485
498
  return item;
486
499
  }
487
500
  const { name, parentFile, parentNamespace, aliasOf } = this._getNames(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": "2.2.0",
3
+ "version": "2.3.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",