@gabrielbryk/json-schema-to-zod 2.10.1 → 2.11.1

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.
Files changed (138) hide show
  1. package/AGENTS.md +44 -0
  2. package/CHANGELOG.md +38 -0
  3. package/README.md +6 -33
  4. package/check-types-lift.sh +23 -0
  5. package/check-types.sh +20 -0
  6. package/dist/{esm/cli.js → cli.js} +0 -6
  7. package/dist/{esm/core → core}/analyzeSchema.js +4 -5
  8. package/dist/core/emitZod.js +263 -0
  9. package/dist/{esm/generators → generators}/generateBundle.js +26 -13
  10. package/dist/{esm/index.js → index.js} +6 -0
  11. package/dist/jsonSchemaToZod.js +17 -0
  12. package/dist/parsers/parseAllOf.js +125 -0
  13. package/dist/parsers/parseAnyOf.js +28 -0
  14. package/dist/{esm/parsers → parsers}/parseArray.js +27 -11
  15. package/dist/parsers/parseBoolean.js +4 -0
  16. package/dist/parsers/parseConst.js +22 -0
  17. package/dist/parsers/parseEnum.js +35 -0
  18. package/dist/{esm/parsers → parsers}/parseIfThenElse.js +10 -6
  19. package/dist/parsers/parseMultipleType.js +10 -0
  20. package/dist/parsers/parseNot.js +14 -0
  21. package/dist/parsers/parseNull.js +4 -0
  22. package/dist/parsers/parseNullable.js +12 -0
  23. package/dist/{esm/parsers → parsers}/parseNumber.js +4 -1
  24. package/dist/{esm/parsers → parsers}/parseObject.js +200 -37
  25. package/dist/parsers/parseOneOf.js +365 -0
  26. package/dist/{esm/parsers → parsers}/parseSchema.js +55 -117
  27. package/dist/parsers/parseSimpleDiscriminatedOneOf.js +24 -0
  28. package/dist/{esm/parsers → parsers}/parseString.js +29 -18
  29. package/dist/types/Types.d.ts +32 -4
  30. package/dist/types/core/analyzeSchema.d.ts +3 -2
  31. package/dist/types/generators/generateBundle.d.ts +0 -2
  32. package/dist/types/index.d.ts +6 -0
  33. package/dist/types/parsers/parseAllOf.d.ts +2 -2
  34. package/dist/types/parsers/parseAnyOf.d.ts +2 -2
  35. package/dist/types/parsers/parseArray.d.ts +2 -2
  36. package/dist/types/parsers/parseBoolean.d.ts +2 -1
  37. package/dist/types/parsers/parseConst.d.ts +2 -2
  38. package/dist/types/parsers/parseDefault.d.ts +2 -2
  39. package/dist/types/parsers/parseEnum.d.ts +2 -2
  40. package/dist/types/parsers/parseIfThenElse.d.ts +2 -2
  41. package/dist/types/parsers/parseMultipleType.d.ts +2 -2
  42. package/dist/types/parsers/parseNot.d.ts +2 -2
  43. package/dist/types/parsers/parseNull.d.ts +2 -1
  44. package/dist/types/parsers/parseNullable.d.ts +2 -2
  45. package/dist/types/parsers/parseNumber.d.ts +2 -2
  46. package/dist/types/parsers/parseObject.d.ts +2 -2
  47. package/dist/types/parsers/parseOneOf.d.ts +2 -2
  48. package/dist/types/parsers/parseSchema.d.ts +2 -2
  49. package/dist/types/parsers/parseSimpleDiscriminatedOneOf.d.ts +2 -2
  50. package/dist/types/parsers/parseString.d.ts +2 -2
  51. package/dist/types/utils/anyOrUnknown.d.ts +5 -4
  52. package/dist/types/utils/esmEmitter.d.ts +29 -0
  53. package/dist/types/utils/extractInlineObject.d.ts +15 -0
  54. package/dist/types/utils/liftInlineObjects.d.ts +21 -0
  55. package/dist/types/utils/namingService.d.ts +21 -0
  56. package/dist/types/utils/resolveRef.d.ts +7 -0
  57. package/dist/types/utils/schemaRepresentation.d.ts +71 -0
  58. package/dist/utils/anyOrUnknown.js +13 -0
  59. package/dist/{esm/utils → utils}/buildRefRegistry.js +4 -0
  60. package/dist/utils/esmEmitter.js +87 -0
  61. package/dist/utils/extractInlineObject.js +119 -0
  62. package/dist/utils/liftInlineObjects.js +476 -0
  63. package/dist/utils/namingService.js +58 -0
  64. package/dist/utils/resolveRef.js +92 -0
  65. package/dist/utils/schemaRepresentation.js +569 -0
  66. package/docs/IMPROVEMENT-PLAN.md +243 -0
  67. package/docs/ZOD-V4-RECURSIVE-TYPE-LIMITATIONS.md +292 -0
  68. package/docs/proposals/bundle-refactor.md +1 -1
  69. package/docs/proposals/discriminated-union-with-default.md +248 -0
  70. package/docs/proposals/inline-object-lifting.md +77 -0
  71. package/eslint.config.js +4 -2
  72. package/jest.config.mjs +19 -0
  73. package/package.json +17 -20
  74. package/scripts/generateWorkflowSchema.ts +0 -1
  75. package/dist/cjs/Types.js +0 -2
  76. package/dist/cjs/cli.js +0 -70
  77. package/dist/cjs/core/analyzeSchema.js +0 -62
  78. package/dist/cjs/core/emitZod.js +0 -157
  79. package/dist/cjs/generators/generateBundle.js +0 -510
  80. package/dist/cjs/index.js +0 -50
  81. package/dist/cjs/jsonSchemaToZod.js +0 -10
  82. package/dist/cjs/package.json +0 -1
  83. package/dist/cjs/parsers/parseAllOf.js +0 -46
  84. package/dist/cjs/parsers/parseAnyOf.js +0 -18
  85. package/dist/cjs/parsers/parseArray.js +0 -90
  86. package/dist/cjs/parsers/parseBoolean.js +0 -5
  87. package/dist/cjs/parsers/parseConst.js +0 -7
  88. package/dist/cjs/parsers/parseDefault.js +0 -8
  89. package/dist/cjs/parsers/parseEnum.js +0 -21
  90. package/dist/cjs/parsers/parseIfThenElse.js +0 -35
  91. package/dist/cjs/parsers/parseMultipleType.js +0 -10
  92. package/dist/cjs/parsers/parseNot.js +0 -12
  93. package/dist/cjs/parsers/parseNull.js +0 -5
  94. package/dist/cjs/parsers/parseNullable.js +0 -12
  95. package/dist/cjs/parsers/parseNumber.js +0 -116
  96. package/dist/cjs/parsers/parseObject.js +0 -318
  97. package/dist/cjs/parsers/parseOneOf.js +0 -53
  98. package/dist/cjs/parsers/parseSchema.js +0 -419
  99. package/dist/cjs/parsers/parseSimpleDiscriminatedOneOf.js +0 -21
  100. package/dist/cjs/parsers/parseString.js +0 -317
  101. package/dist/cjs/utils/anyOrUnknown.js +0 -14
  102. package/dist/cjs/utils/buildRefRegistry.js +0 -56
  103. package/dist/cjs/utils/cliTools.js +0 -108
  104. package/dist/cjs/utils/cycles.js +0 -113
  105. package/dist/cjs/utils/half.js +0 -7
  106. package/dist/cjs/utils/jsdocs.js +0 -20
  107. package/dist/cjs/utils/omit.js +0 -11
  108. package/dist/cjs/utils/resolveUri.js +0 -16
  109. package/dist/cjs/utils/withMessage.js +0 -21
  110. package/dist/cjs/zodToJsonSchema.js +0 -89
  111. package/dist/esm/core/emitZod.js +0 -153
  112. package/dist/esm/jsonSchemaToZod.js +0 -6
  113. package/dist/esm/package.json +0 -1
  114. package/dist/esm/parsers/parseAllOf.js +0 -43
  115. package/dist/esm/parsers/parseAnyOf.js +0 -14
  116. package/dist/esm/parsers/parseBoolean.js +0 -1
  117. package/dist/esm/parsers/parseConst.js +0 -3
  118. package/dist/esm/parsers/parseEnum.js +0 -17
  119. package/dist/esm/parsers/parseMultipleType.js +0 -6
  120. package/dist/esm/parsers/parseNot.js +0 -8
  121. package/dist/esm/parsers/parseNull.js +0 -1
  122. package/dist/esm/parsers/parseNullable.js +0 -8
  123. package/dist/esm/parsers/parseOneOf.js +0 -49
  124. package/dist/esm/parsers/parseSimpleDiscriminatedOneOf.js +0 -17
  125. package/dist/esm/utils/anyOrUnknown.js +0 -10
  126. package/jest.config.cjs +0 -4
  127. package/postcjs.cjs +0 -1
  128. package/postesm.cjs +0 -1
  129. /package/dist/{esm/Types.js → Types.js} +0 -0
  130. /package/dist/{esm/parsers → parsers}/parseDefault.js +0 -0
  131. /package/dist/{esm/utils → utils}/cliTools.js +0 -0
  132. /package/dist/{esm/utils → utils}/cycles.js +0 -0
  133. /package/dist/{esm/utils → utils}/half.js +0 -0
  134. /package/dist/{esm/utils → utils}/jsdocs.js +0 -0
  135. /package/dist/{esm/utils → utils}/omit.js +0 -0
  136. /package/dist/{esm/utils → utils}/resolveUri.js +0 -0
  137. /package/dist/{esm/utils → utils}/withMessage.js +0 -0
  138. /package/dist/{esm/zodToJsonSchema.js → zodToJsonSchema.js} +0 -0
