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