11// Import JSONSchema from core to ensure compatibility
22import type { JSONSchema } from "@trigger.dev/core/v3" ;
3+ import { zodToJsonSchema } from "zod-to-json-schema" ;
4+ import * as z4 from "zod/v4" ;
5+ import { convertSchema } from "@sodaru/yup-to-json-schema" ;
6+ import { JSONSchema as EffectJSONSchema } from "effect" ;
37
48export type Schema = unknown ;
59export type { JSONSchema } ;
610
711export interface ConversionOptions {
812 /**
9- * The name to use for the schema in the JSON Schema
13+ * Enables support for references in the schema.
14+ * This is required for recursive schemas, e.g. with `z.lazy`.
15+ * However, not all language models and providers support such references.
16+ * Defaults to `false`.
1017 */
11- name ?: string ;
12- /**
13- * Additional JSON Schema properties to merge
14- */
15- additionalProperties ?: Record < string , unknown > ;
18+ useReferences ?: boolean ;
1619}
1720
1821export interface ConversionResult {
1922 /**
2023 * The JSON Schema representation (JSON Schema Draft 7)
2124 */
2225 jsonSchema : JSONSchema ;
23- /**
24- * The detected schema type
25- */
26- schemaType :
27- | "zod"
28- | "yup"
29- | "arktype"
30- | "effect"
31- | "valibot"
32- | "superstruct"
33- | "runtypes"
34- | "typebox"
35- | "unknown" ;
3626}
3727
3828/**
@@ -57,107 +47,48 @@ export function schemaToJsonSchema(
5747 if ( typeof parser . toJsonSchema === "function" ) {
5848 try {
5949 const jsonSchema = parser . toJsonSchema ( ) ;
60- // Determine if it's Zod or ArkType based on other methods
61- const schemaType =
62- typeof parser . parseAsync === "function" || typeof parser . parse === "function"
63- ? "zod"
64- : "arktype" ;
50+
6551 return {
66- jsonSchema : options ?. additionalProperties
67- ? { ...jsonSchema , ...options . additionalProperties }
68- : jsonSchema ,
69- schemaType,
52+ jsonSchema,
7053 } ;
7154 } catch ( error ) {
7255 // If toJsonSchema fails, continue to other checks
7356 }
7457 }
7558
59+ if ( isZodSchema ( parser ) ) {
60+ const jsonSchema = convertZodSchema ( parser , options ) ;
61+
62+ if ( jsonSchema ) {
63+ return {
64+ jsonSchema : jsonSchema ,
65+ } ;
66+ }
67+ }
68+
7669 // Check if it's a TypeBox schema (has Static and Kind symbols)
7770 if ( parser [ Symbol . for ( "TypeBox.Kind" ) ] !== undefined ) {
7871 // TypeBox schemas are already JSON Schema compliant
7972 return {
80- jsonSchema : options ?. additionalProperties
81- ? { ...parser , ...options . additionalProperties }
82- : parser ,
83- schemaType : "typebox" ,
73+ jsonSchema : parser ,
8474 } ;
8575 }
8676
87- // For schemas that need external libraries, we need to check if they're available
88- // This approach avoids bundling the dependencies while still allowing runtime usage
89-
90- // Check if it's a Zod schema (without built-in toJsonSchema)
91- if ( typeof parser . parseAsync === "function" || typeof parser . parse === "function" ) {
92- try {
93- // Try to access zod-to-json-schema if it's available
94- // @ts -ignore - This is intentionally dynamic
95- if ( typeof globalThis . __zodToJsonSchema !== "undefined" ) {
96- // @ts -ignore
97- const { zodToJsonSchema } = globalThis . __zodToJsonSchema ;
98- const jsonSchema = options ?. name
99- ? zodToJsonSchema ( parser , options . name )
100- : zodToJsonSchema ( parser ) ;
101-
102- if ( jsonSchema && typeof jsonSchema === "object" && "$schema" in jsonSchema ) {
103- const { $schema, ...rest } = jsonSchema as any ;
104- return {
105- jsonSchema : options ?. additionalProperties
106- ? { ...rest , ...options . additionalProperties }
107- : rest ,
108- schemaType : "zod" ,
109- } ;
110- }
111-
112- return {
113- jsonSchema : options ?. additionalProperties
114- ? { ...jsonSchema , ...options . additionalProperties }
115- : jsonSchema ,
116- schemaType : "zod" ,
117- } ;
118- }
119- } catch ( error ) {
120- // Library not available
121- }
122- }
123-
124- // Check if it's a Yup schema
125- if ( typeof parser . validateSync === "function" && typeof parser . describe === "function" ) {
126- try {
127- // @ts -ignore
128- if ( typeof globalThis . __yupToJsonSchema !== "undefined" ) {
129- // @ts -ignore
130- const { convertSchema } = globalThis . __yupToJsonSchema ;
131- const jsonSchema = convertSchema ( parser ) ;
132- return {
133- jsonSchema : options ?. additionalProperties
134- ? { ...jsonSchema , ...options . additionalProperties }
135- : jsonSchema ,
136- schemaType : "yup" ,
137- } ;
138- }
139- } catch ( error ) {
140- // Library not available
77+ if ( isYupSchema ( parser ) ) {
78+ const jsonSchema = convertYupSchema ( parser ) ;
79+ if ( jsonSchema ) {
80+ return {
81+ jsonSchema : jsonSchema ,
82+ } ;
14183 }
14284 }
14385
144- // Check if it's an Effect schema
145- if ( typeof parser . ast === "object" && typeof parser . ast . _tag === "string" ) {
146- try {
147- // @ts -ignore
148- if ( typeof globalThis . __effectJsonSchema !== "undefined" ) {
149- // @ts -ignore
150- const { JSONSchema } = globalThis . __effectJsonSchema ;
151- const jsonSchema = JSONSchema . make ( parser ) ;
152- return {
153- jsonSchema : options ?. additionalProperties
154- ? { ...jsonSchema , ...options . additionalProperties }
155- : jsonSchema ,
156- schemaType : "effect" ,
157- } ;
158- }
159- } catch ( error ) {
160- // Library not available
86+ if ( isEffectSchema ( parser ) ) {
87+ const jsonSchema = convertEffectSchema ( parser ) ;
88+ if ( jsonSchema ) {
89+ return {
90+ jsonSchema : jsonSchema ,
91+ } ;
16192 }
16293 }
16394
@@ -168,71 +99,75 @@ export function schemaToJsonSchema(
16899}
169100
170101/**
171- * Initialize the schema conversion libraries
172- * This should be called by the consuming application if they want to enable
173- * conversion for schemas that don't have built-in JSON Schema support
102+ * Check if a schema can be converted to JSON Schema
174103 */
175- export async function initializeSchemaConverters ( ) : Promise < void > {
176- try {
177- // @ts -ignore
178- globalThis . __zodToJsonSchema = await import ( "zod-to-json-schema" ) ;
179- } catch {
180- // Zod conversion not available
104+ export function canConvertSchema ( schema : Schema ) : boolean {
105+ const result = schemaToJsonSchema ( schema ) ;
106+ return result !== undefined ;
107+ }
108+
109+ export function isZodSchema ( schema : any ) : boolean {
110+ return isZod3Schema ( schema ) || isZod4Schema ( schema ) ;
111+ }
112+
113+ function isZod3Schema ( schema : any ) : boolean {
114+ return "_def" in schema && "parse" in schema && "parseAsync" in schema && "safeParse" in schema ;
115+ }
116+
117+ function isZod4Schema ( schema : any ) : boolean {
118+ return "_zod" in schema ;
119+ }
120+
121+ function convertZodSchema ( schema : any , options ?: ConversionOptions ) : JSONSchema | undefined {
122+ if ( isZod4Schema ( schema ) ) {
123+ return convertZod4Schema ( schema , options ) ;
181124 }
182125
183- try {
184- // @ts -ignore
185- globalThis . __yupToJsonSchema = await import ( "@sodaru/yup-to-json-schema" ) ;
186- } catch {
187- // Yup conversion not available
126+ if ( isZod3Schema ( schema ) ) {
127+ return convertZod3Schema ( schema , options ) ;
188128 }
189129
190- try {
191- // Try Effect first, then @effect/schema
192- let module ;
193- try {
194- module = await import ( "effect" ) ;
195- } catch { }
130+ return undefined ;
131+ }
196132
197- if ( module ?. JSONSchema ) {
198- // @ts -ignore
199- globalThis . __effectJsonSchema = { JSONSchema : module . JSONSchema } ;
200- }
201- } catch {
202- // Effect conversion not available
203- }
133+ function convertZod3Schema ( schema : any , options ?: ConversionOptions ) : JSONSchema | undefined {
134+ const useReferences = options ?. useReferences ?? false ;
135+
136+ return zodToJsonSchema ( schema , {
137+ $refStrategy : useReferences ? "root" : "none" ,
138+ } ) as JSONSchema ;
204139}
205140
206- /**
207- * Check if a schema can be converted to JSON Schema
208- */
209- export function canConvertSchema ( schema : Schema ) : boolean {
210- const result = schemaToJsonSchema ( schema ) ;
211- return result !== undefined ;
141+ function convertZod4Schema ( schema : any , options ?: ConversionOptions ) : JSONSchema | undefined {
142+ const useReferences = options ?. useReferences ?? false ;
143+
144+ return z4 . toJSONSchema ( schema , {
145+ target : "draft-7" ,
146+ io : "output" ,
147+ reused : useReferences ? "ref" : "inline" ,
148+ } ) as JSONSchema ;
212149}
213150
214- /**
215- * Get the detected schema type
216- */
217- export function detectSchemaType ( schema : Schema ) : ConversionResult [ "schemaType" ] {
218- const result = schemaToJsonSchema ( schema ) ;
219- return result ?. schemaType ?? "unknown" ;
151+ function isYupSchema ( schema : any ) : boolean {
152+ return "spec" in schema && "_typeCheck" in schema ;
220153}
221154
222- /**
223- * Check if the conversion libraries are initialized
224- */
225- export function areConvertersInitialized ( ) : {
226- zod : boolean ;
227- yup : boolean ;
228- effect : boolean ;
229- } {
230- return {
231- // @ts -ignore
232- zod : typeof globalThis . __zodToJsonSchema !== "undefined" ,
233- // @ts -ignore
234- yup : typeof globalThis . __yupToJsonSchema !== "undefined" ,
235- // @ts -ignore
236- effect : typeof globalThis . __effectJsonSchema !== "undefined" ,
237- } ;
155+ function convertYupSchema ( schema : any ) : JSONSchema | undefined {
156+ try {
157+ return convertSchema ( schema ) as JSONSchema ;
158+ } catch {
159+ return undefined ;
160+ }
161+ }
162+
163+ function isEffectSchema ( schema : any ) : boolean {
164+ return "ast" in schema && typeof schema . ast === "object" && typeof schema . ast . _tag === "string" ;
165+ }
166+
167+ function convertEffectSchema ( schema : any ) : JSONSchema | undefined {
168+ try {
169+ return EffectJSONSchema . make ( schema ) as JSONSchema ;
170+ } catch {
171+ return undefined ;
172+ }
238173}
0 commit comments