@@ -3,24 +3,26 @@ import { parseSchema } from "./parseSchema.js";
3
3
  export const parseString = (schema, refs) => {
4
4
  const formatError = schema.errorMessage?.format;
5
5
  const refContext = ensureRefs(refs);
6
+ // Map formats to top-level Zod functions and their return types
6
7
  const topLevelFormatMap = {
7
- email: "z.email",
8
- ipv4: "z.ipv4",
9
- ipv6: "z.ipv6",
10
- uri: "z.url",
11
- uuid: "z.uuid",
12
- cuid: "z.cuid",
13
- cuid2: "z.cuid2",
14
- nanoid: "z.nanoid",
15
- ulid: "z.ulid",
16
- jwt: "z.jwt",
17
- e164: "z.e164",
18
- base64url: "z.base64url",
19
- base64: "z.base64",
20
- emoji: "z.emoji",
21
- "idn-email": "z.email",
8
+ email: { fn: "z.email", zodType: "z.ZodEmail" },
9
+ ipv4: { fn: "z.ipv4", zodType: "z.ZodIPv4" },
10
+ ipv6: { fn: "z.ipv6", zodType: "z.ZodIPv6" },
11
+ uri: { fn: "z.url", zodType: "z.ZodURL" },
12
+ uuid: { fn: "z.uuid", zodType: "z.ZodUUID" },
13
+ cuid: { fn: "z.cuid", zodType: "z.ZodCUID" },
14
+ cuid2: { fn: "z.cuid2", zodType: "z.ZodCUID2" },
15
+ nanoid: { fn: "z.nanoid", zodType: "z.ZodNanoID" },
16
+ ulid: { fn: "z.ulid", zodType: "z.ZodULID" },
17
+ jwt: { fn: "z.jwt", zodType: "z.ZodJWT" },
18
+ e164: { fn: "z.e164", zodType: "z.ZodE164" },
19
+ base64url: { fn: "z.base64url", zodType: "z.ZodBase64URL" },
20
+ base64: { fn: "z.base64", zodType: "z.ZodBase64" },
21
+ emoji: { fn: "z.emoji", zodType: "z.ZodEmoji" },
22
+ "idn-email": { fn: "z.email", zodType: "z.ZodEmail" },
22
23
  };
23
- const formatFn = schema.format && topLevelFormatMap[schema.format];
24
+ const formatInfo = schema.format ? topLevelFormatMap[schema.format] : undefined;
25
+ const formatFn = formatInfo?.fn;
24
26
  const formatParam = formatError !== undefined ? `{ error: ${JSON.stringify(formatError)} }` : "";
25
27
  let r = formatFn ? `${formatFn}(${formatParam})` : "z.string()";
26
28
  const formatHandled = Boolean(formatFn);
@@ -287,8 +289,12 @@ export const parseString = (schema, refs) => {
287
289
  r += contentMediaType;
288
290
  r += withMessage(schema, "contentSchema", ({ value }) => {
289
291
  if (value && typeof value === "object") {
292
+ const parsedContent = parseSchema(value, refContext);
293
+ const contentExpr = typeof parsedContent === "string"
294
+ ? parsedContent
295
+ : parsedContent.expression;
290
296
  return {
291
- opener: `.pipe(${parseSchema(value, refContext)}`,
297
+ opener: `.pipe(${contentExpr}`,
292
298
  closer: ")",
293
299
  messagePrefix: ", { error: ",
294
300
  messageCloser: " })",
@@ -296,7 +302,12 @@ export const parseString = (schema, refs) => {
296
302
  }
297
303
  });
298
304
  }
299
- return r;
305
+ // Use the correct Zod type based on whether a format function was used
306
+ const zodType = formatInfo?.zodType ?? "z.ZodString";
307
+ return {
308
+ expression: r,
309
+ type: zodType,
310
+ };
300
311
  };
301
312
  function ensureRefs(refs) {
302
313
  if (refs)
@@ -1,6 +1,16 @@
1
1
  export type Serializable = {
2
2
  [key: string]: Serializable;
3
3
  } | Serializable[] | string | number | boolean | null;
4
+ /**
5
+ * Dual representation of a Zod schema - tracks both the runtime expression
6
+ * and its TypeScript type annotation for proper recursive schema typing.
7
+ */
8
+ export interface SchemaRepresentation {
9
+ /** The Zod runtime expression, e.g., "z.array(MySchema).optional()" */
10
+ expression: string;
11
+ /** The Zod TypeScript type, e.g., "z.ZodOptional<z.ZodArray<typeof MySchema>>" */
12
+ type: string;
13
+ }
4
14
  export type JsonSchema = JsonSchemaObject | boolean;
5
15
  export type JsonSchemaObject = {
6
16
  type?: string | string[];
@@ -59,11 +69,10 @@ export type JsonSchemaObject = {
59
69
  [key: string]: string | undefined;
60
70
  };
61
71
  } & Record<string, unknown>;
62
- export type ParserSelector = (schema: JsonSchemaObject, refs: Refs) => string;
72
+ export type ParserSelector = (schema: JsonSchemaObject, refs: Refs) => SchemaRepresentation;
63
73
  export type ParserOverride = (schema: JsonSchemaObject, refs: Refs) => string | void;
64
74
  export type Options = {
65
75
  name?: string;
66
- module?: "cjs" | "esm" | "none";
67
76
  withoutDefaults?: boolean;
68
77
  withoutDescribes?: boolean;
69
78
  withJsdocs?: boolean;
@@ -112,15 +121,34 @@ export type Options = {
112
121
  * Return a JsonSchema to register, or undefined if not found.
113
122
  */
114
123
  resolveExternalRef?: (uri: string) => JsonSchema | Promise<JsonSchema> | undefined;
124
+ /**
125
+ * Lift inline object schemas into top-level defs to improve reusability.
126
+ * Default is ON; set enable: false to opt out.
127
+ */
128
+ liftInlineObjects?: {
129
+ /** Whether to enable lifting inline object schemas (default: true). */
130
+ enable?: boolean;
131
+ /** Optional hook to override generated names for lifted defs. */
132
+ nameForPath?: (path: (string | number)[], ctx: {
133
+ parentName?: string;
134
+ existingNames: Set<string>;
135
+ branchInfo?: unknown;
136
+ }) => string;
137
+ /** Deduplicate lifted shapes by structural hash (ignoring titles/descriptions). Default: false. */
138
+ dedup?: boolean;
139
+ /** Allow hoisting inside $defs content (default: true). */
140
+ allowInDefs?: boolean;
141
+ };
115
142
  };
116
143
  export type Refs = Options & {
117
144
  path: (string | number)[];
118
145
  seen: Map<object | boolean, {
119
146
  n: number;
120
- r: string | undefined;
147
+ r: SchemaRepresentation | undefined;
121
148
  }>;
122
149
  root?: JsonSchema;
123
- declarations?: Map<string, string>;
150
+ /** Stores schema declarations with both expression and type */
151
+ declarations?: Map<string, SchemaRepresentation>;
124
152
  dependencies?: Map<string, Set<string>>;
125
153
  inProgress?: Set<string>;
126
154
  refNameByPointer?: Map<string, string>;
@@ -1,14 +1,15 @@
1
- import { Options, JsonSchema } from "../Types.js";
1
+ import { Options, JsonSchema, SchemaRepresentation } from "../Types.js";
2
2
  export type NormalizedOptions = Options & {
3
3
  exportRefs: boolean;
4
4
  withMeta: boolean;
5
+ module: "esm";
5
6
  };
6
7
  export type AnalysisResult = {
7
8
  schema: JsonSchema;
8
9
  options: NormalizedOptions;
9
10
  refNameByPointer: Map<string, string>;
10
11
  usedNames: Set<string>;
11
- declarations: Map<string, string>;
12
+ declarations: Map<string, SchemaRepresentation>;
12
13
  dependencies: Map<string, Set<string>>;
13
14
  cycleRefNames: Set<string>;
14
15
  cycleComponentByName: Map<string, number>;
@@ -48,8 +48,6 @@ export type GenerateBundleOptions = Options & {
48
48
  splitDefs?: SplitDefsOptions;
49
49
  refResolution?: RefResolutionOptions;
50
50
  nestedTypes?: NestedTypesOptions;
51
- /** Force module kind for generated files (defaults to esm) */
52
- module?: "esm" | "cjs" | "none";
53
51
  };
54
52
  export type GeneratedFile = {
55
53
  fileName: string;
@@ -24,10 +24,16 @@ export * from "./parsers/parseString.js";
24
24
  export * from "./utils/anyOrUnknown.js";
25
25
  export * from "./utils/buildRefRegistry.js";
26
26
  export * from "./utils/cycles.js";
27
+ export * from "./utils/esmEmitter.js";
28
+ export * from "./utils/extractInlineObject.js";
27
29
  export * from "./utils/half.js";
28
30
  export * from "./utils/jsdocs.js";
31
+ export * from "./utils/liftInlineObjects.js";
32
+ export * from "./utils/namingService.js";
29
33
  export * from "./utils/omit.js";
34
+ export * from "./utils/resolveRef.js";
30
35
  export * from "./utils/resolveUri.js";
36
+ export * from "./utils/schemaRepresentation.js";
31
37
  export * from "./utils/withMessage.js";
32
38
  export * from "./zodToJsonSchema.js";
33
39
  import { jsonSchemaToZod } from "./jsonSchemaToZod.js";
@@ -1,4 +1,4 @@
1
- import { JsonSchemaObject, JsonSchema, Refs } from "../Types.js";
1
+ import { JsonSchemaObject, JsonSchema, Refs, SchemaRepresentation } from "../Types.js";
2
2
  export declare function parseAllOf(schema: JsonSchemaObject & {
3
3
  allOf: JsonSchema[];
4
- }, refs: Refs): string;
4
+ }, refs: Refs): SchemaRepresentation;
@@ -1,4 +1,4 @@
1
- import { JsonSchemaObject, JsonSchema, Refs } from "../Types.js";
1
+ import { JsonSchemaObject, JsonSchema, Refs, SchemaRepresentation } from "../Types.js";
2
2
  export declare const parseAnyOf: (schema: JsonSchemaObject & {
3
3
  anyOf: JsonSchema[];
4
- }, refs: Refs) => string;
4
+ }, refs: Refs) => SchemaRepresentation;
@@ -1,4 +1,4 @@
1
- import { JsonSchemaObject, Refs } from "../Types.js";
1
+ import { JsonSchemaObject, Refs, SchemaRepresentation } from "../Types.js";
2
2
  export declare const parseArray: (schema: JsonSchemaObject & {
3
3
  type: "array";
4
- }, refs: Refs) => string;
4
+ }, refs: Refs) => SchemaRepresentation;
@@ -1 +1,2 @@
1
- export declare const parseBoolean: () => string;
1
+ import { SchemaRepresentation } from "../Types.js";
2
+ export declare const parseBoolean: () => SchemaRepresentation;
@@ -1,4 +1,4 @@
1
- import { JsonSchemaObject, Serializable } from "../Types.js";
1
+ import { JsonSchemaObject, SchemaRepresentation, Serializable } from "../Types.js";
2
2
  export declare const parseConst: (schema: JsonSchemaObject & {
3
3
  const: Serializable;
4
- }) => string;
4
+ }) => SchemaRepresentation;
@@ -1,2 +1,2 @@
1
- import { JsonSchemaObject, Refs } from "../Types.js";
2
- export declare const parseDefault: (_schema: JsonSchemaObject, refs?: Refs) => string;
1
+ import { JsonSchemaObject, Refs, SchemaRepresentation } from "../Types.js";
2
+ export declare const parseDefault: (_schema: JsonSchemaObject, refs?: Refs) => SchemaRepresentation;
@@ -1,4 +1,4 @@
1
- import { JsonSchemaObject, Serializable } from "../Types.js";
1
+ import { JsonSchemaObject, SchemaRepresentation, Serializable } from "../Types.js";
2
2
  export declare const parseEnum: (schema: JsonSchemaObject & {
3
3
  enum: Serializable[];
4
- }) => string;
4
+ }) => SchemaRepresentation;
@@ -1,6 +1,6 @@
1
- import { JsonSchemaObject, JsonSchema, Refs } from "../Types.js";
1
+ import { JsonSchemaObject, JsonSchema, Refs, SchemaRepresentation } from "../Types.js";
2
2
  export declare const parseIfThenElse: (schema: JsonSchemaObject & {
3
3
  if: JsonSchema;
4
4
  then: JsonSchema;
5
5
  else: JsonSchema;
6
- }, refs: Refs) => string;
6
+ }, refs: Refs) => SchemaRepresentation;
@@ -1,4 +1,4 @@
1
- import { JsonSchemaObject, Refs } from "../Types.js";
1
+ import { JsonSchemaObject, Refs, SchemaRepresentation } from "../Types.js";
2
2
  export declare const parseMultipleType: (schema: JsonSchemaObject & {
3
3
  type: string[];
4
- }, refs: Refs) => string;
4
+ }, refs: Refs) => SchemaRepresentation;
@@ -1,4 +1,4 @@
1
- import { JsonSchemaObject, JsonSchema, Refs } from "../Types.js";
1
+ import { JsonSchemaObject, JsonSchema, Refs, SchemaRepresentation } from "../Types.js";
2
2
  export declare const parseNot: (schema: JsonSchemaObject & {
3
3
  not: JsonSchema;
4
- }, refs: Refs) => string;
4
+ }, refs: Refs) => SchemaRepresentation;
@@ -1 +1,2 @@
1
- export declare const parseNull: () => string;
1
+ import { SchemaRepresentation } from "../Types.js";
2
+ export declare const parseNull: () => SchemaRepresentation;
@@ -1,7 +1,7 @@
1
- import { JsonSchemaObject, Refs } from "../Types.js";
1
+ import { JsonSchemaObject, Refs, SchemaRepresentation } from "../Types.js";
2
2
  /**
3
3
  * For compatibility with open api 3.0 nullable
4
4
  */
