1
1
// Import JSONSchema from core to ensure compatibility
2
2
import 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" ;
3
7
4
8
export type Schema = unknown ;
5
9
export type { JSONSchema } ;
6
10
7
11
export interface ConversionOptions {
8
12
/**
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`.
10
17
*/
11
- name ?: string ;
12
- /**
13
- * Additional JSON Schema properties to merge
14
- */
15
- additionalProperties ?: Record < string , unknown > ;
18
+ useReferences ?: boolean ;
16
19
}
17
20
18
21
export interface ConversionResult {
19
22
/**
20
23
* The JSON Schema representation (JSON Schema Draft 7)
21
24
*/
22
25
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" ;
36
26
}
37
27
38
28
/**
@@ -57,107 +47,48 @@ export function schemaToJsonSchema(
57
47
if ( typeof parser . toJsonSchema === "function" ) {
58
48
try {
59
49
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
+
65
51
return {
66
- jsonSchema : options ?. additionalProperties
67
- ? { ...jsonSchema , ...options . additionalProperties }
68
- : jsonSchema ,
69
- schemaType,
52
+ jsonSchema,
70
53
} ;
71
54
} catch ( error ) {
72
55
// If toJsonSchema fails, continue to other checks
73
56
}
74
57
}
75
58
59
+ if ( isZodSchema ( parser ) ) {
60
+ const jsonSchema = convertZodSchema ( parser , options ) ;
61
+
62
+ if ( jsonSchema ) {
63
+ return {
64
+ jsonSchema : jsonSchema ,
65
+ } ;
66
+ }
67
+ }
68
+
76
69
// Check if it's a TypeBox schema (has Static and Kind symbols)
77
70
if ( parser [ Symbol . for ( "TypeBox.Kind" ) ] !== undefined ) {
78
71
// TypeBox schemas are already JSON Schema compliant
79
72
return {
80
- jsonSchema : options ?. additionalProperties
81
- ? { ...parser , ...options . additionalProperties }
82
- : parser ,
83
- schemaType : "typebox" ,
73
+ jsonSchema : parser ,
84
74
} ;
85
75
}
86
76
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
+ } ;
141
83
}
142
84
}
143
85
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
+ } ;
161
92
}
162
93
}
163
94
@@ -168,71 +99,75 @@ export function schemaToJsonSchema(
168
99
}
169
100
170
101
/**
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
174
103
*/
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 ) ;
181
124
}
182
125
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 ) ;
188
128
}
189
129
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
+ }
196
132
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 ;
204
139
}
205
140
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 ;
212
149
}
213
150
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 ;
220
153
}
221
154
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
+ }
238
173
}
0 commit comments