Skip to content

Commit 7d7a3c4

Browse files
committed
Refactor SDK to encapsulate schema-to-json package
The previous implementation required users to directly import and initialize functions from the `@trigger.dev/schema-to-json` package, which was not the intended user experience. This change refactors the SDK so that all necessary functions and types from `@trigger.dev/schema-to-json` are encapsulated within the `@trigger.dev/*` packages. - The examples in `usage.ts` have been updated to clearly mark `@trigger.dev/schema-to-json` as an internal-only package. - Re-export JSON Schema types and conversions in the SDK to improve developer experience (DX). - Removed unnecessary direct dependencies on `@trigger.dev/schema-to-json` from user-facing code, ensuring initialization and conversion logic is handled internally. - Replaced instances where users were required to manually perform schema conversions with automatic handling within the SDK for simplification and better maintainability.
1 parent bfd3645 commit 7d7a3c4

File tree

9 files changed

+683
-105
lines changed

9 files changed

+683
-105
lines changed
Lines changed: 61 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { task } from '@trigger.dev/sdk/v3';
2-
import { z } from 'zod';
1+
// This file shows how @trigger.dev/schema-to-json is used INTERNALLY by the SDK
2+
// Regular users should NOT import this package directly!
3+
34
import { schemaToJsonSchema, type JSONSchema } from '@trigger.dev/schema-to-json';
5+
import { z } from 'zod';
46

5-
// Example 1: Using schemaTask (automatic conversion)
6-
import { schemaTask } from '@trigger.dev/sdk/v3';
7+
// Example of how the SDK uses this internally:
78