5
5
  export declare const parseNullable: (schema: JsonSchemaObject & {
6
6
  nullable: true;
7
- }, refs: Refs) => string;
7
+ }, refs: Refs) => SchemaRepresentation;
@@ -1,4 +1,4 @@
1
- import { JsonSchemaObject } from "../Types.js";
1
+ import { JsonSchemaObject, SchemaRepresentation } from "../Types.js";
2
2
  export declare const parseNumber: (schema: JsonSchemaObject & {
3
3
  type: "number" | "integer";
4
- }) => string;
4
+ }) => SchemaRepresentation;
@@ -1,4 +1,4 @@
1
- import { JsonSchemaObject, Refs } from "../Types.js";
1
+ import { JsonSchemaObject, Refs, SchemaRepresentation } from "../Types.js";
2
2
  export declare function parseObject(objectSchema: JsonSchemaObject & {
3
3
  type: "object";
4
- }, refs: Refs): string;
4
+ }, refs: Refs): SchemaRepresentation;
@@ -1,4 +1,4 @@
1
- import { JsonSchemaObject, JsonSchema, Refs } from "../Types.js";
1
+ import { JsonSchemaObject, JsonSchema, Refs, SchemaRepresentation } from "../Types.js";
2
2
  export declare const parseOneOf: (schema: JsonSchemaObject & {
3
3
  oneOf: JsonSchema[];
4
- }, refs: Refs) => string;
4
+ }, refs: Refs) => SchemaRepresentation;
@@ -1,5 +1,5 @@
1
- import { Refs, JsonSchemaObject, JsonSchema, Serializable, SimpleDiscriminatedOneOfSchema } from "../Types.js";
2
- export declare const parseSchema: (schema: JsonSchema, refs?: Refs, blockMeta?: boolean) => string;
1
+ import { Refs, JsonSchemaObject, JsonSchema, Serializable, SimpleDiscriminatedOneOfSchema, SchemaRepresentation } from "../Types.js";
2
+ export declare const parseSchema: (schema: JsonSchema, refs?: Refs, blockMeta?: boolean) => SchemaRepresentation;
3
3
  export declare const its: {
4
4
  an: {
5
5
  object: (x: JsonSchemaObject) => x is JsonSchemaObject & {
@@ -1,2 +1,2 @@
1
- import { SimpleDiscriminatedOneOfSchema, Refs } from "../Types.js";
2
- export declare const parseSimpleDiscriminatedOneOf: (schema: SimpleDiscriminatedOneOfSchema, refs: Refs) => string;
1
+ import { SimpleDiscriminatedOneOfSchema, Refs, SchemaRepresentation } from "../Types.js";
2
+ export declare const parseSimpleDiscriminatedOneOf: (schema: SimpleDiscriminatedOneOfSchema, refs: Refs) => SchemaRepresentation;
@@ -1,4 +1,4 @@
1
- import { JsonSchemaObject, Refs } from "../Types.js";
1
+ import { JsonSchemaObject, Refs, SchemaRepresentation } from "../Types.js";
2
2
  export declare const parseString: (schema: JsonSchemaObject & {
3
3
  type: "string";
4
- }, refs?: Refs) => string;
4
+ }, refs?: Refs) => SchemaRepresentation;
@@ -1,9 +1,10 @@
1
- import type { Refs } from "../Types.js";
1
+ import type { Refs, SchemaRepresentation } from "../Types.js";
2
2
  /**
3
- * Returns "z.unknown()" if the useUnknown option is enabled, otherwise "z.any()".
3
+ * Returns a SchemaRepresentation for z.unknown() if the useUnknown option is enabled,
4
+ * otherwise returns a SchemaRepresentation for z.any().
4
5
  * This helper is used throughout the library for fallback cases.
5
6
  *
6
7
  * @param refs - The refs object containing options
7
- * @returns The appropriate Zod schema string
8
+ * @returns The appropriate Zod schema representation
8
9
  */
9
- export declare const anyOrUnknown: (refs?: Refs) => string;
10
+ export declare const anyOrUnknown: (refs?: Refs) => SchemaRepresentation;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Small structured emitter for ESM/TypeScript snippets using the TS printer.
3
+ * Keeps formatting deterministic (double newlines between statements, trailing newline)
4
+ * while avoiding ad-hoc string concatenation.
5
+ */
6
+ export type ConstStatement = {
7
+ name: string;
8
+ expression: string;
9
+ exported?: boolean;
10
+ typeAnnotation?: string;
11
+ jsdoc?: string;
12
+ };
13
+ export type DefaultExport = {
14
+ expression: string;
15
+ jsdoc?: string;
16
+ };
17
+ export type TypeExport = {
18
+ name: string;
19
+ type: string;
20
+ jsdoc?: string;
21
+ };
22
+ export declare class EsmEmitter {
23
+ #private;
24
+ addNamedImport(name: string, source: string): void;
25
+ addConst(statement: ConstStatement): void;
26
+ addDefaultExport(statement: DefaultExport): void;
27
+ addTypeExport(statement: TypeExport): void;
28
+ render(): string;
29
+ }
@@ -0,0 +1,15 @@
1
+ import { JsonSchema, Refs } from "../Types.js";
2
+ /**
3
+ * Rule 1 from Zod v4: Put Object Types at Top-Level
4
+ *
5
+ * Extracts inline object schemas to top-level declarations when they have a title.
6
+ * This prevents embedding object schema declarations inside unions/intersections
7
+ * which can break recursive type inference.
8
+ *
9
+ * We extract any titled object schema, including those with:
10
+ * - $refs to other schemas (dependency ordering handles this)
11
+ * - Composition keywords (oneOf/anyOf/allOf for validation or extension)
12
+ *
13
+ * @returns The reference name if extracted, or null if not extractable
14
+ */
15
+ export declare const extractInlineObject: (schema: JsonSchema, refs: Refs, path: (string | number)[]) => string | null;
@@ -0,0 +1,21 @@
1
+ import { JsonSchema } from "../Types.js";
2
+ import { NameForPathHook } from "./namingService.js";
3
+ type LiftOptions = {
4
+ enable?: boolean;
5
+ nameForPath?: NameForPathHook;
6
+ parentName?: string;
7
+ dedup?: boolean;
8
+ allowInDefs?: boolean;
9
+ };
10
+ export type LiftResult = {
11
+ schema: JsonSchema;
12
+ defs: Record<string, JsonSchema>;
13
+ addedDefNames: string[];
14
+ pathToDefName: Map<string, string>;
15
+ };
16
+ /**
17
+ * Conservatively lift inline object-like schemas into top-level $defs.
18
+ * Skips when disabled or when candidates are ambiguous (contains $ref/dynamicRef).
19
+ */
20
+ export declare const liftInlineObjects: (schema: JsonSchema, options: LiftOptions) => LiftResult;
21
+ export {};
@@ -0,0 +1,21 @@
1
+ export type NameForPathHook = (path: (string | number)[], ctx: {
2
+ parentName?: string;
3
+ existingNames: Set<string>;
4
+ branchInfo?: unknown;
5
+ }) => string;
6
+ export type GenerateNameOptions = {
7
+ parentName?: string;
8
+ path: (string | number)[];
9
+ existingNames: Set<string>;
10
+ branchInfo?: unknown;
11
+ nameForPath?: NameForPathHook;
12
+ schemaTitle?: string;
13
+ };
14
+ /**
15
+ * Generate a stable PascalCase name for a lifted inline schema.
16
+ * - Uses parentName as a base (default: Root).
17
+ * - Adds path segments (properties/indices) to disambiguate.
18
+ * - Applies suffixes to avoid collisions.
19
+ * - Allows an optional hook to override naming.
20
+ */
21
+ export declare const generateNameFromPath: (options: GenerateNameOptions) => string;
@@ -0,0 +1,7 @@
1
+ import { JsonSchemaObject, JsonSchema, Refs } from "../Types.js";
2
+ export declare const resolveRef: (schemaNode: JsonSchemaObject, ref: string, refs: Refs) => {
3
+ schema: JsonSchema;
4
+ path: (string | number)[];
5
+ baseUri: string;
6
+ pointerKey: string;
7
+ } | undefined;
@@ -0,0 +1,71 @@
1
+ import { SchemaRepresentation } from "../Types.js";
2
+ /**
3
+ * Builder functions for composing SchemaRepresentation objects.
4
+ * These track both the Zod expression and its TypeScript type simultaneously.
5
+ */
6
+ export declare const zodString: () => SchemaRepresentation;
7
+ export declare const zodNumber: () => SchemaRepresentation;
8
+ export declare const zodBoolean: () => SchemaRepresentation;
9
+ export declare const zodNull: () => SchemaRepresentation;
10
+ export declare const zodUndefined: () => SchemaRepresentation;
11
+ export declare const zodAny: () => SchemaRepresentation;
12
+ export declare const zodUnknown: () => SchemaRepresentation;
13
+ export declare const zodNever: () => SchemaRepresentation;
14
+ export declare const zodBigInt: () => SchemaRepresentation;
15
+ export declare const zodDate: () => SchemaRepresentation;
16
+ export declare const zodRef: (schemaName: string) => SchemaRepresentation;
17
+ export declare const zodLazy: (schemaName: string) => SchemaRepresentation;
18
+ export declare const zodLazyTyped: (schemaName: string, innerType: string) => SchemaRepresentation;
19
+ export declare const zodArray: (inner: SchemaRepresentation) => SchemaRepresentation;
20
+ export declare const zodOptional: (inner: SchemaRepresentation) => SchemaRepresentation;
21
+ export declare const zodNullable: (inner: SchemaRepresentation) => SchemaRepresentation;
22
+ export declare const zodNullableWrapper: (inner: SchemaRepresentation) => SchemaRepresentation;
23
+ export declare const zodDefault: (inner: SchemaRepresentation, defaultValue: string) => SchemaRepresentation;
24
+ export declare const zodReadonly: (inner: SchemaRepresentation) => SchemaRepresentation;
25
+ export declare const zodDescribe: (inner: SchemaRepresentation, description: string) => SchemaRepresentation;
26
+ export declare const zodMeta: (inner: SchemaRepresentation, meta: string) => SchemaRepresentation;
27
+ export declare const zodLiteral: (value: string) => SchemaRepresentation;
28
+ export declare const zodEnum: (values: string[]) => SchemaRepresentation;
29
+ export declare const zodUnion: (options: SchemaRepresentation[]) => SchemaRepresentation;
30
+ export declare const zodDiscriminatedUnion: (discriminator: string, options: SchemaRepresentation[]) => SchemaRepresentation;
31
+ export declare const zodIntersection: (left: SchemaRepresentation, right: SchemaRepresentation) => SchemaRepresentation;
32
+ export declare const zodAnd: (base: SchemaRepresentation, other: SchemaRepresentation) => SchemaRepresentation;
33
+ export declare const zodTuple: (items: SchemaRepresentation[]) => SchemaRepresentation;
34
+ export declare const zodRecord: (key: SchemaRepresentation, value: SchemaRepresentation) => SchemaRepresentation;
35
+ export declare const zodMap: (key: SchemaRepresentation, value: SchemaRepresentation) => SchemaRepresentation;
36
+ export declare const zodSet: (value: SchemaRepresentation) => SchemaRepresentation;
37
+ export declare const zodObject: (shape: Array<{
38
+ key: string;
39
+ rep: SchemaRepresentation;
40
+ isGetter?: boolean;
41
+ }>) => SchemaRepresentation;
42
+ export declare const zodStrictObject: (shape: Array<{
43
+ key: string;
44
+ rep: SchemaRepresentation;
45
+ isGetter?: boolean;
46
+ }>) => SchemaRepresentation;
47
+ export declare const zodCatchall: (base: SchemaRepresentation, catchallSchema: SchemaRepresentation) => SchemaRepresentation;
48
+ export declare const zodSuperRefine: (base: SchemaRepresentation, refineFn: string) => SchemaRepresentation;
49
+ export declare const zodRefine: (base: SchemaRepresentation, refineFn: string) => SchemaRepresentation;
50
+ export declare const zodTransform: (base: SchemaRepresentation, transformFn: string) => SchemaRepresentation;
51
+ export declare const zodPipe: (first: SchemaRepresentation, second: SchemaRepresentation) => SchemaRepresentation;
52
+ export declare const zodCoerceString: () => SchemaRepresentation;
53
+ export declare const zodCoerceNumber: () => SchemaRepresentation;
54
+ export declare const zodCoerceBoolean: () => SchemaRepresentation;
55
+ export declare const zodCoerceDate: () => SchemaRepresentation;
56
+ export declare const zodChain: (base: SchemaRepresentation, method: string) => SchemaRepresentation;
57
+ export declare const fromExpression: (expression: string) => SchemaRepresentation;
58
+ /**
59
+ * Infers the TypeScript type from a Zod expression string.
60
+ * This is used for backward compatibility during migration.
61
+ */
62
+ export declare const inferTypeFromExpression: (expr: string) => string;
63
+ /**
64
+ * Check if an expression contains a reference to a recursive schema.
65
+ */
66
+ export declare const containsRecursiveRef: (expr: string, cycleRefNames: Set<string> | undefined) => boolean;
67
+ /**
68
+ * Determines if a property should use getter syntax based on its representation
69
+ * and the current schema context.
70
+ */
71
+ export declare const shouldUseGetter: (rep: SchemaRepresentation, currentSchemaName: string | undefined, cycleRefNames: Set<string> | undefined, cycleComponentByName: Map<string, number> | undefined) => boolean;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Returns a SchemaRepresentation for z.unknown() if the useUnknown option is enabled,
3
+ * otherwise returns a SchemaRepresentation for z.any().
4
+ * This helper is used throughout the library for fallback cases.
5
+ *
6
+ * @param refs - The refs object containing options
7
+ * @returns The appropriate Zod schema representation
8
+ */
9
+ export const anyOrUnknown = (refs) => {
10
+ return refs?.useUnknown
11
+ ? { expression: "z.unknown()", type: "z.ZodUnknown" }
12
+ : { expression: "z.any()", type: "z.ZodAny" };
13
+ };
@@ -1,9 +1,13 @@
1
1
  import { resolveUri } from "./resolveUri.js";
2
2
  export const buildRefRegistry = (schema, rootBaseUri = "root:///") => {
3
3
  const registry = new Map();
4
+ const seen = new WeakSet();
4
5
  const walk = (node, baseUri, path) => {
5
6
  if (typeof node !== "object" || node === null)
6
7
  return;
8
+ if (seen.has(node))
9
+ return;
10
+ seen.add(node);
7
11
  const obj = node;
8
12
  const nextBase = obj.$id ? resolveUri(baseUri, obj.$id) : baseUri;
9
13
  // Legacy recursive anchor
@@ -0,0 +1,87 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var _EsmEmitter_imports, _EsmEmitter_statements;
7
+ import ts from "typescript";
8
+ const normalizeJsdoc = (jsdoc) => {
9
+ if (!jsdoc)
10
+ return undefined;
11
+ const trimmed = jsdoc.trim();
12
+ if (!trimmed.startsWith("/**"))
13
+ return `* ${trimmed}`;
14
+ // Strip /** and */ and keep inner content
15
+ const withoutStart = trimmed.replace(/^\/\*\*/, "");
16
+ const withoutEnd = withoutStart.replace(/\*\/$/, "");
17
+ return withoutEnd.trim();
18
+ };
19
+ const attachJsdoc = (node, jsdoc) => {
20
+ const normalized = normalizeJsdoc(jsdoc);
21
+ if (!normalized)
22
+ return node;
23
+ return ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, normalized, true);
24
+ };
25
+ const parseExpression = (expression) => {
26
+ const sf = ts.createSourceFile("expr.ts", `${expression};`, ts.ScriptTarget.ES2020, false, ts.ScriptKind.TS);
27
+ const stmt = sf.statements[0];
28
+ if (stmt && ts.isExpressionStatement(stmt)) {
29
+ return stmt.expression;
30
+ }
31
+ throw new Error(`Failed to parse expression: ${expression}`);
32
+ };
33
+ const parseType = (type) => {
34
+ const sf = ts.createSourceFile("type.ts", `type __T = ${type};`, ts.ScriptTarget.ES2020, false, ts.ScriptKind.TS);
35
+ const stmt = sf.statements[0];
36
+ if (stmt && ts.isTypeAliasDeclaration(stmt)) {
37
+ return stmt.type;
38
+ }
39
+ throw new Error(`Failed to parse type: ${type}`);
40
+ };
41
+ export class EsmEmitter {
42
+ constructor() {
43
+ _EsmEmitter_imports.set(this, new Map());
44
+ _EsmEmitter_statements.set(this, []);
45
+ }
46
+ addNamedImport(name, source) {
47
+ const set = __classPrivateFieldGet(this, _EsmEmitter_imports, "f").get(source) ?? new Set();
48
+ set.add(name);
49
+ __classPrivateFieldGet(this, _EsmEmitter_imports, "f").set(source, set);
50
+ }
51
+ addConst(statement) {
52
+ const initializer = parseExpression(statement.expression);
53
+ const typeNode = statement.typeAnnotation ? parseType(statement.typeAnnotation) : undefined;
54
+ const decl = ts.factory.createVariableDeclaration(statement.name, undefined, typeNode, initializer);
55
+ const modifiers = statement.exported ? [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)] : undefined;
56
+ const varStmt = ts.factory.createVariableStatement(modifiers, ts.factory.createVariableDeclarationList([decl], ts.NodeFlags.Const));
57
+ __classPrivateFieldGet(this, _EsmEmitter_statements, "f").push({ node: attachJsdoc(varStmt, statement.jsdoc) });
58
+ }
59
+ addDefaultExport(statement) {
60
+ const assignment = ts.factory.createExportAssignment(undefined, false, parseExpression(statement.expression));
61
+ __classPrivateFieldGet(this, _EsmEmitter_statements, "f").push({ node: attachJsdoc(assignment, statement.jsdoc) });
62
+ }
63
+ addTypeExport(statement) {
64
+ const modifiers = [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)];
65
+ const typeAlias = ts.factory.createTypeAliasDeclaration(modifiers, statement.name, undefined, parseType(statement.type));
66
+ __classPrivateFieldGet(this, _EsmEmitter_statements, "f").push({ node: attachJsdoc(typeAlias, statement.jsdoc), compact: true });
67
+ }
68
+ render() {
69
+ const printer = ts.createPrinter({
70
+ newLine: ts.NewLineKind.LineFeed,
71
+ });
72
+ const sf = ts.createSourceFile("out.ts", "", ts.ScriptTarget.ES2020, false, ts.ScriptKind.TS);
73
+ const importStmts = [...__classPrivateFieldGet(this, _EsmEmitter_imports, "f").entries()]
74
+ .sort(([a], [b]) => a.localeCompare(b))
75
+ .map(([source, names]) => ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports([...names].sort().map((name) => ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(name))))), ts.factory.createStringLiteral(source)));
76
+ const allStatements = [
77
+ ...importStmts.map((node) => ({ node })),
78
+ ...__classPrivateFieldGet(this, _EsmEmitter_statements, "f"),
79
+ ];
80
+ if (allStatements.length === 0)
81
+ return "";
82
+ const file = ts.factory.updateSourceFile(sf, ts.factory.createNodeArray(allStatements.map((s) => s.node)));
83
+ const printed = printer.printFile(file);
84
+ return printed.endsWith("\n") ? printed : `${printed}\n`;
85
+ }
86
+ }
87
+ _EsmEmitter_imports = new WeakMap(), _EsmEmitter_statements = new WeakMap();