zod-to-x 1.4.6-dev.2 → 1.4.6

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
@@ -25,10 +25,19 @@
25
25
  - [Installation](#installation)
26
26
  - [Quick start](#quick-start)
27
27
  - [Intersections and Unions](#intersections-and-unions)
28
+ - [Expected outputs](#expected-outputs)
29
+ - [Tips for discriminated unions](#tips-for-discriminated-unions)
28
30
  - [Layered modeling](#layered-modeling) <sup>*(new)*</sup>
29
- - [Supported output languages](#supported-output-languages)
31
+ - [Usage example](#usage-example)
32
+ - [Custom layers](#custom-layers)
33
+ - [Currently supported output languages](#currently-supported-output-languages)
34
+ - [Typescript](#1-typescript)
35
+ - [C++](#2-c)
30
36
  - [Additional utils](#additional-utils)
37
+ - [JSON Schema definitions](#1-zod2jsonschemadefinitions)
38
+ - [Protobuf V3 generation](#2-zod2protov3)
31
39
  - [Mapping of supported Zod Types by Language](#mapping-of-supported-zod-types-by-langauge)
40
+ - [Considerations](#considerations)
32
41
 
33
42
 
34
43
 
@@ -251,9 +260,11 @@ To improve Separation of Concerns (SoC), the Dependency Rule, and Maintainabilit
251
260
  To achieve this, new components are included:
252
261
  - **Zod2XModel**: With layered modeling, data is defined using classes. Inheriting this abstract class provides metadata management to handle relationships and also simplifies transpilation by including a `transpile()` method that receives the target language class.
253
262
  - **Layer**: A class decorator that defines class metadata, including a reference to the output file of the modeled data, a namespace under which its types are grouped, and an integer index representing the layer number. It can also be used as a decorator factory to define custom layers. Out of the box, four layers are provided: *Domain*, *Application*, *Infrastructure*, and *Presentation*. Parameters:
263
+ - **index**: Defines the layer boundary. In layer terms, a greater number represents a more external layer. Outer layers can use models from equal or lower layers, but not from higher layers. Otherwise, an error will be raised.
254
264
  - **namespace**: Defines the namespace under which the types are grouped.
255
265
  - **file**: Specifies the expected output file where the transpiled types will be saved.
256
266
  - **externalInheritance**: When a type from one layer is imported into another layer without modifications, it is transpiled as a new type inheriting from the imported type. This ensures type consistency across layers while maintaining reusability. See example (4) below. The default value is `true`.
267
+ - **basicTypes**: Since `v1.4.6`, primitive data types and arrays are also transpiled when using Layered modeling **if they are declared as property inside the layer class**. They are output as aliases of the types they represent. Setting it to `false` will disable this behavior (except for arrays, which are always transpiled). Default is `true`.
257
268
  - **Zod2XMixin**: A function that enables the creation of layers by extending multiple data models, thereby simplifying their definition and organization.
258
269
 
259
270
  ### Usage example
@@ -264,10 +275,12 @@ class UserModels extends Zod2XModel {
264
275
 
265
276
  userRole = z.enum(["Admin", "User"]).zod2x("UserRole"); // (*)
266
277
 
278
+ userEmail = z.string().email(); // This will be transpiled as an alias.
279
+
267
280
  userEntity = z.object({
268
281
  id: z.string().uuid(),
269
282
  name: z.string().min(1),
270
- email: z.string().email(),
283
+ email: this.userEmail,
271
284
  age: z.number().int().nonnegative().optional(),
272
285
  role: this.userRole,
273
286
  }); // (*)
@@ -281,10 +294,12 @@ console.log(userModels.transpile(Zod2XTranspilers.Zod2Ts));
281
294
  // User = "User",
282
295
  // }
283
296
 
297
+ // export type UserEmail = string;
298
+
284
299
  // export interface UserEntity {
285
300
  // id: string;
286
301
  // name: string;
287
- // email: string;
302
+ // email: UserEmail;
288
303
  // age?: number;
289
304
  // role: UserRole;
290
305
  // }
@@ -321,7 +336,7 @@ console.log(userDtos.transpile(Zod2XTranspilers.Zod2Ts))
321
336
 
322
337
  // export interface CreateUserUseCaseDto {
323
338
  // name: string;
324
- // email: string;
339
+ // email: USER.UserEmail;
325
340
  // age?: number;
326
341
  // role: USER.UserRole;
327
342
  // }
@@ -329,7 +344,7 @@ console.log(userDtos.transpile(Zod2XTranspilers.Zod2Ts))
329
344
  // export interface CreateUserUseCaseResultDto {
330
345
  // id: string;
331
346
  // name: string;
332
- // email: string;
347
+ // email: USER.UserEmail;
333
348
  // age?: number;
334
349
  // createdAt: Date;
335
350
  // updatedAt: Date;
@@ -461,8 +476,33 @@ class UserDtos extends Zod2XModel {
461
476
  // In this case, the type of `createUserUseCaseResultDto` is inferred from the parent model (`UserDtos`), but there is no explicit definition of the type itself.
462
477
  ```
463
478
 
479
+ ### Custom Layers
480
+ If the provided layer decorators do not meet your requirements, you can easily define custom ones:
481
+ ```ts
482
+ import { Layer, IZod2xLayerMetadata, Zod2XModel } from "zod-to-x";
483
+
484
+ // Create an enumerate with layer indexes
485
+ enum MyLayers {
486
+ MyDomainLayer = 0,
487
+ MyApplicationLayer = 1,
488
+ MyInfrastructureLayer = 2,
489
+ // ...
490
+ }
491
+
492
+ // Create custom layer decorators
493
+ function MyDomainLayer(opt: Omit<IZod2xLayerMetadata, "index">) {
494
+ return Layer({ ...opt, index: MyLayers.MyDomainLayer });
495
+ }
496
+ // ...
464
497
 
465
- ## Supported output languages
498
+ // Use the custom decorators
499
+ @MyDomainLayer({file: "...", namespace: "..."})
500
+ class MyEntityModels extends Zod2XModel {
501
+ // ...
502
+ }
503
+ ```
504
+
505
+ ## Currently supported output languages
466
506
  Common options:
467
507
  - **header**: Text to add as a comment at the beginning of the output.
468
508
  - **indent**: Number of spaces to use for indentation in the generated code. Defaults to 4 if not specified.
@@ -552,4 +592,60 @@ console.log(createUserDtoProtobuf); // Proto file of CreateUserUseCaseDto's mode
552
592
 
553
593
 
554
594
  ## Mapping of supported Zod Types by Langauge
555
- For a detailed mapping of supported Zod types across supported targets, please refer to the [SUPPORTED_ZOD_TYPES.md](https://github.com/rroumenov/zod-to-x/blob/main/SUPPORTED_ZOD_TYPES.md) file.
595
+ For a detailed mapping of supported Zod types across supported targets, please refer to the [SUPPORTED_ZOD_TYPES.md](https://github.com/rroumenov/zod-to-x/blob/main/SUPPORTED_ZOD_TYPES.md) file.
596
+
597
+
598
+
599
+ ## Considerations
600
+ - Choose the approach that best suits your needs, either Layered or non-Layered modeling, and avoid mixing them whenever possible, especially when working with enumerations, objects, unions, or intersections (except for `ZodLiteral.zod2x()`, which simply links a literal value to the enumeration it belongs to, if applicable). Their metadata handling differs slightly, which may result in some types not being transpiled as expected.
601
+
602
+ - In Layered modeling, the transpilation of primitive types can be completely disabled or combined by defining them outside the layer class:
603
+ ```ts
604
+ // External primitive
605
+ const stringUUID = z.string().uuid();
606
+
607
+ @Domain({ namespace: "USER", file: "user.entity" })
608
+ class UserModels extends Zod2XModel {
609
+
610
+ // Internal primitive
611
+ userEmail = z.string().email(); // This will be transpiled as an alias. It is a layer property.
612
+
613
+ userEntity = z.object({
614
+ id: stringUUID, // "stringUUID" will not be transpiled as an alias. It is not a layer property.
615
+ email: this.userEmail,
616
+ });
617
+ }
618
+
619
+ export const userModels = new UserModels();
620
+ ```
621
+
622
+ - Avoid internal alias redeclarations. If really needed, `ZodLazy` shall be used:
623
+ ```ts
624
+ // Consider the previous UserModels
625
+
626
+ import { z } from "zod";
627
+
628
+ @Application({ namespace: "USER_DTOS", file: "user.dtos", externalInheritance: false})
629
+ class UserDtos extends Zod2XModel {
630
+
631
+ // OK - Type declaration. It creates a new type based on userEntity but without ID.
632
+ createUserUseCaseDto = userModels.userEntity.omit({ id: true });
633
+
634
+ // OK - Redeclaration of external type. Could be useful for typing coherence.
635
+ // "createUserUseCaseResultDto" becomes an alias of "userModels.userEntity".
636
+ createUserUseCaseResultDto = userModels.userEntity;
637
+
638
+ // OK - Redeclaration of an internal type. Could be useful for typing coherence.
639
+ // "createUserUseCaseDtoV2" becomes an alias of "createUserUseCaseDto".
640
+ createUserUseCaseDtoV2 = this.createUserUseCaseDto;
641
+
642
+ // NOK - Redeclaration of an alias. It will be an alias of "userModels.userEntity"
643
+ // because "createUserUseCaseResultDto" is aliased during runtime.
644
+ createUserUseCaseResultDtoV2 = this.createUserUseCaseResultDto;
645
+
646
+ // OK, but avoid it - Redeclaration of an alias. It will wait until
647
+ // "createUserUseCaseResultDto" is aliased and then will becosa an alias
648
+ // of "createUserUseCaseResultDto"
649
+ createUserUseCaseResultDtoV3 = z.lazy(() => this.createUserUseCaseResultDto),
650
+ }
651
+ ```
@@ -149,6 +149,7 @@ export declare class Zod2Ast {
149
149
  * @returns The AST definition for the array schema.
150
150
  */
151
151
  private _getArrayAst;
152
+ private _getAliasAst;
152
153
  /**
153
154
  * Build the AST node of provided Zod Schema
154
155
  * @param schema
@@ -410,6 +410,21 @@ class Zod2Ast {
410
410
  }
411
411
  return this._createDefinition(item);
412
412
  }
413
+ _getAliasAst(schema, item) {
414
+ var _a;
415
+ if (((_a = schema._zod2x) === null || _a === void 0 ? void 0 : _a.typeName) === undefined) {
416
+ return item;
417
+ }
418
+ const { name, parentFile, parentNamespace, aliasOf } = this._getNames(schema);
419
+ item.name = name;
420
+ item.parentFile = parentFile;
421
+ item.parentNamespace = parentNamespace;
422
+ item.aliasOf = aliasOf;
423
+ if (!this.nodes.has(name)) {
424
+ this.nodes.set(name, item);
425
+ }
426
+ return this._createDefinition(item);
427
+ }
413
428
  /**
414
429
  * Build the AST node of provided Zod Schema
415
430
  * @param schema
@@ -419,22 +434,22 @@ class Zod2Ast {
419
434
  var _a, _b, _c, _d, _e;
420
435
  const def = schema._def;
421
436
  if (zod_helpers_1.ZodHelpers.isZodString(schema)) {
422
- return new core_1.ASTString({ description: schema.description });
437
+ return this._getAliasAst(schema, new core_1.ASTString({ description: schema.description }));
423
438
  }
424
439
  else if (zod_helpers_1.ZodHelpers.isZodAnyNumberType(schema)) {
425
- return new core_1.ASTNumber({
440
+ return this._getAliasAst(schema, new core_1.ASTNumber({
426
441
  description: schema.description,
427
442
  constraints: zod_helpers_1.ZodHelpers.getZodNumberConstraints(schema),
428
- });
443
+ }));
429
444
  }
430
445
  else if (zod_helpers_1.ZodHelpers.isZodBoolean(schema)) {
431
- return new core_1.ASTBoolean({ description: schema.description });
446
+ return this._getAliasAst(schema, new core_1.ASTBoolean({ description: schema.description }));
432
447
  }
433
448
  else if (zod_helpers_1.ZodHelpers.isZodDate(schema)) {
434
- return new core_1.ASTDate({ description: schema.description });
449
+ return this._getAliasAst(schema, new core_1.ASTDate({ description: schema.description }));
435
450
  }
436
451
  else if (zod_helpers_1.ZodHelpers.isZodAny(schema)) {
437
- return new core_1.ASTAny({ description: schema.description });
452
+ return this._getAliasAst(schema, new core_1.ASTAny({ description: schema.description }));
438
453
  }
439
454
  else if (zod_helpers_1.ZodHelpers.isZodNullable(schema)) {
440
455
  const subSchema = this._zodToAST(schema.unwrap());
@@ -468,10 +483,10 @@ class Zod2Ast {
468
483
  }
469
484
  }
470
485
  else if (zod_helpers_1.ZodHelpers.isZodSet(schema)) {
471
- return new core_1.ASTSet({
486
+ return this._getAliasAst(schema, new core_1.ASTSet({
472
487
  value: this._zodToAST(def.valueType),
473
488
  description: schema.description,
474
- });
489
+ }));
475
490
  }
476
491
  else if (zod_helpers_1.ZodHelpers.isZodLiteral(schema)) {
477
492
  let parentEnum = undefined;
@@ -490,12 +505,12 @@ class Zod2Ast {
490
505
  });
491
506
  }
492
507
  else if (zod_helpers_1.ZodHelpers.isZodAnyMapType(schema)) {
493
- return new core_1.ASTMap({
508
+ return this._getAliasAst(schema, new core_1.ASTMap({
494
509
  type: zod_helpers_1.ZodHelpers.isZodRecord(schema) ? "record" : "map",
495
510
  key: this._zodToAST(def.keyType),
496
511
  value: this._zodToAST(def.valueType),
497
512
  description: schema.description,
498
- });
513
+ }));
499
514
  }
500
515
  else if (zod_helpers_1.ZodHelpers.isZodLazy(schema)) {
501
516
  /** Lazy items use to be recursive schemas of its own, so the are trated as another
@@ -506,10 +521,10 @@ class Zod2Ast {
506
521
  return lazyPointer;
507
522
  }
508
523
  else if (zod_helpers_1.ZodHelpers.isZodTuple(schema)) {
509
- return new core_1.ASTTuple({
524
+ return this._getAliasAst(schema, new core_1.ASTTuple({
510
525
  items: def.items.map(this._zodToAST.bind(this)),
511
526
  description: schema.description,
512
- });
527
+ }));
513
528
  /**
514
529
  *
515
530
  *
@@ -176,6 +176,12 @@ export declare abstract class Zod2X<T extends IZodToXOpt> {
176
176
  * @returns `true` if the type is transpilerable; otherwise, `false`.
177
177
  */
178
178
  protected isTranspilerable(token: ASTNode): boolean;
179
+ /**
180
+ * Determines if the given AST node represents an aliased type. *
181
+ * @param token - The AST node to evaluate.
182
+ * @returns `true` if the token is an instance of an aliased type, otherwise `false`.
183
+ */
184
+ protected isAliasedType(token: ASTNode): boolean;
179
185
  protected push0: (data: string) => number;
180
186
  protected push1: (data: string) => number;
181
187
  protected push2: (data: string) => number;
@@ -36,6 +36,23 @@ class Zod2X {
36
36
  token instanceof core_1.ASTUnion ||
37
37
  token instanceof core_1.ASTIntersection);
38
38
  }
39
+ /**
40
+ * Determines if the given AST node represents an aliased type. *
41
+ * @param token - The AST node to evaluate.
42
+ * @returns `true` if the token is an instance of an aliased type, otherwise `false`.
43
+ */
44
+ isAliasedType(token) {
45
+ return (token instanceof core_1.ASTString ||
46
+ token instanceof core_1.ASTNumber ||
47
+ token instanceof core_1.ASTBoolean ||
48
+ token instanceof core_1.ASTLiteral ||
49
+ token instanceof core_1.ASTDate ||
50
+ token instanceof core_1.ASTAny ||
51
+ token instanceof core_1.ASTMap ||
52
+ token instanceof core_1.ASTSet ||
53
+ token instanceof core_1.ASTTuple ||
54
+ token instanceof core_1.ASTArray);
55
+ }
39
56
  /**
40
57
  * Adds a comment to the transpiled output.
41
58
  * @param data - The comment text to add.
@@ -163,7 +180,7 @@ class Zod2X {
163
180
  else if (item instanceof core_1.ASTIntersection) {
164
181
  this.transpileIntersection(item);
165
182
  }
166
- else if (item instanceof core_1.ASTArray) {
183
+ else if (this.isAliasedType(item)) {
167
184
  this.transpileAliasedType(item);
168
185
  }
169
186
  else if (item instanceof core_1.ASTCommon) {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { extendZod } from "./lib/zod_ext";
1
+ export { extendZod, IZod2xLayerMetadata } from "./lib/zod_ext";
2
2
  export * as Zod2XTypes from "./core/ast-types";
3
3
  export { Zod2Ast } from "./core/ast_node";
4
4
  export { Zod2X } from "./core/transpiler";
@@ -104,7 +104,8 @@ function Layer(opt) {
104
104
  };
105
105
  Object.getOwnPropertyNames(this).forEach((prop) => {
106
106
  const item = this[prop];
107
- if (zod_helpers_1.ZodHelpers.isTranspilerableZodType(item) || zod_helpers_1.ZodHelpers.isZodArray(item)) {
107
+ if (zod_helpers_1.ZodHelpers.isTranspilerableZodType(item) ||
108
+ zod_helpers_1.ZodHelpers.isTranspilerableAliasedZodType(item, opt.basicTypes === false)) {
108
109
  this[prop] = setMetadata(case_1.default.pascal(prop), item, opt);
109
110
  }
110
111
  });
@@ -54,6 +54,12 @@ export interface IZod2xLayerMetadata {
54
54
  * }
55
55
  */
56
56
  externalInheritance?: boolean;
57
+ /**
58
+ * Indicates if basic types (string, boolean, number, ...) shall be transpiled as aliases.
59
+ * If set to false, only complex types are transpiled (enum, object, array, union,
60
+ * intersection). Default is true.
61
+ */
62
+ basicTypes?: boolean;
57
63
  }
58
64
  export interface IZod2xMetadata {
59
65
  /**
@@ -35,7 +35,21 @@ export declare class ZodHelpers {
35
35
  static isZodAnyEnumType(i: ZodTypeAny): i is z.ZodEnum<any> | z.ZodNativeEnum<any>;
36
36
  static isZodAnyNumberType(i: ZodTypeAny): i is z.ZodNumber | z.ZodBigInt;
37
37
  static isZodAnyMapType(i: ZodTypeAny): i is z.ZodRecord<any, any> | z.ZodMap<any, any>;
38
+ /**
39
+ * Complex types that shall always be transpiled, which output would be a type, or alias if
40
+ * redefined using layered modeling.
41
+ * @param zodType
42
+ * @returns
43
+ */
38
44
  static isTranspilerableZodType(zodType: string | ZodTypeAny): boolean;
45
+ /**
46
+ * Primitive types that can only be transpiled if defined using layered modeling, which output
47
+ * would be a type alias.
48
+ * @param zodType
49
+ * @param onlyArray Array types are always transpiled as alias in layered modeling.
50
+ * @returns
51
+ */
52
+ static isTranspilerableAliasedZodType(zodType: string | ZodTypeAny, onlyArray?: boolean): boolean;
39
53
  static cloneZod(i: ZodTypeAny): any;
40
54
  static createZodObject(properties: Map<string, ZodTypeAny>): ZodObject<any>;
41
55
  static getZodNumberConstraints(i: ZodNumber | z.ZodBigInt): ZodNumberConstraints;
@@ -84,6 +84,12 @@ class ZodHelpers {
84
84
  static isZodAnyMapType(i) {
85
85
  return this.isZodMap(i) || this.isZodRecord(i);
86
86
  }
87
+ /**
88
+ * Complex types that shall always be transpiled, which output would be a type, or alias if
89
+ * redefined using layered modeling.
90
+ * @param zodType
91
+ * @returns
92
+ */
87
93
  static isTranspilerableZodType(zodType) {
88
94
  var _a;
89
95
  const type = typeof zodType === "string" ? zodType : (_a = zodType === null || zodType === void 0 ? void 0 : zodType._def) === null || _a === void 0 ? void 0 : _a.typeName;
@@ -94,6 +100,31 @@ class ZodHelpers {
94
100
  type === zod_1.ZodFirstPartyTypeKind.ZodDiscriminatedUnion ||
95
101
  type === zod_1.ZodFirstPartyTypeKind.ZodIntersection);
96
102
  }
103
+ /**
104
+ * Primitive types that can only be transpiled if defined using layered modeling, which output
105
+ * would be a type alias.
106
+ * @param zodType
107
+ * @param onlyArray Array types are always transpiled as alias in layered modeling.
108
+ * @returns
109
+ */
110
+ static isTranspilerableAliasedZodType(zodType, onlyArray = false) {
111
+ var _a;
112
+ const type = typeof zodType === "string" ? zodType : (_a = zodType === null || zodType === void 0 ? void 0 : zodType._def) === null || _a === void 0 ? void 0 : _a.typeName;
113
+ if (onlyArray === true) {
114
+ return type === zod_1.ZodFirstPartyTypeKind.ZodArray;
115
+ }
116
+ return (type === zod_1.ZodFirstPartyTypeKind.ZodString ||
117
+ type === zod_1.ZodFirstPartyTypeKind.ZodNumber ||
118
+ type === zod_1.ZodFirstPartyTypeKind.ZodBigInt ||
119
+ type === zod_1.ZodFirstPartyTypeKind.ZodBoolean ||
120
+ type === zod_1.ZodFirstPartyTypeKind.ZodDate ||
121
+ type === zod_1.ZodFirstPartyTypeKind.ZodAny ||
122
+ type === zod_1.ZodFirstPartyTypeKind.ZodMap ||
123
+ type === zod_1.ZodFirstPartyTypeKind.ZodSet ||
124
+ type === zod_1.ZodFirstPartyTypeKind.ZodRecord ||
125
+ type === zod_1.ZodFirstPartyTypeKind.ZodTuple ||
126
+ type === zod_1.ZodFirstPartyTypeKind.ZodArray);
127
+ }
97
128
  static cloneZod(i) {
98
129
  const zodType = i._def.typeName;
99
130
  return new zod_1.z[zodType](Object.assign({}, i._def));
@@ -171,6 +171,9 @@ class Zod2Cpp extends core_1.Zod2X {
171
171
  if (data instanceof core_1.ASTArray) {
172
172
  extendedType = this.getAttributeType(data.item);
173
173
  }
174
+ else {
175
+ extendedType = this.getAttributeType(data);
176
+ }
174
177
  if (extendedType !== undefined) {
175
178
  this.push0(`using ${data.name} = ${extendedType};\n`);
176
179
  }
@@ -96,6 +96,9 @@ class Zod2Ts extends core_1.Zod2X {
96
96
  if (data instanceof core_1.ASTArray) {
97
97
  extendedType = this.getAttributeType(data.item);
98
98
  }
99
+ else {
100
+ extendedType = this.getAttributeType(data);
101
+ }
99
102
  if (extendedType !== undefined) {
100
103
  this.push0(`export type ${data.name} = ${extendedType};\n`);
101
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod-to-x",
3
- "version": "1.4.6-dev.2",
3
+ "version": "1.4.6",
4
4
  "description": "Multi language types generation from Zod schemas.",
5
5
  "main": "dist/index.js",
6
6
  "files": [