89
const userSchema = z.object({
910
id: z.string(),
@@ -12,96 +13,69 @@ const userSchema = z.object({
1213
age: z.number().int().min(0),
1314
});
1415

15-
export const processUser = schemaTask({
16-
id: 'process-user',
17-
schema: userSchema,
18-
run: async (payload) => {
19-
// payload is fully typed based on the schema
20-
console.log(`Processing user ${payload.name}`);
21-
return { processed: true };
22-
},
23-
});
16+
// This is what happens internally in the SDK's schemaTask:
17+
const result = schemaToJsonSchema(userSchema);
18+
if (result) {
19+
console.log('Converted Zod schema to JSON Schema:', result.jsonSchema);
20+
console.log('Detected schema type:', result.schemaType);
21+
// The SDK then includes this JSON Schema in the task metadata
22+
}
2423

25-
// Example 2: Using plain task with manual JSON Schema
26-
export const processOrder = task({
27-
id: 'process-order',
28-
// Manually provide JSON Schema for the payload
29-
payloadSchema: {
30-
type: 'object',
31-
properties: {
32-
orderId: { type: 'string' },
33-
items: {
34-
type: 'array',
35-
items: {
36-
type: 'object',
37-
properties: {
38-
productId: { type: 'string' },
39-
quantity: { type: 'integer', minimum: 1 },
40-
price: { type: 'number', minimum: 0 },
41-
},
42-
required: ['productId', 'quantity', 'price'],
43-
},
44-
},
45-
totalAmount: { type: 'number' },
46-
},
47-
required: ['orderId', 'items', 'totalAmount'],
48-
} satisfies JSONSchema,
49-
run: async (payload) => {
50-
// payload is typed as any, but the schema will be validated at runtime
51-
console.log(`Processing order ${payload.orderId}`);
52-
return { processed: true };
53-
},
54-
});
24+
// Example: How different schema libraries are detected and converted
5525

56-
// Example 3: Using plain task with schema conversion
57-
const orderSchema = z.object({
58-
orderId: z.string(),
59-
items: z.array(z.object({
60-
productId: z.string(),
61-
quantity: z.number().int().min(1),
62-
price: z.number().min(0),
63-
})),
64-
totalAmount: z.number(),
26+
// Yup schema
27+
import * as y from 'yup';
28+
const yupSchema = y.object({
29+
name: y.string().required(),
30+
age: y.number().required(),
6531
});
6632

67-
// Convert the schema to JSON Schema
68-
const orderJsonSchema = schemaToJsonSchema(orderSchema);
69-
70-
export const processOrderWithConversion = task({
71-
id: 'process-order-converted',
72-
// Use the converted JSON Schema
73-
payloadSchema: orderJsonSchema?.jsonSchema,
74-
run: async (payload) => {
75-
// Note: You still need to validate the payload yourself in plain tasks
76-
const parsed = orderSchema.parse(payload);
77-
console.log(`Processing order ${parsed.orderId}`);
78-
return { processed: true };
79-
},
33+
const yupResult = schemaToJsonSchema(yupSchema);
34+
console.log('Yup conversion:', yupResult);
35+
36+
// ArkType schema (has built-in toJsonSchema)
37+
import { type } from 'arktype';
38+
const arkSchema = type({
39+
name: 'string',
40+
age: 'number',
8041
});
8142

82-
// Example 4: Type-safe JSON Schema creation
83-
import { Type, Static } from '@sinclair/typebox';
43+
const arkResult = schemaToJsonSchema(arkSchema);
44+
console.log('ArkType conversion:', arkResult);
8445

46+
// TypeBox (already JSON Schema)
47+
import { Type } from '@sinclair/typebox';
8548
const typeBoxSchema = Type.Object({
86-
userId: Type.String(),
87-
action: Type.Union([
88-
Type.Literal('create'),
89-
Type.Literal('update'),
90-
Type.Literal('delete'),
91-
]),
92-
timestamp: Type.Number(),
49+
name: Type.String(),
50+
age: Type.Number(),
9351
});
9452

95-
type UserAction = Static<typeof typeBoxSchema>;
96-
97-
export const processUserAction = task({
98-
id: 'process-user-action',
99-
// TypeBox schemas are already JSON Schema compliant
100-
payloadSchema: typeBoxSchema,
101-
run: async (payload) => {
102-
// Cast to get type safety (or validate at runtime)
103-
const action = payload as UserAction;
104-
console.log(`User ${action.userId} performed ${action.action}`);
105-
return { processed: true };
106-
},
107-
});
53+
const typeBoxResult = schemaToJsonSchema(typeBoxSchema);
54+
console.log('TypeBox conversion:', typeBoxResult);
55+
56+
// Example: Initialization (done automatically by the SDK)
57+
import { initializeSchemaConverters, areConvertersInitialized } from '@trigger.dev/schema-to-json';
58+
59+
// The SDK calls this once when it loads
60+
await initializeSchemaConverters();
61+
62+
// Check which converters are available
63+
const status = areConvertersInitialized();
64+
console.log('Converter status:', status);
65+
// { zod: true, yup: true, effect: true }
66+
67+
// Example: How the SDK determines if a schema can be converted
68+
import { canConvertSchema, detectSchemaType } from '@trigger.dev/schema-to-json';
69+
70+
const zodSchema = z.string();
71+
console.log('Can convert Zod?', canConvertSchema(zodSchema)); // true
72+
console.log('Schema type:', detectSchemaType(zodSchema)); // 'zod'
73+
74+
// For users: Just use the SDK!
75+
// import { schemaTask } from '@trigger.dev/sdk/v3';
76+
//
77+
// export const myTask = schemaTask({
78+
// id: 'my-task',
79+
// schema: zodSchema,
80+
// run: async (payload) => { /* ... */ }
81+
// });

packages/schema-to-json/src/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1-
import type { JSONSchema7, JSONSchema7Definition } from '@types/json-schema';
1+
import type { JSONSchema7, JSONSchema7Definition, JSONSchema7Type, JSONSchema7TypeName, JSONSchema7Object, JSONSchema7Array } from '@types/json-schema';
22

33
export type Schema = unknown;
44
export type JSONSchema = JSONSchema7;
55
export type JSONSchemaDefinition = JSONSchema7Definition;
66

7+
// Re-export the standard JSON Schema types for convenience
8+
export type {
9+
JSONSchema7,
10+
JSONSchema7Type,
11+
JSONSchema7TypeName,
12+
JSONSchema7Definition,
13+
JSONSchema7Object,
14+
JSONSchema7Array,
15+
} from '@types/json-schema';
16+
717
export interface ConversionOptions {
818
/**
919
* The name to use for the schema in the JSON Schema

packages/trigger-sdk/src/v3/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export * from "./timeout.js";
1414
export * from "./webhooks.js";
1515
export * from "./locals.js";
1616
export * from "./otel.js";
17+
export * from "./schemas.js";
1718
export type { Context };
1819

1920
import type { Context } from "./shared.js";
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Re-export JSON Schema types for user convenience
2+
export type { JSONSchema, JSONSchemaDefinition } from "@trigger.dev/schema-to-json";
3+
4+
// Re-export the standard JSON Schema types from @types/json-schema
5+
export type {
6+
JSONSchema7,
7+
JSONSchema7Type,
8+
JSONSchema7TypeName,
9+
JSONSchema7Definition,
10+
JSONSchema7Object,
11+
JSONSchema7Array,
12+
} from "@trigger.dev/schema-to-json";

packages/trigger-sdk/src/v3/shared.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,17 @@ import {
2828
TaskRunExecutionResult,
2929
TaskRunPromise,
3030
} from "@trigger.dev/core/v3";
31-
import { schemaToJsonSchema } from "@trigger.dev/schema-to-json";
31+
import { schemaToJsonSchema, initializeSchemaConverters } from "@trigger.dev/schema-to-json";
3232
import { PollOptions, runs } from "./runs.js";
3333
import { tracer } from "./tracer.js";
3434

35+
// Initialize schema converters once when the module loads
36+
// This happens automatically, users don't need to know about it
37+
initializeSchemaConverters().catch(() => {
38+
// Silently fail if converters can't be initialized
39+
// Built-in conversions will still work
40+
});
41+
3542
import type {
3643
AnyOnCatchErrorHookFunction,
3744
AnyOnCleanupHookFunction,

references/hello-world/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
"dependencies": {
99
"@trigger.dev/build": "workspace:*",
1010
"@trigger.dev/sdk": "workspace:*",
11-
"@trigger.dev/schema-to-json": "workspace:*",
1211
"arktype": "^2.0.0",
1312
"openai": "^4.97.0",
1413
"puppeteer-core": "^24.15.0",

references/hello-world/src/trigger/jsonSchema.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1-
import { task, schemaTask, logger } from "@trigger.dev/sdk/v3";
1+
import { task, schemaTask, logger, type JSONSchema } from "@trigger.dev/sdk/v3";
22
import { z } from "zod";
3-
import { schemaToJsonSchema, initializeSchemaConverters, type JSONSchema } from "@trigger.dev/schema-to-json";
43
import * as y from "yup";
54
import { type } from "arktype";
65
import { Type, Static } from "@sinclair/typebox";
76

8-
// Initialize converters for schemas that need external libraries
9-
// This is only needed if you want to convert Zod 3, Yup, or Effect schemas
10-
await initializeSchemaConverters();
11-
127
// ===========================================
138
// Example 1: Using schemaTask with Zod
149
// ===========================================
@@ -235,8 +230,12 @@ export const processEventWithTypeBox = task({
235230
});
236231

237232
// ===========================================
238-
// Example 6: Converting schemas at build time
233+
// Example 6: Using plain task with a Zod schema
239234
// ===========================================
235+
// If you need to use a plain task but have a Zod schema,
236+
// you should use schemaTask instead for better DX.
237+
// This example shows what NOT to do:
238+
240239
const notificationSchema = z.object({
241240
recipientId: z.string(),
242241
type: z.enum(["email", "sms", "push"]),
@@ -247,17 +246,25 @@ const notificationSchema = z.object({
247246
metadata: z.record(z.unknown()).optional(),
248247
});
249248

250-
// Convert the schema to JSON Schema at build time
251-
const notificationJsonSchema = schemaToJsonSchema(notificationSchema);
252-
253-
export const sendNotificationWithConversion = task({
254-
id: "json-schema-conversion-example",
255-
// Use the converted JSON Schema
256-
payloadSchema: notificationJsonSchema?.jsonSchema,
249+
// ❌ Don't do this - use schemaTask instead!
250+
export const sendNotificationBadExample = task({
251+
id: "json-schema-dont-do-this",
257252
run: async (payload, { ctx }) => {
258-
// Validate the payload using the original Zod schema
253+
// You'd have to manually validate
259254
const notification = notificationSchema.parse(payload);
260255

256+
logger.info("This is not ideal - use schemaTask instead!");
257+
258+
return { sent: true };
259+
},
260+
});
261+
262+
// ✅ Do this instead - much better!
263+
export const sendNotificationGoodExample = schemaTask({
264+
id: "json-schema-do-this-instead",
265+
schema: notificationSchema,
266+
run: async (notification, { ctx }) => {
267+
// notification is already validated and typed!
261268
logger.info("Sending notification", {
262269
recipientId: notification.recipientId,
263270
type: notification.type,

0 commit comments

Comments
 (0)