From e86000b221e84277ade301f7b7c89e6f61cbef03 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Sun, 27 Jul 2025 13:36:37 +0100 Subject: [PATCH 01/19] Add payload schema handling for task indexing This change introduces support for handling payload schemas during task indexing. By incorporating the `payloadSchema` attribute into various components, we ensure that each task's payload structure is clearly defined and can be validated before processing. - Updated the TaskManifest and task metadata structures to include an optional `payloadSchema` attribute. This addition allows for more robust validation and handling of task payloads. - Enhanced several core modules to export and utilize the new `getSchemaToJsonSchema` function, providing easier conversion of schema types to JSON schemas. - Modified the database schema to store the `payloadSchema` attribute, ensuring that the payload schema information is persisted. - The change helps in maintaining consistency in data handling and improves the integrity of task data across the application. --- .../services/createBackgroundWorker.server.ts | 1 + .../database/prisma/schema.prisma | 2 ++ packages/core/src/v3/index.ts | 1 + packages/core/src/v3/schemas/resources.ts | 1 + packages/core/src/v3/schemas/schemas.ts | 1 + packages/core/src/v3/types/schemas.ts | 33 +++++++++++++++++++ packages/core/src/v3/types/tasks.ts | 5 +++ packages/trigger-sdk/src/v3/shared.ts | 3 ++ 8 files changed, 47 insertions(+) diff --git a/apps/webapp/app/v3/services/createBackgroundWorker.server.ts b/apps/webapp/app/v3/services/createBackgroundWorker.server.ts index 5ca2d5d387..f1aad74fe5 100644 --- a/apps/webapp/app/v3/services/createBackgroundWorker.server.ts +++ b/apps/webapp/app/v3/services/createBackgroundWorker.server.ts @@ -280,6 +280,7 @@ async function createWorkerTask( fileId: tasksToBackgroundFiles?.get(task.id) ?? null, maxDurationInSeconds: task.maxDuration ? clampMaxDuration(task.maxDuration) : null, queueId: queue.id, + payloadSchema: task.payloadSchema, }, }); } catch (error) { diff --git a/internal-packages/database/prisma/schema.prisma b/internal-packages/database/prisma/schema.prisma index 211ff2b355..ce12dcf7c7 100644 --- a/internal-packages/database/prisma/schema.prisma +++ b/internal-packages/database/prisma/schema.prisma @@ -510,6 +510,8 @@ model BackgroundWorkerTask { triggerSource TaskTriggerSource @default(STANDARD) + payloadSchema Json? + @@unique([workerId, slug]) // Quick lookup of task identifiers @@index([projectId, slug]) diff --git a/packages/core/src/v3/index.ts b/packages/core/src/v3/index.ts index 3bd1fc4547..afbd7b26f1 100644 --- a/packages/core/src/v3/index.ts +++ b/packages/core/src/v3/index.ts @@ -72,6 +72,7 @@ export * from "./utils/interval.js"; export * from "./config.js"; export { getSchemaParseFn, + getSchemaToJsonSchema, type AnySchemaParseFn, type SchemaParseFn, isSchemaZodEsque, diff --git a/packages/core/src/v3/schemas/resources.ts b/packages/core/src/v3/schemas/resources.ts index ec2b180bbc..1af03f47b7 100644 --- a/packages/core/src/v3/schemas/resources.ts +++ b/packages/core/src/v3/schemas/resources.ts @@ -13,6 +13,7 @@ export const TaskResource = z.object({ triggerSource: z.string().optional(), schedule: ScheduleMetadata.optional(), maxDuration: z.number().optional(), + payloadSchema: z.any().optional(), }); export type TaskResource = z.infer; diff --git a/packages/core/src/v3/schemas/schemas.ts b/packages/core/src/v3/schemas/schemas.ts index bd32d848ff..5fd8ef3f89 100644 --- a/packages/core/src/v3/schemas/schemas.ts +++ b/packages/core/src/v3/schemas/schemas.ts @@ -189,6 +189,7 @@ const taskMetadata = { triggerSource: z.string().optional(), schedule: ScheduleMetadata.optional(), maxDuration: z.number().optional(), + payloadSchema: z.any().optional(), }; export const TaskMetadata = z.object(taskMetadata); diff --git a/packages/core/src/v3/types/schemas.ts b/packages/core/src/v3/types/schemas.ts index e5ae9c3d88..7c145fef83 100644 --- a/packages/core/src/v3/types/schemas.ts +++ b/packages/core/src/v3/types/schemas.ts @@ -146,3 +146,36 @@ export function getSchemaParseFn(procedureParser: Schema): SchemaParseFn< throw new Error("Could not find a validator fn"); } + +export function getSchemaToJsonSchema(schema: Schema): any | undefined { + const parser = schema as any; + + // Check if schema has a built-in toJsonSchema method (e.g., ArkType) + if (typeof parser.toJsonSchema === "function") { + return parser.toJsonSchema(); + } + + // Check if it's a Zod schema + if (typeof parser.parseAsync === "function" || typeof parser.parse === "function") { + // Zod schema detected, but we need zod-to-json-schema library to convert + // Return undefined for now, will be handled by the caller + return undefined; + } + + // Check if it's a Yup schema + if (typeof parser.validateSync === "function") { + // Yup schema detected, but we need a yup-to-json-schema library to convert + // Return undefined for now, will be handled by the caller + return undefined; + } + + // Check if it's an Effect schema + if (parser._tag === "Schema" || parser._tag === "SchemaClass") { + // Effect schema detected, but we need Effect's JSONSchema.make to convert + // Return undefined for now, will be handled by the caller + return undefined; + } + + // For other schema types, return undefined + return undefined; +} diff --git a/packages/core/src/v3/types/tasks.ts b/packages/core/src/v3/types/tasks.ts index f9595b51e6..d02c121c13 100644 --- a/packages/core/src/v3/types/tasks.ts +++ b/packages/core/src/v3/types/tasks.ts @@ -339,6 +339,11 @@ type CommonTaskOptions< * onFailure is called after a task run has failed (meaning the run function threw an error and won't be retried anymore) */ onFailure?: OnFailureHookFunction; + + /** + * JSON Schema for the task payload. This will be synced to the server during indexing. + */ + payloadSchema?: any; }; export type TaskOptions< diff --git a/packages/trigger-sdk/src/v3/shared.ts b/packages/trigger-sdk/src/v3/shared.ts index 487c16308e..3a490a1723 100644 --- a/packages/trigger-sdk/src/v3/shared.ts +++ b/packages/trigger-sdk/src/v3/shared.ts @@ -11,6 +11,7 @@ import { flattenIdempotencyKey, getEnvVar, getSchemaParseFn, + getSchemaToJsonSchema, InitOutput, lifecycleHooks, makeIdempotencyKey, @@ -204,6 +205,7 @@ export function createTask< retry: params.retry ? { ...defaultRetryOptions, ...params.retry } : undefined, machine: typeof params.machine === "string" ? { preset: params.machine } : params.machine, maxDuration: params.maxDuration, + payloadSchema: params.payloadSchema, fns: { run: params.run, }, @@ -334,6 +336,7 @@ export function createSchemaTask< retry: params.retry ? { ...defaultRetryOptions, ...params.retry } : undefined, machine: typeof params.machine === "string" ? { preset: params.machine } : params.machine, maxDuration: params.maxDuration, + payloadSchema: params.schema ? getSchemaToJsonSchema(params.schema) : undefined, fns: { run: params.run, parsePayload, From 9b395d6a4a903dedebc72ffd30d534b01792c2be Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Sun, 27 Jul 2025 13:55:55 +0100 Subject: [PATCH 02/19] Refactor: Remove getSchemaToJsonSchema in favor of schemaToJsonSchema The `getSchemaToJsonSchema` function was removed and replaced with `schemaToJsonSchema` across the codebase. This update introduces a new `@trigger.dev/schema-to-json` package to handle conversions of schema validation libraries to JSON Schema format, centralizing the functionality and improving maintainability. - Removed `getSchemaToJsonSchema` exports and references. - Added new schema conversion utility `@trigger.dev/schema-to-json`. - Updated `trigger-sdk` package to utilize `schemaToJsonSchema` for payloads. - Extensive testing coverage included to ensure conversion accuracy across various schema libraries including Zod, Yup, ArkType, Effect, and TypeBox. - The update ensures consistent and reliable schema conversions, facilitating future enhancements and supporting additional schema libraries. --- packages/core/src/v3/index.ts | 1 - packages/core/src/v3/types/schemas.ts | 33 -- packages/schema-to-json/README.md | 102 ++++++ packages/schema-to-json/package.json | 109 +++++++ .../src/__tests__/index.test.ts | 301 ++++++++++++++++++ packages/schema-to-json/src/index.ts | 151 +++++++++ packages/schema-to-json/tsconfig.json | 11 + packages/trigger-sdk/package.json | 1 + packages/trigger-sdk/src/v3/shared.ts | 4 +- 9 files changed, 677 insertions(+), 36 deletions(-) create mode 100644 packages/schema-to-json/README.md create mode 100644 packages/schema-to-json/package.json create mode 100644 packages/schema-to-json/src/__tests__/index.test.ts create mode 100644 packages/schema-to-json/src/index.ts create mode 100644 packages/schema-to-json/tsconfig.json diff --git a/packages/core/src/v3/index.ts b/packages/core/src/v3/index.ts index afbd7b26f1..3bd1fc4547 100644 --- a/packages/core/src/v3/index.ts +++ b/packages/core/src/v3/index.ts @@ -72,7 +72,6 @@ export * from "./utils/interval.js"; export * from "./config.js"; export { getSchemaParseFn, - getSchemaToJsonSchema, type AnySchemaParseFn, type SchemaParseFn, isSchemaZodEsque, diff --git a/packages/core/src/v3/types/schemas.ts b/packages/core/src/v3/types/schemas.ts index 7c145fef83..e5ae9c3d88 100644 --- a/packages/core/src/v3/types/schemas.ts +++ b/packages/core/src/v3/types/schemas.ts @@ -146,36 +146,3 @@ export function getSchemaParseFn(procedureParser: Schema): SchemaParseFn< throw new Error("Could not find a validator fn"); } - -export function getSchemaToJsonSchema(schema: Schema): any | undefined { - const parser = schema as any; - - // Check if schema has a built-in toJsonSchema method (e.g., ArkType) - if (typeof parser.toJsonSchema === "function") { - return parser.toJsonSchema(); - } - - // Check if it's a Zod schema - if (typeof parser.parseAsync === "function" || typeof parser.parse === "function") { - // Zod schema detected, but we need zod-to-json-schema library to convert - // Return undefined for now, will be handled by the caller - return undefined; - } - - // Check if it's a Yup schema - if (typeof parser.validateSync === "function") { - // Yup schema detected, but we need a yup-to-json-schema library to convert - // Return undefined for now, will be handled by the caller - return undefined; - } - - // Check if it's an Effect schema - if (parser._tag === "Schema" || parser._tag === "SchemaClass") { - // Effect schema detected, but we need Effect's JSONSchema.make to convert - // Return undefined for now, will be handled by the caller - return undefined; - } - - // For other schema types, return undefined - return undefined; -} diff --git a/packages/schema-to-json/README.md b/packages/schema-to-json/README.md new file mode 100644 index 0000000000..945d4101d8 --- /dev/null +++ b/packages/schema-to-json/README.md @@ -0,0 +1,102 @@ +# @trigger.dev/schema-to-json + +Convert various schema validation libraries to JSON Schema format. + +## Installation + +```bash +npm install @trigger.dev/schema-to-json +``` + +## Supported Schema Libraries + +- ✅ **Zod** - Full support via `zod-to-json-schema` +- ✅ **Yup** - Full support via `@sodaru/yup-to-json-schema` +- ✅ **ArkType** - Native support (built-in `toJsonSchema` method) +- ✅ **Effect/Schema** - Full support via Effect's JSONSchema module +- ✅ **TypeBox** - Native support (already JSON Schema compliant) +- ⏳ **Valibot** - Coming soon +- ⏳ **Superstruct** - Coming soon +- ⏳ **Runtypes** - Coming soon + +## Usage + +```typescript +import { schemaToJsonSchema } from '@trigger.dev/schema-to-json'; +import { z } from 'zod'; + +// Convert a Zod schema +const zodSchema = z.object({ + name: z.string(), + age: z.number(), + email: z.string().email(), +}); + +const result = schemaToJsonSchema(zodSchema); +console.log(result); +// { +// jsonSchema: { +// type: 'object', +// properties: { +// name: { type: 'string' }, +// age: { type: 'number' }, +// email: { type: 'string', format: 'email' } +// }, +// required: ['name', 'age', 'email'] +// }, +// schemaType: 'zod' +// } +``` + +## API + +### `schemaToJsonSchema(schema, options?)` + +Convert a schema to JSON Schema format. + +**Parameters:** +- `schema` - The schema to convert +- `options` (optional) + - `name` - Name to use for the schema (supported by some converters) + - `additionalProperties` - Additional properties to merge into the result + +**Returns:** +- `{ jsonSchema, schemaType }` - The converted JSON Schema and detected type +- `undefined` - If the schema cannot be converted + +### `canConvertSchema(schema)` + +Check if a schema can be converted to JSON Schema. + +**Returns:** `boolean` + +### `detectSchemaType(schema)` + +Detect the type of schema. + +**Returns:** `'zod' | 'yup' | 'arktype' | 'effect' | 'valibot' | 'superstruct' | 'runtypes' | 'typebox' | 'unknown'` + +## Peer Dependencies + +Each schema library is an optional peer dependency. Install only the ones you need: + +```bash +# For Zod +npm install zod + +# For Yup +npm install yup + +# For ArkType +npm install arktype + +# For Effect +npm install effect @effect/schema + +# For TypeBox +npm install @sinclair/typebox +``` + +## License + +MIT \ No newline at end of file diff --git a/packages/schema-to-json/package.json b/packages/schema-to-json/package.json new file mode 100644 index 0000000000..0f74d21e55 --- /dev/null +++ b/packages/schema-to-json/package.json @@ -0,0 +1,109 @@ +{ + "name": "@trigger.dev/schema-to-json", + "version": "4.0.0-v4-beta.25", + "description": "Convert various schema validation libraries to JSON Schema", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/triggerdotdev/trigger.dev", + "directory": "packages/schema-to-json" + }, + "type": "module", + "engines": { + "node": ">=18.20.0" + }, + "files": [ + "dist" + ], + "exports": { + ".": { + "import": { + "@triggerdotdev/source": "./src/index.ts", + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "@triggerdotdev/source": "./src/index.ts", + "types": "./dist/commonjs/index.d.ts", + "default": "./dist/commonjs/index.js" + } + } + }, + "scripts": { + "clean": "rimraf dist", + "build": "npm run clean && npm run build:tshy", + "build:tshy": "tshy", + "dev": "tshy --watch", + "typecheck": "tsc -p tsconfig.src.json --noEmit", + "test": "vitest", + "update-version": "changeset version && node scripts/updateVersion.js", + "check-exports": "tshy --check-exports" + }, + "dependencies": { + "zod-to-json-schema": "^3.24.5", + "@sodaru/yup-to-json-schema": "^2.0.1" + }, + "devDependencies": { + "@effect/schema": "^0.76.5", + "arktype": "^2.0.0", + "effect": "^3.11.11", + "runtypes": "^6.7.0", + "superstruct": "^2.0.2", + "tshy": "^3.0.2", + "typebox": "^0.34.3", + "valibot": "^1.0.0-beta.8", + "vitest": "^2.1.8", + "yup": "^1.6.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "@effect/schema": "^0.76.5", + "arktype": "^2.0.0", + "effect": "^3.11.11", + "runtypes": "^6.7.0", + "superstruct": "^2.0.2", + "typebox": "^0.34.3", + "valibot": "^1.0.0-beta.8", + "yup": "^1.6.1", + "zod": "^3.24.1" + }, + "peerDependenciesMeta": { + "@effect/schema": { + "optional": true + }, + "arktype": { + "optional": true + }, + "effect": { + "optional": true + }, + "runtypes": { + "optional": true + }, + "superstruct": { + "optional": true + }, + "typebox": { + "optional": true + }, + "valibot": { + "optional": true + }, + "yup": { + "optional": true + }, + "zod": { + "optional": true + } + }, + "tshy": { + "selfLink": false, + "exports": { + ".": "./src/index.ts" + }, + "project": "./tsconfig.src.json" + } +} \ No newline at end of file diff --git a/packages/schema-to-json/src/__tests__/index.test.ts b/packages/schema-to-json/src/__tests__/index.test.ts new file mode 100644 index 0000000000..253e52d0b6 --- /dev/null +++ b/packages/schema-to-json/src/__tests__/index.test.ts @@ -0,0 +1,301 @@ +import { describe, it, expect } from 'vitest'; +import { z } from 'zod'; +import * as y from 'yup'; +import { type } from 'arktype'; +import { Schema } from '@effect/schema'; +import { Type } from '@sinclair/typebox'; +import { schemaToJsonSchema, canConvertSchema, detectSchemaType } from '../index.js'; + +describe('schemaToJsonSchema', () => { + describe('Zod schemas', () => { + it('should convert a simple Zod object schema', () => { + const schema = z.object({ + name: z.string(), + age: z.number(), + email: z.string().email(), + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.schemaType).toBe('zod'); + expect(result?.jsonSchema).toMatchObject({ + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + email: { type: 'string', format: 'email' }, + }, + required: ['name', 'age', 'email'], + }); + }); + + it('should convert a Zod schema with optional fields', () => { + const schema = z.object({ + id: z.string(), + description: z.string().optional(), + tags: z.array(z.string()).optional(), + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.jsonSchema).toMatchObject({ + type: 'object', + properties: { + id: { type: 'string' }, + description: { type: 'string' }, + tags: { type: 'array', items: { type: 'string' } }, + }, + required: ['id'], + }); + }); + + it('should handle Zod schema with name option', () => { + const schema = z.object({ + value: z.number(), + }); + + const result = schemaToJsonSchema(schema, { name: 'MySchema' }); + + expect(result).toBeDefined(); + expect(result?.jsonSchema).toBeDefined(); + // The exact structure depends on zod-to-json-schema implementation + }); + }); + + describe('Yup schemas', () => { + it('should convert a simple Yup object schema', () => { + const schema = y.object({ + name: y.string().required(), + age: y.number().required(), + email: y.string().email().required(), + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.schemaType).toBe('yup'); + expect(result?.jsonSchema).toMatchObject({ + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + email: { type: 'string', format: 'email' }, + }, + required: ['name', 'age', 'email'], + }); + }); + + it('should convert a Yup schema with optional fields', () => { + const schema = y.object({ + id: y.string().required(), + description: y.string(), + count: y.number().min(0).max(100), + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.jsonSchema).toMatchObject({ + type: 'object', + properties: { + id: { type: 'string' }, + description: { type: 'string' }, + count: { type: 'number', minimum: 0, maximum: 100 }, + }, + required: ['id'], + }); + }); + }); + + describe('ArkType schemas', () => { + it('should convert a simple ArkType schema', () => { + const schema = type({ + name: 'string', + age: 'number', + active: 'boolean', + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.schemaType).toBe('arktype'); + expect(result?.jsonSchema).toBeDefined(); + expect(result?.jsonSchema.type).toBe('object'); + }); + + it('should convert an ArkType schema with optional fields', () => { + const schema = type({ + id: 'string', + 'description?': 'string', + 'tags?': 'string[]', + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.jsonSchema).toBeDefined(); + expect(result?.jsonSchema.type).toBe('object'); + }); + }); + + describe('Effect schemas', () => { + it('should convert a simple Effect schema', () => { + const schema = Schema.Struct({ + name: Schema.String, + age: Schema.Number, + active: Schema.Boolean, + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.schemaType).toBe('effect'); + expect(result?.jsonSchema).toMatchObject({ + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + active: { type: 'boolean' }, + }, + required: ['name', 'age', 'active'], + }); + }); + + it('should convert an Effect schema with optional fields', () => { + const schema = Schema.Struct({ + id: Schema.String, + description: Schema.optional(Schema.String), + count: Schema.optional(Schema.Number), + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.jsonSchema).toBeDefined(); + expect(result?.jsonSchema.type).toBe('object'); + }); + }); + + describe('TypeBox schemas', () => { + it('should convert a simple TypeBox schema', () => { + const schema = Type.Object({ + name: Type.String(), + age: Type.Number(), + active: Type.Boolean(), + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.schemaType).toBe('typebox'); + expect(result?.jsonSchema).toMatchObject({ + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + active: { type: 'boolean' }, + }, + required: ['name', 'age', 'active'], + }); + }); + + it('should convert a TypeBox schema with optional fields', () => { + const schema = Type.Object({ + id: Type.String(), + description: Type.Optional(Type.String()), + tags: Type.Optional(Type.Array(Type.String())), + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.jsonSchema).toMatchObject({ + type: 'object', + properties: { + id: { type: 'string' }, + description: { type: 'string' }, + tags: { type: 'array', items: { type: 'string' } }, + }, + required: ['id'], + }); + }); + }); + + describe('Additional options', () => { + it('should merge additional properties', () => { + const schema = z.object({ + value: z.number(), + }); + + const result = schemaToJsonSchema(schema, { + additionalProperties: { + title: 'My Schema', + description: 'A test schema', + 'x-custom': 'custom value', + }, + }); + + expect(result).toBeDefined(); + expect(result?.jsonSchema.title).toBe('My Schema'); + expect(result?.jsonSchema.description).toBe('A test schema'); + expect(result?.jsonSchema['x-custom']).toBe('custom value'); + }); + }); + + describe('Unsupported schemas', () => { + it('should return undefined for unsupported schema types', () => { + const invalidSchema = { notASchema: true }; + const result = schemaToJsonSchema(invalidSchema); + expect(result).toBeUndefined(); + }); + + it('should return undefined for plain functions', () => { + const fn = (value: unknown) => typeof value === 'string'; + const result = schemaToJsonSchema(fn); + expect(result).toBeUndefined(); + }); + }); +}); + +describe('canConvertSchema', () => { + it('should return true for supported schemas', () => { + expect(canConvertSchema(z.string())).toBe(true); + expect(canConvertSchema(y.string())).toBe(true); + expect(canConvertSchema(type('string'))).toBe(true); + expect(canConvertSchema(Schema.String)).toBe(true); + expect(canConvertSchema(Type.String())).toBe(true); + }); + + it('should return false for unsupported schemas', () => { + expect(canConvertSchema({ notASchema: true })).toBe(false); + expect(canConvertSchema(() => true)).toBe(false); + }); +}); + +describe('detectSchemaType', () => { + it('should detect Zod schemas', () => { + expect(detectSchemaType(z.string())).toBe('zod'); + }); + + it('should detect Yup schemas', () => { + expect(detectSchemaType(y.string())).toBe('yup'); + }); + + it('should detect ArkType schemas', () => { + expect(detectSchemaType(type('string'))).toBe('arktype'); + }); + + it('should detect Effect schemas', () => { + expect(detectSchemaType(Schema.String)).toBe('effect'); + }); + + it('should detect TypeBox schemas', () => { + expect(detectSchemaType(Type.String())).toBe('typebox'); + }); + + it('should return unknown for unsupported schemas', () => { + expect(detectSchemaType({ notASchema: true })).toBe('unknown'); + }); +}); \ No newline at end of file diff --git a/packages/schema-to-json/src/index.ts b/packages/schema-to-json/src/index.ts new file mode 100644 index 0000000000..1724a60090 --- /dev/null +++ b/packages/schema-to-json/src/index.ts @@ -0,0 +1,151 @@ +export type Schema = unknown; + +export interface ConversionOptions { + /** + * The name to use for the schema in the JSON Schema + */ + name?: string; + /** + * Additional JSON Schema properties to merge + */ + additionalProperties?: Record; +} + +export interface ConversionResult { + /** + * The JSON Schema representation + */ + jsonSchema: any; + /** + * The detected schema type + */ + schemaType: 'zod' | 'yup' | 'arktype' | 'effect' | 'valibot' | 'superstruct' | 'runtypes' | 'typebox' | 'unknown'; +} + +/** + * Convert a schema from various validation libraries to JSON Schema + */ +export function schemaToJsonSchema(schema: Schema, options?: ConversionOptions): ConversionResult | undefined { + const parser = schema as any; + + // Check if schema has a built-in toJsonSchema method (e.g., ArkType) + if (typeof parser.toJsonSchema === "function") { + const jsonSchema = parser.toJsonSchema(); + return { + jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, + schemaType: 'arktype' + }; + } + + // Check if it's a TypeBox schema (has Static and Kind symbols) + if (parser[Symbol.for('TypeBox.Kind')] !== undefined) { + // TypeBox schemas are already JSON Schema compliant + return { + jsonSchema: options?.additionalProperties ? { ...parser, ...options.additionalProperties } : parser, + schemaType: 'typebox' + }; + } + + // Check if it's a Zod schema + if (typeof parser.parseAsync === "function" || typeof parser.parse === "function") { + try { + const { zodToJsonSchema } = require('zod-to-json-schema'); + const jsonSchema = options?.name + ? zodToJsonSchema(parser, options.name) + : zodToJsonSchema(parser); + + if (jsonSchema && typeof jsonSchema === 'object' && '$schema' in jsonSchema) { + // Remove the $schema property as it's not needed for our use case + const { $schema, ...rest } = jsonSchema as any; + return { + jsonSchema: options?.additionalProperties ? { ...rest, ...options.additionalProperties } : rest, + schemaType: 'zod' + }; + } + + return { + jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, + schemaType: 'zod' + }; + } catch (error) { + console.warn('Failed to convert Zod schema to JSON Schema:', error); + return undefined; + } + } + + // Check if it's a Yup schema + if (typeof parser.validateSync === "function" && typeof parser.describe === "function") { + try { + const { convertSchema } = require('@sodaru/yup-to-json-schema'); + const jsonSchema = convertSchema(parser); + return { + jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, + schemaType: 'yup' + }; + } catch (error) { + console.warn('Failed to convert Yup schema to JSON Schema:', error); + return undefined; + } + } + + // Check if it's an Effect schema + if (parser._tag === "Schema" || parser._tag === "SchemaClass" || typeof parser.ast === "function") { + try { + // Try to load Effect's JSONSchema module + const effectModule = require('effect'); + const schemaModule = require('@effect/schema'); + + if (effectModule?.JSONSchema && schemaModule?.JSONSchema) { + const JSONSchema = schemaModule.JSONSchema || effectModule.JSONSchema; + const jsonSchema = JSONSchema.make(parser); + return { + jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, + schemaType: 'effect' + }; + } + } catch (error) { + console.warn('Failed to convert Effect schema to JSON Schema:', error); + return undefined; + } + } + + // Check if it's a Valibot schema + if (typeof parser === "function" && parser._def?.kind !== undefined) { + // Valibot doesn't have built-in JSON Schema conversion yet + // We could implement a basic converter for common types + return undefined; + } + + // Check if it's a Superstruct schema + if (typeof parser.create === "function" && parser.TYPE !== undefined) { + // Superstruct doesn't have built-in JSON Schema conversion + // We could implement a basic converter for common types + return undefined; + } + + // Check if it's a Runtypes schema + if (typeof parser.guard === "function" && parser._tag !== undefined) { + // Runtypes doesn't have built-in JSON Schema conversion + // We could implement a basic converter for common types + return undefined; + } + + // Unknown schema type + return undefined; +} + +/** + * Check if a schema can be converted to JSON Schema + */ +export function canConvertSchema(schema: Schema): boolean { + const result = schemaToJsonSchema(schema); + return result !== undefined; +} + +/** + * Get the detected schema type + */ +export function detectSchemaType(schema: Schema): ConversionResult['schemaType'] { + const result = schemaToJsonSchema(schema); + return result?.schemaType ?? 'unknown'; +} \ No newline at end of file diff --git a/packages/schema-to-json/tsconfig.json b/packages/schema-to-json/tsconfig.json new file mode 100644 index 0000000000..5bf5eba8d5 --- /dev/null +++ b/packages/schema-to-json/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../.configs/tsconfig.base.json", + "references": [ + { + "path": "./tsconfig.src.json" + }, + { + "path": "./tsconfig.test.json" + } + ] +} \ No newline at end of file diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index c42cd98d36..e37691fe94 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -53,6 +53,7 @@ "@opentelemetry/api-logs": "0.52.1", "@opentelemetry/semantic-conventions": "1.25.1", "@trigger.dev/core": "workspace:4.0.0-v4-beta.25", + "@trigger.dev/schema-to-json": "workspace:4.0.0-v4-beta.25", "chalk": "^5.2.0", "cronstrue": "^2.21.0", "debug": "^4.3.4", diff --git a/packages/trigger-sdk/src/v3/shared.ts b/packages/trigger-sdk/src/v3/shared.ts index 3a490a1723..59351dd921 100644 --- a/packages/trigger-sdk/src/v3/shared.ts +++ b/packages/trigger-sdk/src/v3/shared.ts @@ -11,7 +11,6 @@ import { flattenIdempotencyKey, getEnvVar, getSchemaParseFn, - getSchemaToJsonSchema, InitOutput, lifecycleHooks, makeIdempotencyKey, @@ -29,6 +28,7 @@ import { TaskRunExecutionResult, TaskRunPromise, } from "@trigger.dev/core/v3"; +import { schemaToJsonSchema } from "@trigger.dev/schema-to-json"; import { PollOptions, runs } from "./runs.js"; import { tracer } from "./tracer.js"; @@ -336,7 +336,7 @@ export function createSchemaTask< retry: params.retry ? { ...defaultRetryOptions, ...params.retry } : undefined, machine: typeof params.machine === "string" ? { preset: params.machine } : params.machine, maxDuration: params.maxDuration, - payloadSchema: params.schema ? getSchemaToJsonSchema(params.schema) : undefined, + payloadSchema: params.schema ? schemaToJsonSchema(params.schema)?.jsonSchema : undefined, fns: { run: params.run, parsePayload, From 87ed9c4a67c3038c2ba8576473dd3dbb461489e6 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Sun, 27 Jul 2025 15:48:50 +0100 Subject: [PATCH 03/19] Add support for Zod 4 in schema-to-json This change enhances the schema-to-json package by adding support for Zod version 4, which introduces the native `toJsonSchema` method. This method facilitates a direct conversion of Zod schemas to JSON Schema format, improving performance and reducing reliance on the `zod-to-json-schema` library. - Updated README to reflect Zod 4 support with native method and retained support for Zod 3 via existing library. - Modified package.json to allow installation of both Zod 3 and 4 versions. - Implemented handling for Zod 4 schemas in `src/index.ts` using their native method. - Added a test case to verify the proper conversion of Zod 4 schemas to JSON Schema. - Included a script for updating the package version based on the root package.json. - Introduced a specific TypeScript config for source files. --- packages/schema-to-json/README.md | 4 ++- packages/schema-to-json/package.json | 4 +-- .../schema-to-json/scripts/updateVersion.js | 24 +++++++++++++++ .../src/__tests__/index.test.ts | 29 +++++++++++++++++++ packages/schema-to-json/src/index.ts | 16 +++++++++- packages/schema-to-json/tsconfig.src.json | 10 +++++++ 6 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 packages/schema-to-json/scripts/updateVersion.js create mode 100644 packages/schema-to-json/tsconfig.src.json diff --git a/packages/schema-to-json/README.md b/packages/schema-to-json/README.md index 945d4101d8..de6a79d2b3 100644 --- a/packages/schema-to-json/README.md +++ b/packages/schema-to-json/README.md @@ -10,7 +10,9 @@ npm install @trigger.dev/schema-to-json ## Supported Schema Libraries -- ✅ **Zod** - Full support via `zod-to-json-schema` +- ✅ **Zod** - Full support + - Zod 4: Native support via built-in `toJsonSchema` method + - Zod 3: Support via `zod-to-json-schema` library - ✅ **Yup** - Full support via `@sodaru/yup-to-json-schema` - ✅ **ArkType** - Native support (built-in `toJsonSchema` method) - ✅ **Effect/Schema** - Full support via Effect's JSONSchema module diff --git a/packages/schema-to-json/package.json b/packages/schema-to-json/package.json index 0f74d21e55..47ef1074ba 100644 --- a/packages/schema-to-json/package.json +++ b/packages/schema-to-json/package.json @@ -57,7 +57,7 @@ "valibot": "^1.0.0-beta.8", "vitest": "^2.1.8", "yup": "^1.6.1", - "zod": "^3.24.1" + "zod": "^3.24.1 || ^4.0.0" }, "peerDependencies": { "@effect/schema": "^0.76.5", @@ -68,7 +68,7 @@ "typebox": "^0.34.3", "valibot": "^1.0.0-beta.8", "yup": "^1.6.1", - "zod": "^3.24.1" + "zod": "^3.24.1 || ^4.0.0" }, "peerDependenciesMeta": { "@effect/schema": { diff --git a/packages/schema-to-json/scripts/updateVersion.js b/packages/schema-to-json/scripts/updateVersion.js new file mode 100644 index 0000000000..ed0ab53841 --- /dev/null +++ b/packages/schema-to-json/scripts/updateVersion.js @@ -0,0 +1,24 @@ +#!/usr/bin/env node + +import { readFileSync, writeFileSync } from "fs"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Read the package.json +const packageJsonPath = join(__dirname, "..", "package.json"); +const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")); + +// Read the root package.json to get the version +const rootPackageJsonPath = join(__dirname, "..", "..", "..", "package.json"); +const rootPackageJson = JSON.parse(readFileSync(rootPackageJsonPath, "utf-8")); + +// Update the version +packageJson.version = rootPackageJson.version; + +// Write back the package.json +writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n"); + +console.log(`Updated @trigger.dev/schema-to-json version to ${packageJson.version}`); \ No newline at end of file diff --git a/packages/schema-to-json/src/__tests__/index.test.ts b/packages/schema-to-json/src/__tests__/index.test.ts index 253e52d0b6..968785acd7 100644 --- a/packages/schema-to-json/src/__tests__/index.test.ts +++ b/packages/schema-to-json/src/__tests__/index.test.ts @@ -62,6 +62,35 @@ describe('schemaToJsonSchema', () => { expect(result?.jsonSchema).toBeDefined(); // The exact structure depends on zod-to-json-schema implementation }); + + it('should handle Zod 4 schema with built-in toJsonSchema method', () => { + // Mock a Zod 4 schema with toJsonSchema method + const mockZod4Schema = { + parse: (val: unknown) => val, + parseAsync: async (val: unknown) => val, + toJsonSchema: () => ({ + type: 'object', + properties: { + id: { type: 'string' }, + count: { type: 'number' } + }, + required: ['id', 'count'] + }) + }; + + const result = schemaToJsonSchema(mockZod4Schema); + + expect(result).toBeDefined(); + expect(result?.schemaType).toBe('zod'); + expect(result?.jsonSchema).toEqual({ + type: 'object', + properties: { + id: { type: 'string' }, + count: { type: 'number' } + }, + required: ['id', 'count'] + }); + }); }); describe('Yup schemas', () => { diff --git a/packages/schema-to-json/src/index.ts b/packages/schema-to-json/src/index.ts index 1724a60090..e3dd7ea5b6 100644 --- a/packages/schema-to-json/src/index.ts +++ b/packages/schema-to-json/src/index.ts @@ -48,6 +48,20 @@ export function schemaToJsonSchema(schema: Schema, options?: ConversionOptions): // Check if it's a Zod schema if (typeof parser.parseAsync === "function" || typeof parser.parse === "function") { + // Check if it's Zod 4 with built-in toJsonSchema method + if (typeof parser.toJsonSchema === "function") { + try { + const jsonSchema = parser.toJsonSchema(); + return { + jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, + schemaType: 'zod' + }; + } catch (error) { + console.warn('Failed to convert Zod 4 schema using built-in toJsonSchema:', error); + } + } + + // Fall back to zod-to-json-schema library (for Zod 3 or if built-in method fails) try { const { zodToJsonSchema } = require('zod-to-json-schema'); const jsonSchema = options?.name @@ -68,7 +82,7 @@ export function schemaToJsonSchema(schema: Schema, options?: ConversionOptions): schemaType: 'zod' }; } catch (error) { - console.warn('Failed to convert Zod schema to JSON Schema:', error); + console.warn('Failed to convert Zod schema to JSON Schema using zod-to-json-schema:', error); return undefined; } } diff --git a/packages/schema-to-json/tsconfig.src.json b/packages/schema-to-json/tsconfig.src.json new file mode 100644 index 0000000000..18167f8eda --- /dev/null +++ b/packages/schema-to-json/tsconfig.src.json @@ -0,0 +1,10 @@ +{ + "extends": "../../.configs/tsconfig.base.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "types": ["node"] + }, + "include": ["./src/**/*.ts"], + "exclude": ["./src/**/*.test.ts"] +} \ No newline at end of file From eb81722cd51f107ad022500e0f1112b201c452bc Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Sun, 27 Jul 2025 19:26:36 +0100 Subject: [PATCH 04/19] Revise schema-to-json for bundle safety and tests The package @trigger.dev/schema-to-json has been revised to ensure bundle safety by removing direct dependencies on schema libraries such as Zod, Yup, and Effect. This change minimizes bundle size and enhances tree-shaking by allowing external conversion libraries to be utilized only at runtime if necessary. As a result, the README was updated to reflect this usage pattern. - Introduced `initializeSchemaConverters` function to load necessary conversion libraries at runtime, keeping the base package slim. - Adjusted test suite to initialize converters before tests, ensuring accurate testing of schema conversion capabilities. - Updated `schemaToJsonSchema` function to dynamically check for availability of conversion libraries, improving flexibility without increasing the package size. - Added configuration files for Vitest to support the new testing framework, reflecting the transition from previous test setups. These enhancements ensure that only the schema libraries actively used in an application are bundled, optimizing performance and resource usage. --- packages/schema-to-json/.gitignore | 6 + packages/schema-to-json/README.md | 57 +++++- .../src/__tests__/index.test.ts | 24 ++- packages/schema-to-json/src/index.ts | 185 +++++++++++------- packages/schema-to-json/tsconfig.test.json | 8 + packages/schema-to-json/vitest.config.ts | 8 + 6 files changed, 210 insertions(+), 78 deletions(-) create mode 100644 packages/schema-to-json/.gitignore create mode 100644 packages/schema-to-json/tsconfig.test.json create mode 100644 packages/schema-to-json/vitest.config.ts diff --git a/packages/schema-to-json/.gitignore b/packages/schema-to-json/.gitignore new file mode 100644 index 0000000000..c887152393 --- /dev/null +++ b/packages/schema-to-json/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist +.tshy +.tshy-build +*.log +.DS_Store \ No newline at end of file diff --git a/packages/schema-to-json/README.md b/packages/schema-to-json/README.md index de6a79d2b3..9576b6e800 100644 --- a/packages/schema-to-json/README.md +++ b/packages/schema-to-json/README.md @@ -8,14 +8,27 @@ Convert various schema validation libraries to JSON Schema format. npm install @trigger.dev/schema-to-json ``` +## Important: Bundle Safety + +This package is designed to be **bundle-safe**. It does NOT bundle any schema libraries (zod, yup, etc.) as dependencies. Instead: + +1. **Built-in conversions** work immediately (ArkType, Zod 4, TypeBox) +2. **External conversions** (Zod 3, Yup, Effect) require the conversion libraries to be available at runtime + +This design ensures that: +- ✅ Your bundle size stays small +- ✅ You only include the schema libraries you actually use +- ✅ Tree-shaking works properly +- ✅ No unnecessary dependencies are installed + ## Supported Schema Libraries - ✅ **Zod** - Full support - - Zod 4: Native support via built-in `toJsonSchema` method - - Zod 3: Support via `zod-to-json-schema` library -- ✅ **Yup** - Full support via `@sodaru/yup-to-json-schema` + - Zod 4: Native support via built-in `toJsonSchema` method (no external deps needed) + - Zod 3: Requires `zod-to-json-schema` to be installed +- ✅ **Yup** - Requires `@sodaru/yup-to-json-schema` to be installed - ✅ **ArkType** - Native support (built-in `toJsonSchema` method) -- ✅ **Effect/Schema** - Full support via Effect's JSONSchema module +- ✅ **Effect/Schema** - Requires `effect` or `@effect/schema` to be installed - ✅ **TypeBox** - Native support (already JSON Schema compliant) - ⏳ **Valibot** - Coming soon - ⏳ **Superstruct** - Coming soon @@ -23,11 +36,33 @@ npm install @trigger.dev/schema-to-json ## Usage +### Basic Usage (Built-in conversions only) + ```typescript import { schemaToJsonSchema } from '@trigger.dev/schema-to-json'; +import { type } from 'arktype'; + +// Works immediately for schemas with built-in conversion +const arkSchema = type({ + name: 'string', + age: 'number', +}); + +const result = schemaToJsonSchema(arkSchema); +console.log(result); +// { jsonSchema: {...}, schemaType: 'arktype' } +``` + +### Full Usage (With external conversion libraries) + +```typescript +import { schemaToJsonSchema, initializeSchemaConverters } from '@trigger.dev/schema-to-json'; import { z } from 'zod'; -// Convert a Zod schema +// Initialize converters once in your app (loads conversion libraries if available) +await initializeSchemaConverters(); + +// Now you can convert Zod 3, Yup, and Effect schemas const zodSchema = z.object({ name: z.string(), age: z.number(), @@ -66,6 +101,12 @@ Convert a schema to JSON Schema format. - `{ jsonSchema, schemaType }` - The converted JSON Schema and detected type - `undefined` - If the schema cannot be converted +### `initializeSchemaConverters()` + +Initialize the external conversion libraries. Call this once in your application if you need to convert schemas that don't have built-in JSON Schema support (Zod 3, Yup, Effect). + +**Returns:** `Promise` + ### `canConvertSchema(schema)` Check if a schema can be converted to JSON Schema. @@ -78,6 +119,12 @@ Detect the type of schema. **Returns:** `'zod' | 'yup' | 'arktype' | 'effect' | 'valibot' | 'superstruct' | 'runtypes' | 'typebox' | 'unknown'` +### `areConvertersInitialized()` + +Check which conversion libraries are available. + +**Returns:** `{ zod: boolean, yup: boolean, effect: boolean }` + ## Peer Dependencies Each schema library is an optional peer dependency. Install only the ones you need: diff --git a/packages/schema-to-json/src/__tests__/index.test.ts b/packages/schema-to-json/src/__tests__/index.test.ts index 968785acd7..6e71a5b149 100644 --- a/packages/schema-to-json/src/__tests__/index.test.ts +++ b/packages/schema-to-json/src/__tests__/index.test.ts @@ -1,12 +1,32 @@ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, beforeAll } from 'vitest'; import { z } from 'zod'; import * as y from 'yup'; import { type } from 'arktype'; import { Schema } from '@effect/schema'; import { Type } from '@sinclair/typebox'; -import { schemaToJsonSchema, canConvertSchema, detectSchemaType } from '../index.js'; +import { + schemaToJsonSchema, + canConvertSchema, + detectSchemaType, + initializeSchemaConverters, + areConvertersInitialized +} from '../index.js'; + +// Initialize converters before running tests +beforeAll(async () => { + await initializeSchemaConverters(); +}); describe('schemaToJsonSchema', () => { + describe('Initialization', () => { + it('should have converters initialized', () => { + const status = areConvertersInitialized(); + expect(status.zod).toBe(true); + expect(status.yup).toBe(true); + expect(status.effect).toBe(true); + }); + }); + describe('Zod schemas', () => { it('should convert a simple Zod object schema', () => { const schema = z.object({ diff --git a/packages/schema-to-json/src/index.ts b/packages/schema-to-json/src/index.ts index e3dd7ea5b6..1762fbbb95 100644 --- a/packages/schema-to-json/src/index.ts +++ b/packages/schema-to-json/src/index.ts @@ -24,17 +24,32 @@ export interface ConversionResult { /** * Convert a schema from various validation libraries to JSON Schema + * + * This function attempts to convert schemas without requiring external dependencies to be bundled. + * It will only succeed if: + * 1. The schema has built-in JSON Schema conversion (ArkType, Zod 4, TypeBox) + * 2. The required conversion library is available at runtime (zod-to-json-schema, @sodaru/yup-to-json-schema, etc.) + * + * @param schema The schema to convert + * @param options Optional conversion options + * @returns The conversion result or undefined if conversion is not possible */ export function schemaToJsonSchema(schema: Schema, options?: ConversionOptions): ConversionResult | undefined { const parser = schema as any; - // Check if schema has a built-in toJsonSchema method (e.g., ArkType) + // Check if schema has a built-in toJsonSchema method (e.g., ArkType, Zod 4) if (typeof parser.toJsonSchema === "function") { - const jsonSchema = parser.toJsonSchema(); - return { - jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, - schemaType: 'arktype' - }; + try { + const jsonSchema = parser.toJsonSchema(); + // Determine if it's Zod or ArkType based on other methods + const schemaType = (typeof parser.parseAsync === "function" || typeof parser.parse === "function") ? 'zod' : 'arktype'; + return { + jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, + schemaType + }; + } catch (error) { + // If toJsonSchema fails, continue to other checks + } } // Check if it's a TypeBox schema (has Static and Kind symbols) @@ -46,71 +61,64 @@ export function schemaToJsonSchema(schema: Schema, options?: ConversionOptions): }; } - // Check if it's a Zod schema + // For schemas that need external libraries, we need to check if they're available + // This approach avoids bundling the dependencies while still allowing runtime usage + + // Check if it's a Zod schema (without built-in toJsonSchema) if (typeof parser.parseAsync === "function" || typeof parser.parse === "function") { - // Check if it's Zod 4 with built-in toJsonSchema method - if (typeof parser.toJsonSchema === "function") { - try { - const jsonSchema = parser.toJsonSchema(); - return { - jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, - schemaType: 'zod' - }; - } catch (error) { - console.warn('Failed to convert Zod 4 schema using built-in toJsonSchema:', error); - } - } - - // Fall back to zod-to-json-schema library (for Zod 3 or if built-in method fails) try { - const { zodToJsonSchema } = require('zod-to-json-schema'); - const jsonSchema = options?.name - ? zodToJsonSchema(parser, options.name) - : zodToJsonSchema(parser); - - if (jsonSchema && typeof jsonSchema === 'object' && '$schema' in jsonSchema) { - // Remove the $schema property as it's not needed for our use case - const { $schema, ...rest } = jsonSchema as any; + // Try to access zod-to-json-schema if it's available + // @ts-ignore - This is intentionally dynamic + if (typeof globalThis.__zodToJsonSchema !== 'undefined') { + // @ts-ignore + const { zodToJsonSchema } = globalThis.__zodToJsonSchema; + const jsonSchema = options?.name + ? zodToJsonSchema(parser, options.name) + : zodToJsonSchema(parser); + + if (jsonSchema && typeof jsonSchema === 'object' && '$schema' in jsonSchema) { + const { $schema, ...rest } = jsonSchema as any; + return { + jsonSchema: options?.additionalProperties ? { ...rest, ...options.additionalProperties } : rest, + schemaType: 'zod' + }; + } + return { - jsonSchema: options?.additionalProperties ? { ...rest, ...options.additionalProperties } : rest, + jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, schemaType: 'zod' }; } - - return { - jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, - schemaType: 'zod' - }; } catch (error) { - console.warn('Failed to convert Zod schema to JSON Schema using zod-to-json-schema:', error); - return undefined; + // Library not available } } // Check if it's a Yup schema if (typeof parser.validateSync === "function" && typeof parser.describe === "function") { try { - const { convertSchema } = require('@sodaru/yup-to-json-schema'); - const jsonSchema = convertSchema(parser); - return { - jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, - schemaType: 'yup' - }; + // @ts-ignore + if (typeof globalThis.__yupToJsonSchema !== 'undefined') { + // @ts-ignore + const { convertSchema } = globalThis.__yupToJsonSchema; + const jsonSchema = convertSchema(parser); + return { + jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, + schemaType: 'yup' + }; + } } catch (error) { - console.warn('Failed to convert Yup schema to JSON Schema:', error); - return undefined; + // Library not available } } // Check if it's an Effect schema if (parser._tag === "Schema" || parser._tag === "SchemaClass" || typeof parser.ast === "function") { try { - // Try to load Effect's JSONSchema module - const effectModule = require('effect'); - const schemaModule = require('@effect/schema'); - - if (effectModule?.JSONSchema && schemaModule?.JSONSchema) { - const JSONSchema = schemaModule.JSONSchema || effectModule.JSONSchema; + // @ts-ignore + if (typeof globalThis.__effectJsonSchema !== 'undefined') { + // @ts-ignore + const { JSONSchema } = globalThis.__effectJsonSchema; const jsonSchema = JSONSchema.make(parser); return { jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, @@ -118,34 +126,51 @@ export function schemaToJsonSchema(schema: Schema, options?: ConversionOptions): }; } } catch (error) { - console.warn('Failed to convert Effect schema to JSON Schema:', error); - return undefined; + // Library not available } } - // Check if it's a Valibot schema - if (typeof parser === "function" && parser._def?.kind !== undefined) { - // Valibot doesn't have built-in JSON Schema conversion yet - // We could implement a basic converter for common types - return undefined; - } + // Future schema types can be added here... - // Check if it's a Superstruct schema - if (typeof parser.create === "function" && parser.TYPE !== undefined) { - // Superstruct doesn't have built-in JSON Schema conversion - // We could implement a basic converter for common types - return undefined; + // Unknown schema type + return undefined; +} + +/** + * Initialize the schema conversion libraries + * This should be called by the consuming application if they want to enable + * conversion for schemas that don't have built-in JSON Schema support + */ +export async function initializeSchemaConverters(): Promise { + try { + // @ts-ignore + globalThis.__zodToJsonSchema = await import('zod-to-json-schema'); + } catch { + // Zod conversion not available } - // Check if it's a Runtypes schema - if (typeof parser.guard === "function" && parser._tag !== undefined) { - // Runtypes doesn't have built-in JSON Schema conversion - // We could implement a basic converter for common types - return undefined; + try { + // @ts-ignore + globalThis.__yupToJsonSchema = await import('@sodaru/yup-to-json-schema'); + } catch { + // Yup conversion not available } - // Unknown schema type - return undefined; + try { + // Try Effect first, then @effect/schema + let module; + try { + module = await import('effect'); + } catch { + module = await import('@effect/schema'); + } + if (module?.JSONSchema) { + // @ts-ignore + globalThis.__effectJsonSchema = { JSONSchema: module.JSONSchema }; + } + } catch { + // Effect conversion not available + } } /** @@ -162,4 +187,22 @@ export function canConvertSchema(schema: Schema): boolean { export function detectSchemaType(schema: Schema): ConversionResult['schemaType'] { const result = schemaToJsonSchema(schema); return result?.schemaType ?? 'unknown'; +} + +/** + * Check if the conversion libraries are initialized + */ +export function areConvertersInitialized(): { + zod: boolean; + yup: boolean; + effect: boolean; +} { + return { + // @ts-ignore + zod: typeof globalThis.__zodToJsonSchema !== 'undefined', + // @ts-ignore + yup: typeof globalThis.__yupToJsonSchema !== 'undefined', + // @ts-ignore + effect: typeof globalThis.__effectJsonSchema !== 'undefined', + }; } \ No newline at end of file diff --git a/packages/schema-to-json/tsconfig.test.json b/packages/schema-to-json/tsconfig.test.json new file mode 100644 index 0000000000..1db15ed6ce --- /dev/null +++ b/packages/schema-to-json/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "../../.configs/tsconfig.base.json", + "compilerOptions": { + "rootDir": "./", + "types": ["node", "vitest/globals"] + }, + "include": ["./src/**/*.test.ts", "./test/**/*.ts"] +} \ No newline at end of file diff --git a/packages/schema-to-json/vitest.config.ts b/packages/schema-to-json/vitest.config.ts new file mode 100644 index 0000000000..c7da6b38e1 --- /dev/null +++ b/packages/schema-to-json/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + }, +}); \ No newline at end of file From 74ec4063b06fb7c63cddc682f444e605f6b7f882 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Sun, 27 Jul 2025 20:14:22 +0100 Subject: [PATCH 05/19] Refine JSON Schema typing across packages The changes introduce stricter typing for JSON Schema-related definitions, specifically replacing vague types with more precise ones, such as using `z.record(z.unknown())` instead of `z.any()` and `Record` in place of `any`. This is part of an effort to better align with common practices and improve type safety in the packages. - Updated the `payloadSchema` in several files to use `z.record(z.unknown())`, enhancing the type strictness and consistency with JSON Schema Draft 7 recommendations. - Added `@types/json-schema` as a dependency, utilizing its definitions for improved type clarity and adherence to best practices in TypeScript. - Modified various comments to explicitly mention JSON Schema Draft 7, ensuring developers are aware of the JSON Schema version being implemented. - These adjustments are informed by research into how popular libraries and tools handle JSON Schema typing, aiming to integrate best practices for improved maintainability and interoperability. --- packages/core/src/v3/schemas/resources.ts | 2 +- packages/core/src/v3/schemas/schemas.ts | 2 +- packages/core/src/v3/types/tasks.ts | 3 ++- packages/schema-to-json/package.json | 1 + packages/schema-to-json/src/index.ts | 8 ++++++-- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/core/src/v3/schemas/resources.ts b/packages/core/src/v3/schemas/resources.ts index 1af03f47b7..ff015e39d1 100644 --- a/packages/core/src/v3/schemas/resources.ts +++ b/packages/core/src/v3/schemas/resources.ts @@ -13,7 +13,7 @@ export const TaskResource = z.object({ triggerSource: z.string().optional(), schedule: ScheduleMetadata.optional(), maxDuration: z.number().optional(), - payloadSchema: z.any().optional(), + payloadSchema: z.record(z.unknown()).optional(), }); export type TaskResource = z.infer; diff --git a/packages/core/src/v3/schemas/schemas.ts b/packages/core/src/v3/schemas/schemas.ts index 5fd8ef3f89..699be74bd4 100644 --- a/packages/core/src/v3/schemas/schemas.ts +++ b/packages/core/src/v3/schemas/schemas.ts @@ -189,7 +189,7 @@ const taskMetadata = { triggerSource: z.string().optional(), schedule: ScheduleMetadata.optional(), maxDuration: z.number().optional(), - payloadSchema: z.any().optional(), + payloadSchema: z.record(z.unknown()).optional(), }; export const TaskMetadata = z.object(taskMetadata); diff --git a/packages/core/src/v3/types/tasks.ts b/packages/core/src/v3/types/tasks.ts index d02c121c13..67bed2ddf4 100644 --- a/packages/core/src/v3/types/tasks.ts +++ b/packages/core/src/v3/types/tasks.ts @@ -342,8 +342,9 @@ type CommonTaskOptions< /** * JSON Schema for the task payload. This will be synced to the server during indexing. + * Should be a valid JSON Schema Draft 7 object. */ - payloadSchema?: any; + payloadSchema?: Record; }; export type TaskOptions< diff --git a/packages/schema-to-json/package.json b/packages/schema-to-json/package.json index 47ef1074ba..b55c89487e 100644 --- a/packages/schema-to-json/package.json +++ b/packages/schema-to-json/package.json @@ -43,6 +43,7 @@ "check-exports": "tshy --check-exports" }, "dependencies": { + "@types/json-schema": "^7.0.15", "zod-to-json-schema": "^3.24.5", "@sodaru/yup-to-json-schema": "^2.0.1" }, diff --git a/packages/schema-to-json/src/index.ts b/packages/schema-to-json/src/index.ts index 1762fbbb95..5190e34310 100644 --- a/packages/schema-to-json/src/index.ts +++ b/packages/schema-to-json/src/index.ts @@ -1,4 +1,8 @@ +import type { JSONSchema7, JSONSchema7Definition } from '@types/json-schema'; + export type Schema = unknown; +export type JSONSchema = JSONSchema7; +export type JSONSchemaDefinition = JSONSchema7Definition; export interface ConversionOptions { /** @@ -13,9 +17,9 @@ export interface ConversionOptions { export interface ConversionResult { /** - * The JSON Schema representation + * The JSON Schema representation (JSON Schema Draft 7) */ - jsonSchema: any; + jsonSchema: JSONSchema; /** * The detected schema type */ From 088185b05b7a8ea695681e648e6e57ec5e036f6d Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Sun, 27 Jul 2025 20:36:14 +0100 Subject: [PATCH 06/19] Add JSON Schema examples using various libraries The change introduces extensive examples of using JSON Schemas in the 'references/hello-world' project within the 'trigger.dev' repository. These examples utilize libraries like Zod, Yup, and TypeBox for JSON Schema conversion and validation. The new examples demonstrate different use cases, including automatic conversion with schemaTask, manual schema provision, and schema conversion at build time. We also updated the dependencies in 'package.json' to include the necessary libraries for schema conversion and validation. - Included examples of processing tasks with JSON Schema using libraries such as Zod, Yup, TypeBox, and ArkType. - Showcased schema conversion techniques and type-safe JSON Schema creation. - Updated 'package.json' to ensure all necessary dependencies for schema operations are available. - Created illustrative scripts that cover task management from user processing to complex schema implementations. --- packages/schema-to-json/examples/usage.ts | 107 +++++ references/hello-world/package.json | 6 +- .../hello-world/src/trigger/jsonSchema.ts | 398 ++++++++++++++++++ 3 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 packages/schema-to-json/examples/usage.ts create mode 100644 references/hello-world/src/trigger/jsonSchema.ts diff --git a/packages/schema-to-json/examples/usage.ts b/packages/schema-to-json/examples/usage.ts new file mode 100644 index 0000000000..3049b18265 --- /dev/null +++ b/packages/schema-to-json/examples/usage.ts @@ -0,0 +1,107 @@ +import { task } from '@trigger.dev/sdk/v3'; +import { z } from 'zod'; +import { schemaToJsonSchema, type JSONSchema } from '@trigger.dev/schema-to-json'; + +// Example 1: Using schemaTask (automatic conversion) +import { schemaTask } from '@trigger.dev/sdk/v3'; + +const userSchema = z.object({ + id: z.string(), + name: z.string(), + email: z.string().email(), + age: z.number().int().min(0), +}); + +export const processUser = schemaTask({ + id: 'process-user', + schema: userSchema, + run: async (payload) => { + // payload is fully typed based on the schema + console.log(`Processing user ${payload.name}`); + return { processed: true }; + }, +}); + +// Example 2: Using plain task with manual JSON Schema +export const processOrder = task({ + id: 'process-order', + // Manually provide JSON Schema for the payload + payloadSchema: { + type: 'object', + properties: { + orderId: { type: 'string' }, + items: { + type: 'array', + items: { + type: 'object', + properties: { + productId: { type: 'string' }, + quantity: { type: 'integer', minimum: 1 }, + price: { type: 'number', minimum: 0 }, + }, + required: ['productId', 'quantity', 'price'], + }, + }, + totalAmount: { type: 'number' }, + }, + required: ['orderId', 'items', 'totalAmount'], + } satisfies JSONSchema, + run: async (payload) => { + // payload is typed as any, but the schema will be validated at runtime + console.log(`Processing order ${payload.orderId}`); + return { processed: true }; + }, +}); + +// Example 3: Using plain task with schema conversion +const orderSchema = z.object({ + orderId: z.string(), + items: z.array(z.object({ + productId: z.string(), + quantity: z.number().int().min(1), + price: z.number().min(0), + })), + totalAmount: z.number(), +}); + +// Convert the schema to JSON Schema +const orderJsonSchema = schemaToJsonSchema(orderSchema); + +export const processOrderWithConversion = task({ + id: 'process-order-converted', + // Use the converted JSON Schema + payloadSchema: orderJsonSchema?.jsonSchema, + run: async (payload) => { + // Note: You still need to validate the payload yourself in plain tasks + const parsed = orderSchema.parse(payload); + console.log(`Processing order ${parsed.orderId}`); + return { processed: true }; + }, +}); + +// Example 4: Type-safe JSON Schema creation +import { Type, Static } from '@sinclair/typebox'; + +const typeBoxSchema = Type.Object({ + userId: Type.String(), + action: Type.Union([ + Type.Literal('create'), + Type.Literal('update'), + Type.Literal('delete'), + ]), + timestamp: Type.Number(), +}); + +type UserAction = Static; + +export const processUserAction = task({ + id: 'process-user-action', + // TypeBox schemas are already JSON Schema compliant + payloadSchema: typeBoxSchema, + run: async (payload) => { + // Cast to get type safety (or validate at runtime) + const action = payload as UserAction; + console.log(`User ${action.userId} performed ${action.action}`); + return { processed: true }; + }, +}); \ No newline at end of file diff --git a/references/hello-world/package.json b/references/hello-world/package.json index 89dbeea911..fb22d8332c 100644 --- a/references/hello-world/package.json +++ b/references/hello-world/package.json @@ -8,10 +8,14 @@ "dependencies": { "@trigger.dev/build": "workspace:*", "@trigger.dev/sdk": "workspace:*", + "@trigger.dev/schema-to-json": "workspace:*", + "arktype": "^2.0.0", "openai": "^4.97.0", "puppeteer-core": "^24.15.0", "replicate": "^1.0.1", - "zod": "3.23.8" + "yup": "^1.6.1", + "zod": "3.23.8", + "@sinclair/typebox": "^0.34.3" }, "scripts": { "dev": "trigger dev", diff --git a/references/hello-world/src/trigger/jsonSchema.ts b/references/hello-world/src/trigger/jsonSchema.ts new file mode 100644 index 0000000000..7aa5baf0e0 --- /dev/null +++ b/references/hello-world/src/trigger/jsonSchema.ts @@ -0,0 +1,398 @@ +import { task, schemaTask, logger } from "@trigger.dev/sdk/v3"; +import { z } from "zod"; +import { schemaToJsonSchema, initializeSchemaConverters, type JSONSchema } from "@trigger.dev/schema-to-json"; +import * as y from "yup"; +import { type } from "arktype"; +import { Type, Static } from "@sinclair/typebox"; + +// Initialize converters for schemas that need external libraries +// This is only needed if you want to convert Zod 3, Yup, or Effect schemas +await initializeSchemaConverters(); + +// =========================================== +// Example 1: Using schemaTask with Zod +// =========================================== +const userSchema = z.object({ + id: z.string().uuid(), + name: z.string().min(1), + email: z.string().email(), + age: z.number().int().min(0).max(150), + preferences: z.object({ + newsletter: z.boolean().default(false), + theme: z.enum(["light", "dark"]).default("light"), + }).optional(), +}); + +export const processUserWithZod = schemaTask({ + id: "json-schema-zod-example", + schema: userSchema, + run: async (payload, { ctx }) => { + // payload is fully typed based on the Zod schema + logger.info("Processing user with Zod schema", { + userId: payload.id, + userName: payload.name + }); + + // The schema is automatically converted to JSON Schema and synced + return { + processed: true, + userId: payload.id, + welcomeMessage: `Welcome ${payload.name}!`, + }; + }, +}); + +// =========================================== +// Example 2: Using plain task with manual JSON Schema +// =========================================== +export const processOrderManualSchema = task({ + id: "json-schema-manual-example", + // Manually provide JSON Schema for the payload + payloadSchema: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + title: "Order Processing Request", + description: "Schema for processing customer orders", + properties: { + orderId: { + type: "string", + pattern: "^ORD-[0-9]+$", + description: "Order ID in format ORD-XXXXX" + }, + customerId: { + type: "string", + format: "uuid" + }, + items: { + type: "array", + minItems: 1, + items: { + type: "object", + properties: { + productId: { type: "string" }, + quantity: { type: "integer", minimum: 1 }, + price: { type: "number", minimum: 0, multipleOf: 0.01 }, + }, + required: ["productId", "quantity", "price"], + additionalProperties: false, + }, + }, + totalAmount: { + type: "number", + minimum: 0, + multipleOf: 0.01, + }, + status: { + type: "string", + enum: ["pending", "processing", "shipped", "delivered"], + default: "pending", + }, + }, + required: ["orderId", "customerId", "items", "totalAmount"], + additionalProperties: false, + } satisfies JSONSchema, + run: async (payload, { ctx }) => { + logger.info("Processing order with manual JSON Schema", { + orderId: payload.orderId + }); + + // Note: With plain tasks, the payload is typed as 'any' + // The JSON Schema will be used for documentation and validation on the server + return { + processed: true, + orderId: payload.orderId, + status: "processing", + }; + }, +}); + +// =========================================== +// Example 3: Using schemaTask with Yup +// =========================================== +const productSchema = y.object({ + sku: y.string().required().matches(/^[A-Z]{3}-[0-9]{5}$/), + name: y.string().required().min(3).max(100), + description: y.string().max(500), + price: y.number().required().positive(), + categories: y.array().of(y.string()).min(1).required(), + inStock: y.boolean().default(true), +}); + +export const processProductWithYup = schemaTask({ + id: "json-schema-yup-example", + schema: productSchema, + run: async (payload, { ctx }) => { + logger.info("Processing product with Yup schema", { + sku: payload.sku, + name: payload.name, + }); + + return { + processed: true, + sku: payload.sku, + message: `Product ${payload.name} has been processed`, + }; + }, +}); + +// =========================================== +// Example 4: Using schemaTask with ArkType +// =========================================== +const invoiceSchema = type({ + invoiceNumber: "string", + date: "Date", + dueDate: "Date", + "discount?": "number", + lineItems: [{ + description: "string", + quantity: "integer", + unitPrice: "number", + }], + customer: { + id: "string", + name: "string", + "taxId?": "string", + }, +}); + +export const processInvoiceWithArkType = schemaTask({ + id: "json-schema-arktype-example", + schema: invoiceSchema, + run: async (payload, { ctx }) => { + logger.info("Processing invoice with ArkType schema", { + invoiceNumber: payload.invoiceNumber, + customerName: payload.customer.name, + }); + + const total = payload.lineItems.reduce( + (sum, item) => sum + (item.quantity * item.unitPrice), + 0 + ); + + const discount = payload.discount || 0; + const finalAmount = total * (1 - discount / 100); + + return { + processed: true, + invoiceNumber: payload.invoiceNumber, + totalAmount: finalAmount, + }; + }, +}); + +// =========================================== +// Example 5: Using TypeBox (already JSON Schema) +// =========================================== +const eventSchema = Type.Object({ + eventId: Type.String({ format: "uuid" }), + eventType: Type.Union([ + Type.Literal("user.created"), + Type.Literal("user.updated"), + Type.Literal("user.deleted"), + Type.Literal("order.placed"), + Type.Literal("order.shipped"), + ]), + timestamp: Type.Integer({ minimum: 0 }), + userId: Type.String(), + metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown())), + payload: Type.Unknown(), +}); + +type EventType = Static; + +export const processEventWithTypeBox = task({ + id: "json-schema-typebox-example", + // TypeBox schemas are already JSON Schema compliant + payloadSchema: eventSchema, + run: async (payload, { ctx }) => { + // Cast to get TypeScript type safety + const event = payload as EventType; + + logger.info("Processing event with TypeBox schema", { + eventId: event.eventId, + eventType: event.eventType, + userId: event.userId, + }); + + // Handle different event types + switch (event.eventType) { + case "user.created": + logger.info("New user created", { userId: event.userId }); + break; + case "order.placed": + logger.info("Order placed", { userId: event.userId }); + break; + default: + logger.info("Event processed", { eventType: event.eventType }); + } + + return { + processed: true, + eventId: event.eventId, + eventType: event.eventType, + }; + }, +}); + +// =========================================== +// Example 6: Converting schemas at build time +// =========================================== +const notificationSchema = z.object({ + recipientId: z.string(), + type: z.enum(["email", "sms", "push"]), + subject: z.string().optional(), + message: z.string(), + priority: z.enum(["low", "normal", "high"]).default("normal"), + scheduledFor: z.date().optional(), + metadata: z.record(z.unknown()).optional(), +}); + +// Convert the schema to JSON Schema at build time +const notificationJsonSchema = schemaToJsonSchema(notificationSchema); + +export const sendNotificationWithConversion = task({ + id: "json-schema-conversion-example", + // Use the converted JSON Schema + payloadSchema: notificationJsonSchema?.jsonSchema, + run: async (payload, { ctx }) => { + // Validate the payload using the original Zod schema + const notification = notificationSchema.parse(payload); + + logger.info("Sending notification", { + recipientId: notification.recipientId, + type: notification.type, + priority: notification.priority, + }); + + // Simulate sending notification + await new Promise(resolve => setTimeout(resolve, 1000)); + + return { + sent: true, + notificationId: ctx.run.id, + recipientId: notification.recipientId, + type: notification.type, + }; + }, +}); + +// =========================================== +// Example 7: Complex nested schema with references +// =========================================== +const addressSchema = z.object({ + street: z.string(), + city: z.string(), + state: z.string().length(2), + zipCode: z.string().regex(/^\d{5}(-\d{4})?$/), + country: z.string().default("US"), +}); + +const companySchema = z.object({ + companyId: z.string().uuid(), + name: z.string(), + taxId: z.string().optional(), + addresses: z.object({ + billing: addressSchema, + shipping: addressSchema.optional(), + }), + contacts: z.array(z.object({ + name: z.string(), + email: z.string().email(), + phone: z.string().optional(), + role: z.enum(["primary", "billing", "technical"]), + })).min(1), + settings: z.object({ + invoicePrefix: z.string().default("INV"), + paymentTerms: z.number().int().min(0).max(90).default(30), + currency: z.enum(["USD", "EUR", "GBP"]).default("USD"), + }), +}); + +export const processCompanyWithComplexSchema = schemaTask({ + id: "json-schema-complex-example", + schema: companySchema, + maxDuration: 300, // 5 minutes + retry: { + maxAttempts: 3, + factor: 2, + }, + run: async (payload, { ctx }) => { + logger.info("Processing company with complex schema", { + companyId: payload.companyId, + name: payload.name, + contactCount: payload.contacts.length, + }); + + // Process each contact + for (const contact of payload.contacts) { + logger.info("Processing contact", { + name: contact.name, + role: contact.role, + }); + } + + return { + processed: true, + companyId: payload.companyId, + name: payload.name, + primaryContact: payload.contacts.find(c => c.role === "primary"), + }; + }, +}); + +// =========================================== +// Example 8: Demonstrating schema benefits +// =========================================== +export const triggerExamples = task({ + id: "json-schema-trigger-examples", + run: async (_, { ctx }) => { + logger.info("Triggering various schema examples"); + + // Trigger Zod example - TypeScript will enforce correct payload + await processUserWithZod.trigger({ + id: "550e8400-e29b-41d4-a716-446655440000", + name: "John Doe", + email: "john@example.com", + age: 30, + preferences: { + newsletter: true, + theme: "dark", + }, + }); + + // Trigger Yup example + await processProductWithYup.trigger({ + sku: "ABC-12345", + name: "Premium Widget", + description: "A high-quality widget for all your needs", + price: 99.99, + categories: ["electronics", "gadgets"], + inStock: true, + }); + + // Trigger manual schema example (no compile-time validation) + await processOrderManualSchema.trigger({ + orderId: "ORD-12345", + customerId: "550e8400-e29b-41d4-a716-446655440001", + items: [ + { + productId: "PROD-001", + quantity: 2, + price: 29.99, + }, + { + productId: "PROD-002", + quantity: 1, + price: 49.99, + }, + ], + totalAmount: 109.97, + status: "pending", + }); + + return { + message: "All examples triggered successfully", + timestamp: new Date().toISOString(), + }; + }, +}); \ No newline at end of file From 200262818fc2facdd2d76ea454e84973ea801518 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Sun, 27 Jul 2025 20:42:35 +0100 Subject: [PATCH 07/19] 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. --- packages/schema-to-json/examples/usage.ts | 148 ++++---- packages/schema-to-json/src/index.ts | 12 +- packages/trigger-sdk/src/v3/index.ts | 1 + packages/trigger-sdk/src/v3/schemas.ts | 12 + packages/trigger-sdk/src/v3/shared.ts | 9 +- references/hello-world/package.json | 1 - .../hello-world/src/trigger/jsonSchema.ts | 37 +- .../hello-world/src/trigger/jsonSchemaApi.ts | 343 ++++++++++++++++++ .../src/trigger/jsonSchemaSimple.ts | 225 ++++++++++++ 9 files changed, 683 insertions(+), 105 deletions(-) create mode 100644 packages/trigger-sdk/src/v3/schemas.ts create mode 100644 references/hello-world/src/trigger/jsonSchemaApi.ts create mode 100644 references/hello-world/src/trigger/jsonSchemaSimple.ts diff --git a/packages/schema-to-json/examples/usage.ts b/packages/schema-to-json/examples/usage.ts index 3049b18265..5d136fc673 100644 --- a/packages/schema-to-json/examples/usage.ts +++ b/packages/schema-to-json/examples/usage.ts @@ -1,9 +1,10 @@ -import { task } from '@trigger.dev/sdk/v3'; -import { z } from 'zod'; +// This file shows how @trigger.dev/schema-to-json is used INTERNALLY by the SDK +// Regular users should NOT import this package directly! + import { schemaToJsonSchema, type JSONSchema } from '@trigger.dev/schema-to-json'; +import { z } from 'zod'; -// Example 1: Using schemaTask (automatic conversion) -import { schemaTask } from '@trigger.dev/sdk/v3'; +// Example of how the SDK uses this internally: const userSchema = z.object({ id: z.string(), @@ -12,96 +13,69 @@ const userSchema = z.object({ age: z.number().int().min(0), }); -export const processUser = schemaTask({ - id: 'process-user', - schema: userSchema, - run: async (payload) => { - // payload is fully typed based on the schema - console.log(`Processing user ${payload.name}`); - return { processed: true }; - }, -}); +// This is what happens internally in the SDK's schemaTask: +const result = schemaToJsonSchema(userSchema); +if (result) { + console.log('Converted Zod schema to JSON Schema:', result.jsonSchema); + console.log('Detected schema type:', result.schemaType); + // The SDK then includes this JSON Schema in the task metadata +} -// Example 2: Using plain task with manual JSON Schema -export const processOrder = task({ - id: 'process-order', - // Manually provide JSON Schema for the payload - payloadSchema: { - type: 'object', - properties: { - orderId: { type: 'string' }, - items: { - type: 'array', - items: { - type: 'object', - properties: { - productId: { type: 'string' }, - quantity: { type: 'integer', minimum: 1 }, - price: { type: 'number', minimum: 0 }, - }, - required: ['productId', 'quantity', 'price'], - }, - }, - totalAmount: { type: 'number' }, - }, - required: ['orderId', 'items', 'totalAmount'], - } satisfies JSONSchema, - run: async (payload) => { - // payload is typed as any, but the schema will be validated at runtime - console.log(`Processing order ${payload.orderId}`); - return { processed: true }; - }, -}); +// Example: How different schema libraries are detected and converted -// Example 3: Using plain task with schema conversion -const orderSchema = z.object({ - orderId: z.string(), - items: z.array(z.object({ - productId: z.string(), - quantity: z.number().int().min(1), - price: z.number().min(0), - })), - totalAmount: z.number(), +// Yup schema +import * as y from 'yup'; +const yupSchema = y.object({ + name: y.string().required(), + age: y.number().required(), }); -// Convert the schema to JSON Schema -const orderJsonSchema = schemaToJsonSchema(orderSchema); - -export const processOrderWithConversion = task({ - id: 'process-order-converted', - // Use the converted JSON Schema - payloadSchema: orderJsonSchema?.jsonSchema, - run: async (payload) => { - // Note: You still need to validate the payload yourself in plain tasks - const parsed = orderSchema.parse(payload); - console.log(`Processing order ${parsed.orderId}`); - return { processed: true }; - }, +const yupResult = schemaToJsonSchema(yupSchema); +console.log('Yup conversion:', yupResult); + +// ArkType schema (has built-in toJsonSchema) +import { type } from 'arktype'; +const arkSchema = type({ + name: 'string', + age: 'number', }); -// Example 4: Type-safe JSON Schema creation -import { Type, Static } from '@sinclair/typebox'; +const arkResult = schemaToJsonSchema(arkSchema); +console.log('ArkType conversion:', arkResult); +// TypeBox (already JSON Schema) +import { Type } from '@sinclair/typebox'; const typeBoxSchema = Type.Object({ - userId: Type.String(), - action: Type.Union([ - Type.Literal('create'), - Type.Literal('update'), - Type.Literal('delete'), - ]), - timestamp: Type.Number(), + name: Type.String(), + age: Type.Number(), }); -type UserAction = Static; - -export const processUserAction = task({ - id: 'process-user-action', - // TypeBox schemas are already JSON Schema compliant - payloadSchema: typeBoxSchema, - run: async (payload) => { - // Cast to get type safety (or validate at runtime) - const action = payload as UserAction; - console.log(`User ${action.userId} performed ${action.action}`); - return { processed: true }; - }, -}); \ No newline at end of file +const typeBoxResult = schemaToJsonSchema(typeBoxSchema); +console.log('TypeBox conversion:', typeBoxResult); + +// Example: Initialization (done automatically by the SDK) +import { initializeSchemaConverters, areConvertersInitialized } from '@trigger.dev/schema-to-json'; + +// The SDK calls this once when it loads +await initializeSchemaConverters(); + +// Check which converters are available +const status = areConvertersInitialized(); +console.log('Converter status:', status); +// { zod: true, yup: true, effect: true } + +// Example: How the SDK determines if a schema can be converted +import { canConvertSchema, detectSchemaType } from '@trigger.dev/schema-to-json'; + +const zodSchema = z.string(); +console.log('Can convert Zod?', canConvertSchema(zodSchema)); // true +console.log('Schema type:', detectSchemaType(zodSchema)); // 'zod' + +// For users: Just use the SDK! +// import { schemaTask } from '@trigger.dev/sdk/v3'; +// +// export const myTask = schemaTask({ +// id: 'my-task', +// schema: zodSchema, +// run: async (payload) => { /* ... */ } +// }); \ No newline at end of file diff --git a/packages/schema-to-json/src/index.ts b/packages/schema-to-json/src/index.ts index 5190e34310..3a5b7b487b 100644 --- a/packages/schema-to-json/src/index.ts +++ b/packages/schema-to-json/src/index.ts @@ -1,9 +1,19 @@ -import type { JSONSchema7, JSONSchema7Definition } from '@types/json-schema'; +import type { JSONSchema7, JSONSchema7Definition, JSONSchema7Type, JSONSchema7TypeName, JSONSchema7Object, JSONSchema7Array } from '@types/json-schema'; export type Schema = unknown; export type JSONSchema = JSONSchema7; export type JSONSchemaDefinition = JSONSchema7Definition; +// Re-export the standard JSON Schema types for convenience +export type { + JSONSchema7, + JSONSchema7Type, + JSONSchema7TypeName, + JSONSchema7Definition, + JSONSchema7Object, + JSONSchema7Array, +} from '@types/json-schema'; + export interface ConversionOptions { /** * The name to use for the schema in the JSON Schema diff --git a/packages/trigger-sdk/src/v3/index.ts b/packages/trigger-sdk/src/v3/index.ts index 4527cd6d01..78554b4539 100644 --- a/packages/trigger-sdk/src/v3/index.ts +++ b/packages/trigger-sdk/src/v3/index.ts @@ -13,6 +13,7 @@ export * from "./metadata.js"; export * from "./timeout.js"; export * from "./webhooks.js"; export * from "./locals.js"; +export * from "./schemas.js"; export type { Context }; import type { Context } from "./shared.js"; diff --git a/packages/trigger-sdk/src/v3/schemas.ts b/packages/trigger-sdk/src/v3/schemas.ts new file mode 100644 index 0000000000..7c293ef19e --- /dev/null +++ b/packages/trigger-sdk/src/v3/schemas.ts @@ -0,0 +1,12 @@ +// Re-export JSON Schema types for user convenience +export type { JSONSchema, JSONSchemaDefinition } from "@trigger.dev/schema-to-json"; + +// Re-export the standard JSON Schema types from @types/json-schema +export type { + JSONSchema7, + JSONSchema7Type, + JSONSchema7TypeName, + JSONSchema7Definition, + JSONSchema7Object, + JSONSchema7Array, +} from "@trigger.dev/schema-to-json"; \ No newline at end of file diff --git a/packages/trigger-sdk/src/v3/shared.ts b/packages/trigger-sdk/src/v3/shared.ts index 59351dd921..7677da9358 100644 --- a/packages/trigger-sdk/src/v3/shared.ts +++ b/packages/trigger-sdk/src/v3/shared.ts @@ -28,10 +28,17 @@ import { TaskRunExecutionResult, TaskRunPromise, } from "@trigger.dev/core/v3"; -import { schemaToJsonSchema } from "@trigger.dev/schema-to-json"; +import { schemaToJsonSchema, initializeSchemaConverters } from "@trigger.dev/schema-to-json"; import { PollOptions, runs } from "./runs.js"; import { tracer } from "./tracer.js"; +// Initialize schema converters once when the module loads +// This happens automatically, users don't need to know about it +initializeSchemaConverters().catch(() => { + // Silently fail if converters can't be initialized + // Built-in conversions will still work +}); + import type { AnyOnCatchErrorHookFunction, AnyOnCleanupHookFunction, diff --git a/references/hello-world/package.json b/references/hello-world/package.json index fb22d8332c..3529940529 100644 --- a/references/hello-world/package.json +++ b/references/hello-world/package.json @@ -8,7 +8,6 @@ "dependencies": { "@trigger.dev/build": "workspace:*", "@trigger.dev/sdk": "workspace:*", - "@trigger.dev/schema-to-json": "workspace:*", "arktype": "^2.0.0", "openai": "^4.97.0", "puppeteer-core": "^24.15.0", diff --git a/references/hello-world/src/trigger/jsonSchema.ts b/references/hello-world/src/trigger/jsonSchema.ts index 7aa5baf0e0..e194001c01 100644 --- a/references/hello-world/src/trigger/jsonSchema.ts +++ b/references/hello-world/src/trigger/jsonSchema.ts @@ -1,14 +1,9 @@ -import { task, schemaTask, logger } from "@trigger.dev/sdk/v3"; +import { task, schemaTask, logger, type JSONSchema } from "@trigger.dev/sdk/v3"; import { z } from "zod"; -import { schemaToJsonSchema, initializeSchemaConverters, type JSONSchema } from "@trigger.dev/schema-to-json"; import * as y from "yup"; import { type } from "arktype"; import { Type, Static } from "@sinclair/typebox"; -// Initialize converters for schemas that need external libraries -// This is only needed if you want to convert Zod 3, Yup, or Effect schemas -await initializeSchemaConverters(); - // =========================================== // Example 1: Using schemaTask with Zod // =========================================== @@ -235,8 +230,12 @@ export const processEventWithTypeBox = task({ }); // =========================================== -// Example 6: Converting schemas at build time +// Example 6: Using plain task with a Zod schema // =========================================== +// If you need to use a plain task but have a Zod schema, +// you should use schemaTask instead for better DX. +// This example shows what NOT to do: + const notificationSchema = z.object({ recipientId: z.string(), type: z.enum(["email", "sms", "push"]), @@ -247,17 +246,25 @@ const notificationSchema = z.object({ metadata: z.record(z.unknown()).optional(), }); -// Convert the schema to JSON Schema at build time -const notificationJsonSchema = schemaToJsonSchema(notificationSchema); - -export const sendNotificationWithConversion = task({ - id: "json-schema-conversion-example", - // Use the converted JSON Schema - payloadSchema: notificationJsonSchema?.jsonSchema, +// ❌ Don't do this - use schemaTask instead! +export const sendNotificationBadExample = task({ + id: "json-schema-dont-do-this", run: async (payload, { ctx }) => { - // Validate the payload using the original Zod schema + // You'd have to manually validate const notification = notificationSchema.parse(payload); + logger.info("This is not ideal - use schemaTask instead!"); + + return { sent: true }; + }, +}); + +// ✅ Do this instead - much better! +export const sendNotificationGoodExample = schemaTask({ + id: "json-schema-do-this-instead", + schema: notificationSchema, + run: async (notification, { ctx }) => { + // notification is already validated and typed! logger.info("Sending notification", { recipientId: notification.recipientId, type: notification.type, diff --git a/references/hello-world/src/trigger/jsonSchemaApi.ts b/references/hello-world/src/trigger/jsonSchemaApi.ts new file mode 100644 index 0000000000..9ac121b369 --- /dev/null +++ b/references/hello-world/src/trigger/jsonSchemaApi.ts @@ -0,0 +1,343 @@ +import { task, schemaTask, logger, type JSONSchema } from "@trigger.dev/sdk/v3"; +import { z } from "zod"; + +// =========================================== +// Example: Webhook Handler with Schema Validation +// =========================================== + +// Define schemas for different webhook event types +const baseWebhookSchema = z.object({ + id: z.string(), + timestamp: z.string().datetime(), + type: z.string(), + version: z.literal("1.0"), +}); + +// Payment webhook events +const paymentEventSchema = baseWebhookSchema.extend({ + type: z.literal("payment"), + data: z.object({ + paymentId: z.string(), + amount: z.number().positive(), + currency: z.string().length(3), + status: z.enum(["pending", "processing", "completed", "failed"]), + customerId: z.string(), + paymentMethod: z.object({ + type: z.enum(["card", "bank_transfer", "paypal"]), + last4: z.string().optional(), + }), + metadata: z.record(z.string()).optional(), + }), +}); + +// Customer webhook events +const customerEventSchema = baseWebhookSchema.extend({ + type: z.literal("customer"), + data: z.object({ + customerId: z.string(), + action: z.enum(["created", "updated", "deleted"]), + email: z.string().email(), + name: z.string(), + subscription: z.object({ + status: z.enum(["active", "cancelled", "past_due"]), + plan: z.string(), + }).optional(), + }), +}); + +// Union of all webhook types +const webhookSchema = z.discriminatedUnion("type", [ + paymentEventSchema, + customerEventSchema, +]); + +export const handleWebhook = schemaTask({ + id: "handle-webhook", + schema: webhookSchema, + run: async (payload, { ctx }) => { + logger.info("Processing webhook", { + id: payload.id, + type: payload.type, + timestamp: payload.timestamp, + }); + + // TypeScript knows the exact shape based on the discriminated union + switch (payload.type) { + case "payment": + logger.info("Payment event received", { + paymentId: payload.data.paymentId, + amount: payload.data.amount, + status: payload.data.status, + }); + + if (payload.data.status === "completed") { + // Trigger order fulfillment + await fulfillOrder.trigger({ + customerId: payload.data.customerId, + paymentId: payload.data.paymentId, + amount: payload.data.amount, + }); + } + break; + + case "customer": + logger.info("Customer event received", { + customerId: payload.data.customerId, + action: payload.data.action, + }); + + if (payload.data.action === "created") { + // Send welcome email + await sendWelcomeEmail.trigger({ + email: payload.data.email, + name: payload.data.name, + }); + } + break; + } + + return { + processed: true, + eventId: payload.id, + eventType: payload.type, + }; + }, +}); + +// =========================================== +// Example: External API Integration +// =========================================== + +// Schema for making API requests to a third-party service +const apiRequestSchema = z.object({ + endpoint: z.enum(["/users", "/products", "/orders"]), + method: z.enum(["GET", "POST", "PUT", "DELETE"]), + params: z.record(z.string()).optional(), + body: z.unknown().optional(), + headers: z.record(z.string()).optional(), + retryOnError: z.boolean().default(true), +}); + +// Response schemas for different endpoints +const userResponseSchema = z.object({ + id: z.string(), + email: z.string().email(), + name: z.string(), + createdAt: z.string().datetime(), +}); + +const productResponseSchema = z.object({ + id: z.string(), + name: z.string(), + price: z.number(), + inStock: z.boolean(), +}); + +export const callExternalApi = schemaTask({ + id: "call-external-api", + schema: apiRequestSchema, + retry: { + maxAttempts: 3, + factor: 2, + minTimeoutInMs: 1000, + maxTimeoutInMs: 10000, + }, + run: async (payload, { ctx }) => { + logger.info("Making API request", { + endpoint: payload.endpoint, + method: payload.method, + }); + + // Simulate API call + const response = await makeApiCall(payload); + + // Validate response based on endpoint + let validatedResponse; + switch (payload.endpoint) { + case "/users": + validatedResponse = userResponseSchema.parse(response); + break; + case "/products": + validatedResponse = productResponseSchema.parse(response); + break; + default: + validatedResponse = response; + } + + return { + success: true, + endpoint: payload.endpoint, + response: validatedResponse, + }; + }, +}); + +// Helper function to simulate API calls +async function makeApiCall(request: z.infer) { + // Simulate network delay + await new Promise(resolve => setTimeout(resolve, 100)); + + // Return mock data based on endpoint + switch (request.endpoint) { + case "/users": + return { + id: "user_123", + email: "user@example.com", + name: "John Doe", + createdAt: new Date().toISOString(), + }; + case "/products": + return { + id: "prod_456", + name: "Premium Widget", + price: 99.99, + inStock: true, + }; + default: + return { message: "Success" }; + } +} + +// =========================================== +// Example: Batch Processing with Validation +// =========================================== + +const batchItemSchema = z.object({ + id: z.string(), + operation: z.enum(["create", "update", "delete"]), + resourceType: z.enum(["user", "product", "order"]), + data: z.record(z.unknown()), +}); + +const batchRequestSchema = z.object({ + batchId: z.string(), + items: z.array(batchItemSchema).min(1).max(100), + options: z.object({ + stopOnError: z.boolean().default(false), + parallel: z.boolean().default(true), + maxConcurrency: z.number().int().min(1).max(10).default(5), + }).default({}), +}); + +export const processBatch = schemaTask({ + id: "process-batch", + schema: batchRequestSchema, + maxDuration: 300, // 5 minutes for large batches + run: async (payload, { ctx }) => { + logger.info("Processing batch", { + batchId: payload.batchId, + itemCount: payload.items.length, + parallel: payload.options.parallel, + }); + + const results = []; + const errors = []; + + if (payload.options.parallel) { + // Process items in parallel with concurrency limit + const chunks = chunkArray(payload.items, payload.options.maxConcurrency); + + for (const chunk of chunks) { + const chunkResults = await Promise.allSettled( + chunk.map(item => processItem(item)) + ); + + chunkResults.forEach((result, index) => { + if (result.status === "fulfilled") { + results.push(result.value); + } else { + errors.push({ + item: chunk[index], + error: result.reason, + }); + + if (payload.options.stopOnError) { + throw new Error(`Batch processing stopped due to error in item ${chunk[index].id}`); + } + } + }); + } + } else { + // Process items sequentially + for (const item of payload.items) { + try { + const result = await processItem(item); + results.push(result); + } catch (error) { + errors.push({ item, error }); + + if (payload.options.stopOnError) { + throw new Error(`Batch processing stopped due to error in item ${item.id}`); + } + } + } + } + + return { + batchId: payload.batchId, + processed: results.length, + failed: errors.length, + results, + errors, + }; + }, +}); + +async function processItem(item: z.infer) { + logger.info("Processing batch item", { + id: item.id, + operation: item.operation, + resourceType: item.resourceType, + }); + + // Simulate processing + await new Promise(resolve => setTimeout(resolve, 50)); + + return { + id: item.id, + success: true, + operation: item.operation, + resourceType: item.resourceType, + }; +} + +function chunkArray(array: T[], size: number): T[][] { + const chunks: T[][] = []; + for (let i = 0; i < array.length; i += size) { + chunks.push(array.slice(i, i + size)); + } + return chunks; +} + +// =========================================== +// Helper Tasks +// =========================================== + +const orderSchema = z.object({ + customerId: z.string(), + paymentId: z.string(), + amount: z.number(), +}); + +export const fulfillOrder = schemaTask({ + id: "fulfill-order", + schema: orderSchema, + run: async (payload, { ctx }) => { + logger.info("Fulfilling order", payload); + return { fulfilled: true }; + }, +}); + +const welcomeEmailSchema = z.object({ + email: z.string().email(), + name: z.string(), +}); + +export const sendWelcomeEmail = schemaTask({ + id: "send-welcome-email", + schema: welcomeEmailSchema, + run: async (payload, { ctx }) => { + logger.info("Sending welcome email", payload); + return { sent: true }; + }, +}); \ No newline at end of file diff --git a/references/hello-world/src/trigger/jsonSchemaSimple.ts b/references/hello-world/src/trigger/jsonSchemaSimple.ts new file mode 100644 index 0000000000..58812fc34a --- /dev/null +++ b/references/hello-world/src/trigger/jsonSchemaSimple.ts @@ -0,0 +1,225 @@ +import { task, schemaTask, logger, type JSONSchema } from "@trigger.dev/sdk/v3"; +import { z } from "zod"; + +// =========================================== +// The Two Main Approaches +// =========================================== + +// Approach 1: Using schemaTask (Recommended) +// - Automatic JSON Schema conversion +// - Full TypeScript type safety +// - Runtime validation built-in +const emailSchema = z.object({ + to: z.string().email(), + subject: z.string(), + body: z.string(), + attachments: z.array(z.object({ + filename: z.string(), + url: z.string().url(), + })).optional(), +}); + +export const sendEmailSchemaTask = schemaTask({ + id: "send-email-schema-task", + schema: emailSchema, + run: async (payload, { ctx }) => { + // payload is fully typed as: + // { + // to: string; + // subject: string; + // body: string; + // attachments?: Array<{ filename: string; url: string; }>; + // } + + logger.info("Sending email", { + to: payload.to, + subject: payload.subject, + hasAttachments: !!payload.attachments?.length, + }); + + // Your email sending logic here... + + return { + sent: true, + messageId: `msg_${ctx.run.id}`, + sentAt: new Date().toISOString(), + }; + }, +}); + +// Approach 2: Using plain task with payloadSchema +// - Manual JSON Schema definition +// - No automatic type inference (payload is 'any') +// - Good for when you already have JSON Schema definitions +export const sendEmailPlainTask = task({ + id: "send-email-plain-task", + payloadSchema: { + type: "object", + properties: { + to: { + type: "string", + format: "email", + description: "Recipient email address", + }, + subject: { + type: "string", + maxLength: 200, + }, + body: { + type: "string", + }, + attachments: { + type: "array", + items: { + type: "object", + properties: { + filename: { type: "string" }, + url: { type: "string", format: "uri" }, + }, + required: ["filename", "url"], + }, + }, + }, + required: ["to", "subject", "body"], + } satisfies JSONSchema, // Use 'satisfies' for type checking + run: async (payload, { ctx }) => { + // payload is typed as 'any' - you need to validate/cast it yourself + logger.info("Sending email", { + to: payload.to, + subject: payload.subject, + }); + + // Your email sending logic here... + + return { + sent: true, + messageId: `msg_${ctx.run.id}`, + sentAt: new Date().toISOString(), + }; + }, +}); + +// =========================================== +// Benefits of JSON Schema +// =========================================== + +// 1. Documentation - The schema is visible in the Trigger.dev dashboard +// 2. Validation - Invalid payloads are rejected before execution +// 3. Type Safety - With schemaTask, you get full TypeScript support +// 4. OpenAPI Generation - Can be used to generate API documentation +// 5. Client SDKs - Can generate typed clients for other languages + +export const demonstrateBenefits = task({ + id: "json-schema-benefits-demo", + run: async (_, { ctx }) => { + logger.info("Demonstrating JSON Schema benefits"); + + // With schemaTask, TypeScript prevents invalid payloads at compile time + try { + await sendEmailSchemaTask.trigger({ + to: "user@example.com", + subject: "Welcome!", + body: "Thanks for signing up!", + // TypeScript error if you try to add invalid fields + // invalidField: "This would cause a TypeScript error", + }); + } catch (error) { + logger.error("Failed to send email", { error }); + } + + // With plain task, validation happens at runtime + try { + await sendEmailPlainTask.trigger({ + to: "not-an-email", // This will fail validation at runtime + subject: "Test", + body: "Test email", + }); + } catch (error) { + logger.error("Failed validation", { error }); + } + + return { demonstrated: true }; + }, +}); + +// =========================================== +// Real-World Example: User Registration Flow +// =========================================== +const userRegistrationSchema = z.object({ + email: z.string().email(), + username: z.string().min(3).max(20).regex(/^[a-zA-Z0-9_]+$/), + password: z.string().min(8), + profile: z.object({ + firstName: z.string(), + lastName: z.string(), + dateOfBirth: z.string().optional(), // ISO date string + preferences: z.object({ + newsletter: z.boolean().default(false), + notifications: z.boolean().default(true), + }).default({}), + }), + referralCode: z.string().optional(), +}); + +export const registerUser = schemaTask({ + id: "register-user", + schema: userRegistrationSchema, + retry: { + maxAttempts: 3, + minTimeoutInMs: 1000, + maxTimeoutInMs: 10000, + }, + run: async (payload, { ctx }) => { + logger.info("Registering new user", { + email: payload.email, + username: payload.username, + }); + + // Step 1: Validate uniqueness + logger.info("Checking if user exists"); + // ... database check logic ... + + // Step 2: Create user account + logger.info("Creating user account"); + const userId = `user_${Date.now()}`; + // ... user creation logic ... + + // Step 3: Send welcome email + await sendEmailSchemaTask.trigger({ + to: payload.email, + subject: `Welcome to our platform, ${payload.profile.firstName}!`, + body: `Hi ${payload.profile.firstName},\n\nThanks for joining us...`, + }); + + // Step 4: Apply referral code if provided + if (payload.referralCode) { + logger.info("Processing referral code", { code: payload.referralCode }); + // ... referral logic ... + } + + return { + success: true, + userId, + username: payload.username, + welcomeEmailSent: true, + }; + }, +}); + +// =========================================== +// When to Use Each Approach +// =========================================== + +/* +Use schemaTask when: +- You're already using Zod, Yup, ArkType, etc. in your codebase +- You want TypeScript type inference +- You want runtime validation handled automatically +- You're building new tasks from scratch + +Use plain task with payloadSchema when: +- You have existing JSON Schema definitions +- You're migrating from another system that uses JSON Schema +- You need fine-grained control over the schema format +- You're working with generated schemas from OpenAPI/Swagger +*/ \ No newline at end of file From 7e4e77b24ac0b295fe2abcaa52246479db960e69 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Sun, 27 Jul 2025 20:49:17 +0100 Subject: [PATCH 08/19] Add JSONSchema type for payloadSchema in tasks The change was necessary to improve type safety by using a proper JSONSchema type definition instead of a generic Record. This enhances the developer experience and ensures that task payloads conform to the JSON Schema Draft 7 specification. The JSONSchema type is now re-exported from the SDK for user convenience, hiding internal complexity and maintaining a seamless developer experience. - Added JSONSchema type based on Draft 7 specification - Updated task metadata and options to use JSONSchema type - Hid internal schema conversion logic from users by re-exporting types from SDK - Improved bundle safety and dependency management --- packages/core/src/v3/schemas/resources.ts | 1 + packages/core/src/v3/schemas/schemas.ts | 1 + packages/core/src/v3/types/index.ts | 1 + packages/core/src/v3/types/jsonSchema.ts | 69 +++++++++++++++++++++++ packages/core/src/v3/types/tasks.ts | 3 +- packages/trigger-sdk/src/v3/schemas.ts | 12 +--- 6 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 packages/core/src/v3/types/jsonSchema.ts diff --git a/packages/core/src/v3/schemas/resources.ts b/packages/core/src/v3/schemas/resources.ts index ff015e39d1..845044c8b7 100644 --- a/packages/core/src/v3/schemas/resources.ts +++ b/packages/core/src/v3/schemas/resources.ts @@ -13,6 +13,7 @@ export const TaskResource = z.object({ triggerSource: z.string().optional(), schedule: ScheduleMetadata.optional(), maxDuration: z.number().optional(), + // JSONSchema type - using z.record for runtime validation payloadSchema: z.record(z.unknown()).optional(), }); diff --git a/packages/core/src/v3/schemas/schemas.ts b/packages/core/src/v3/schemas/schemas.ts index 699be74bd4..e65739b33a 100644 --- a/packages/core/src/v3/schemas/schemas.ts +++ b/packages/core/src/v3/schemas/schemas.ts @@ -189,6 +189,7 @@ const taskMetadata = { triggerSource: z.string().optional(), schedule: ScheduleMetadata.optional(), maxDuration: z.number().optional(), + // JSONSchema type - using z.record for runtime validation payloadSchema: z.record(z.unknown()).optional(), }; diff --git a/packages/core/src/v3/types/index.ts b/packages/core/src/v3/types/index.ts index 55cc4d3a12..ea2bc8d558 100644 --- a/packages/core/src/v3/types/index.ts +++ b/packages/core/src/v3/types/index.ts @@ -7,6 +7,7 @@ export * from "./tasks.js"; export * from "./idempotencyKeys.js"; export * from "./tools.js"; export * from "./queues.js"; +export * from "./jsonSchema.js"; type ResolveEnvironmentVariablesOptions = { variables: Record | Array<{ name: string; value: string }>; diff --git a/packages/core/src/v3/types/jsonSchema.ts b/packages/core/src/v3/types/jsonSchema.ts new file mode 100644 index 0000000000..54b544e7b5 --- /dev/null +++ b/packages/core/src/v3/types/jsonSchema.ts @@ -0,0 +1,69 @@ +/** + * Basic JSON Schema type definition + * Based on JSON Schema Draft 7 + */ +export type JSONSchemaType = "string" | "number" | "integer" | "boolean" | "object" | "array" | "null"; + +export interface JSONSchema { + $id?: string; + $ref?: string; + $schema?: string; + $comment?: string; + + type?: JSONSchemaType | JSONSchemaType[]; + enum?: any[]; + const?: any; + + // Number/Integer validations + multipleOf?: number; + maximum?: number; + exclusiveMaximum?: number; + minimum?: number; + exclusiveMinimum?: number; + + // String validations + maxLength?: number; + minLength?: number; + pattern?: string; + format?: string; + + // Array validations + items?: JSONSchema | JSONSchema[]; + additionalItems?: JSONSchema | boolean; + maxItems?: number; + minItems?: number; + uniqueItems?: boolean; + contains?: JSONSchema; + + // Object validations + maxProperties?: number; + minProperties?: number; + required?: string[]; + properties?: Record; + patternProperties?: Record; + additionalProperties?: JSONSchema | boolean; + dependencies?: Record; + propertyNames?: JSONSchema; + + // Conditionals + if?: JSONSchema; + then?: JSONSchema; + else?: JSONSchema; + + // Boolean logic + allOf?: JSONSchema[]; + anyOf?: JSONSchema[]; + oneOf?: JSONSchema[]; + not?: JSONSchema; + + // Metadata + title?: string; + description?: string; + default?: any; + readOnly?: boolean; + writeOnly?: boolean; + examples?: any[]; + + // Additional properties + [key: string]: any; +} \ No newline at end of file diff --git a/packages/core/src/v3/types/tasks.ts b/packages/core/src/v3/types/tasks.ts index 67bed2ddf4..40b6de6e7c 100644 --- a/packages/core/src/v3/types/tasks.ts +++ b/packages/core/src/v3/types/tasks.ts @@ -28,6 +28,7 @@ import { QueueOptions } from "./queues.js"; import { AnySchemaParseFn, inferSchemaIn, inferSchemaOut, Schema } from "./schemas.js"; import { inferToolParameters, ToolTaskParameters } from "./tools.js"; import { Prettify } from "./utils.js"; +import { JSONSchema } from "./jsonSchema.js"; export type Queue = QueueOptions; export type TaskSchema = Schema; @@ -344,7 +345,7 @@ type CommonTaskOptions< * JSON Schema for the task payload. This will be synced to the server during indexing. * Should be a valid JSON Schema Draft 7 object. */ - payloadSchema?: Record; + payloadSchema?: JSONSchema; }; export type TaskOptions< diff --git a/packages/trigger-sdk/src/v3/schemas.ts b/packages/trigger-sdk/src/v3/schemas.ts index 7c293ef19e..65be53024e 100644 --- a/packages/trigger-sdk/src/v3/schemas.ts +++ b/packages/trigger-sdk/src/v3/schemas.ts @@ -1,12 +1,2 @@ // Re-export JSON Schema types for user convenience -export type { JSONSchema, JSONSchemaDefinition } from "@trigger.dev/schema-to-json"; - -// Re-export the standard JSON Schema types from @types/json-schema -export type { - JSONSchema7, - JSONSchema7Type, - JSONSchema7TypeName, - JSONSchema7Definition, - JSONSchema7Object, - JSONSchema7Array, -} from "@trigger.dev/schema-to-json"; \ No newline at end of file +export type { JSONSchema } from "@trigger.dev/core/v3"; \ No newline at end of file From af1e369fe4105d9df0caf13a72b7ccd50673dd3a Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 28 Jul 2025 08:14:56 +0100 Subject: [PATCH 09/19] Add JSON schema testing and revert package dependencies This commit introduces a comprehensive set of JSON schema testing within the monorepo, specifically adding a new test project in `references/json-schema-test`. This includes a variety of schema definitions and tasks utilizing multiple validation libraries to ensure robust type-checking and runtime validation. Additionally, the dependency versions for `@effect/schema` have been adjusted from `^0.76.5` to `^0.75.5` to maintain compatibility across the project components. This ensures consistent behavior and compatibility with existing code bases without introducing breaking changes or unexpected behavior due to version discrepancies. Key updates include: - Added new test project with extensive schema validation tests. - Ensured type safety across various task implementations. - Reverted dependency versions to ensure compatibility. - Created multiple schema tasks using libraries like Zod, Yup, and others for thorough testing. --- packages/schema-to-json/package.json | 8 +- references/json-schema-test/package.json | 19 + .../src/trigger/core-functionality-test.ts | 424 ++++++++++++++++++ .../src/trigger/simple-type-test.ts | 242 ++++++++++ .../src/trigger/test-batch-operations.ts | 311 +++++++++++++ .../src/trigger/test-other-schemas.ts | 383 ++++++++++++++++ .../json-schema-test/src/trigger/test-yup.ts | 189 ++++++++ .../json-schema-test/src/trigger/test-zod.ts | 282 ++++++++++++ references/json-schema-test/trigger.config.ts | 9 + references/json-schema-test/tsconfig.json | 24 + references/json-schema-test/type-test.ts | 259 +++++++++++ 11 files changed, 2146 insertions(+), 4 deletions(-) create mode 100644 references/json-schema-test/package.json create mode 100644 references/json-schema-test/src/trigger/core-functionality-test.ts create mode 100644 references/json-schema-test/src/trigger/simple-type-test.ts create mode 100644 references/json-schema-test/src/trigger/test-batch-operations.ts create mode 100644 references/json-schema-test/src/trigger/test-other-schemas.ts create mode 100644 references/json-schema-test/src/trigger/test-yup.ts create mode 100644 references/json-schema-test/src/trigger/test-zod.ts create mode 100644 references/json-schema-test/trigger.config.ts create mode 100644 references/json-schema-test/tsconfig.json create mode 100644 references/json-schema-test/type-test.ts diff --git a/packages/schema-to-json/package.json b/packages/schema-to-json/package.json index b55c89487e..a092a3e1db 100644 --- a/packages/schema-to-json/package.json +++ b/packages/schema-to-json/package.json @@ -48,25 +48,25 @@ "@sodaru/yup-to-json-schema": "^2.0.1" }, "devDependencies": { - "@effect/schema": "^0.76.5", + "@effect/schema": "^0.75.5", "arktype": "^2.0.0", "effect": "^3.11.11", "runtypes": "^6.7.0", "superstruct": "^2.0.2", "tshy": "^3.0.2", - "typebox": "^0.34.3", + "@sinclair/typebox": "^0.34.3", "valibot": "^1.0.0-beta.8", "vitest": "^2.1.8", "yup": "^1.6.1", "zod": "^3.24.1 || ^4.0.0" }, "peerDependencies": { - "@effect/schema": "^0.76.5", + "@effect/schema": "^0.75.5", "arktype": "^2.0.0", "effect": "^3.11.11", "runtypes": "^6.7.0", "superstruct": "^2.0.2", - "typebox": "^0.34.3", + "@sinclair/typebox": "^0.34.3", "valibot": "^1.0.0-beta.8", "yup": "^1.6.1", "zod": "^3.24.1 || ^4.0.0" diff --git a/references/json-schema-test/package.json b/references/json-schema-test/package.json new file mode 100644 index 0000000000..577cac465f --- /dev/null +++ b/references/json-schema-test/package.json @@ -0,0 +1,19 @@ +{ + "name": "json-schema-test", + "version": "1.0.0", + "description": "Test project for JSON schema functionality", + "type": "module", + "scripts": { + "dev": "trigger.dev@beta dev", + "trigger:deploy": "trigger.dev@beta deploy", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@trigger.dev/sdk": "workspace:*", + "zod": "3.22.3" + }, + "devDependencies": { + "@types/node": "^20.14.8", + "typescript": "^5.7.2" + } +} \ No newline at end of file diff --git a/references/json-schema-test/src/trigger/core-functionality-test.ts b/references/json-schema-test/src/trigger/core-functionality-test.ts new file mode 100644 index 0000000000..e18187664e --- /dev/null +++ b/references/json-schema-test/src/trigger/core-functionality-test.ts @@ -0,0 +1,424 @@ +// Core functionality test - testing JSON schema implementation with minimal dependencies +import { schemaTask, task, type JSONSchema } from "@trigger.dev/sdk/v3"; +import { z } from "zod"; + +// Test 1: Verify JSONSchema type is exported and usable +const manualSchema: JSONSchema = { + type: "object", + properties: { + id: { type: "string" }, + name: { type: "string", minLength: 1, maxLength: 100 }, + email: { type: "string", format: "email" }, + active: { type: "boolean" }, + score: { type: "number", minimum: 0, maximum: 100 }, + tags: { + type: "array", + items: { type: "string" }, + maxItems: 10, + }, + metadata: { + type: "object", + additionalProperties: true, + }, + }, + required: ["id", "name", "email"], +}; + +// Test 2: Plain task accepts payloadSchema +export const plainJsonSchemaTask = task({ + id: "plain-json-schema-task", + payloadSchema: manualSchema, + run: async (payload, { ctx }) => { + // payload is any, but schema is properly stored + console.log("Received payload:", payload); + + return { + taskId: ctx.task.id, + runId: ctx.run.id, + received: true, + // Manual type assertion needed with plain task + userId: payload.id as string, + userName: payload.name as string, + }; + }, +}); + +// Test 3: Zod schema with automatic conversion +const userSchema = z.object({ + userId: z.string().uuid(), + userName: z.string().min(2).max(50), + userEmail: z.string().email(), + age: z.number().int().min(18).max(120), + preferences: z.object({ + theme: z.enum(["light", "dark", "auto"]).default("auto"), + notifications: z.boolean().default(true), + language: z.string().default("en"), + }), + tags: z.array(z.string()).max(5).default([]), + createdAt: z.string().datetime().optional(), +}); + +export const zodSchemaTask = schemaTask({ + id: "zod-schema-task", + schema: userSchema, + run: async (payload, { ctx }) => { + // Full type inference from Zod schema + console.log("Processing user:", payload.userName); + + // All these are properly typed + const id: string = payload.userId; + const name: string = payload.userName; + const email: string = payload.userEmail; + const age: number = payload.age; + const theme: "light" | "dark" | "auto" = payload.preferences.theme; + const notifications: boolean = payload.preferences.notifications; + const tagCount: number = payload.tags.length; + + return { + processedUserId: id, + processedUserName: name, + processedUserEmail: email, + userAge: age, + theme, + notificationsEnabled: notifications, + tagCount, + }; + }, +}); + +// Test 4: Complex nested schema +const orderSchema = z.object({ + orderId: z.string(), + customerId: z.string(), + items: z.array(z.object({ + productId: z.string(), + productName: z.string(), + quantity: z.number().positive(), + unitPrice: z.number().positive(), + discount: z.number().min(0).max(100).default(0), + })).min(1), + shippingAddress: z.object({ + street: z.string(), + city: z.string(), + state: z.string(), + zipCode: z.string(), + country: z.string().default("US"), + }), + billingAddress: z.object({ + street: z.string(), + city: z.string(), + state: z.string(), + zipCode: z.string(), + country: z.string(), + }).optional(), + paymentMethod: z.discriminatedUnion("type", [ + z.object({ + type: z.literal("credit_card"), + cardNumber: z.string().regex(/^\d{4}$/), // last 4 digits only + cardBrand: z.enum(["visa", "mastercard", "amex", "discover"]), + }), + z.object({ + type: z.literal("paypal"), + paypalEmail: z.string().email(), + }), + z.object({ + type: z.literal("bank_transfer"), + accountNumber: z.string(), + routingNumber: z.string(), + }), + ]), + orderStatus: z.enum(["pending", "processing", "shipped", "delivered", "cancelled"]).default("pending"), + createdAt: z.string().datetime(), + notes: z.string().optional(), +}); + +export const complexOrderTask = schemaTask({ + id: "complex-order-task", + schema: orderSchema, + run: async (payload, { ctx }) => { + // Deep nested type inference + const orderId = payload.orderId; + const firstItem = payload.items[0]; + const productName = firstItem.productName; + const quantity = firstItem.quantity; + + // Calculate totals with full type safety + const subtotal = payload.items.reduce((sum, item) => { + const itemTotal = item.quantity * item.unitPrice; + const discount = itemTotal * (item.discount / 100); + return sum + (itemTotal - discount); + }, 0); + + // Discriminated union handling + let paymentSummary: string; + switch (payload.paymentMethod.type) { + case "credit_card": + paymentSummary = `${payload.paymentMethod.cardBrand} ending in ${payload.paymentMethod.cardNumber}`; + break; + case "paypal": + paymentSummary = `PayPal (${payload.paymentMethod.paypalEmail})`; + break; + case "bank_transfer": + paymentSummary = `Bank transfer ending in ${payload.paymentMethod.accountNumber.slice(-4)}`; + break; + } + + // Optional field handling + const hasBillingAddress = !!payload.billingAddress; + const billingCity = payload.billingAddress?.city ?? payload.shippingAddress.city; + + return { + orderId, + customerId: payload.customerId, + itemCount: payload.items.length, + subtotal, + status: payload.orderStatus, + paymentSummary, + shippingCity: payload.shippingAddress.city, + billingCity, + hasBillingAddress, + hasNotes: !!payload.notes, + }; + }, +}); + +// Test 5: Task trigger type safety +export const testTriggerTypeSafety = task({ + id: "test-trigger-type-safety", + run: async (_, { ctx }) => { + console.log("Testing trigger type safety..."); + + // Valid trigger - should compile + const handle1 = await zodSchemaTask.trigger({ + userId: "550e8400-e29b-41d4-a716-446655440000", + userName: "John Doe", + userEmail: "john@example.com", + age: 30, + preferences: { + theme: "dark", + notifications: false, + language: "es", + }, + tags: ["customer", "premium"], + createdAt: new Date().toISOString(), + }); + + // Using defaults - should also compile + const handle2 = await zodSchemaTask.trigger({ + userId: "550e8400-e29b-41d4-a716-446655440001", + userName: "Jane Smith", + userEmail: "jane@example.com", + age: 25, + preferences: {}, // Will use defaults + // tags will default to [] + }); + + // Test triggerAndWait with result handling + const result = await zodSchemaTask.triggerAndWait({ + userId: "550e8400-e29b-41d4-a716-446655440002", + userName: "Bob Wilson", + userEmail: "bob@example.com", + age: 45, + preferences: { + theme: "light", + }, + }); + + if (result.ok) { + // Type-safe access to output + console.log("Processed user:", result.output.processedUserName); + console.log("User email:", result.output.processedUserEmail); + console.log("Theme:", result.output.theme); + + return { + success: true, + processedUserId: result.output.processedUserId, + userName: result.output.processedUserName, + }; + } else { + return { + success: false, + error: String(result.error), + }; + } + }, +}); + +// Test 6: Batch operations with type safety +export const testBatchOperations = task({ + id: "test-batch-operations", + run: async (_, { ctx }) => { + console.log("Testing batch operations..."); + + // Batch trigger + const batchHandle = await zodSchemaTask.batchTrigger([ + { + payload: { + userId: "batch-001", + userName: "Batch User 1", + userEmail: "batch1@example.com", + age: 20, + preferences: { + theme: "dark", + }, + }, + }, + { + payload: { + userId: "batch-002", + userName: "Batch User 2", + userEmail: "batch2@example.com", + age: 30, + preferences: { + theme: "light", + notifications: false, + }, + tags: ["batch", "test"], + }, + }, + ]); + + console.log(`Triggered batch ${batchHandle.batchId} with ${batchHandle.runCount} runs`); + + // Batch trigger and wait + const batchResult = await zodSchemaTask.batchTriggerAndWait([ + { + payload: { + userId: "batch-003", + userName: "Batch User 3", + userEmail: "batch3@example.com", + age: 40, + preferences: {}, + }, + }, + { + payload: { + userId: "batch-004", + userName: "Batch User 4", + userEmail: "batch4@example.com", + age: 50, + preferences: { + language: "fr", + }, + tags: ["batch", "wait"], + }, + }, + ]); + + // Process results with type safety + const processed = batchResult.runs.map(run => { + if (run.ok) { + return { + success: true, + userId: run.output.processedUserId, + userName: run.output.processedUserName, + theme: run.output.theme, + }; + } else { + return { + success: false, + runId: run.id, + error: String(run.error), + }; + } + }); + + return { + batchId: batchResult.id, + totalRuns: batchResult.runs.length, + successfulRuns: processed.filter(p => p.success).length, + processed, + }; + }, +}); + +// Test 7: Integration test - all features together +export const integrationTest = task({ + id: "json-schema-integration-test", + run: async (_, { ctx }) => { + console.log("Running integration test..."); + + const results = { + plainTask: false, + zodTask: false, + complexTask: false, + triggerTypes: false, + batchOps: false, + }; + + try { + // Test plain JSON schema task + const plainResult = await plainJsonSchemaTask.trigger({ + id: "test-001", + name: "Test User", + email: "test@example.com", + active: true, + score: 85, + tags: ["test"], + metadata: { source: "integration-test" }, + }); + results.plainTask = !!plainResult.id; + + // Test Zod schema task + const zodResult = await zodSchemaTask.triggerAndWait({ + userId: "int-test-001", + userName: "Integration Test User", + userEmail: "integration@example.com", + age: 35, + preferences: { + theme: "auto", + }, + }).unwrap(); + results.zodTask = zodResult.processedUserId === "int-test-001"; + + // Test complex schema + const complexResult = await complexOrderTask.trigger({ + orderId: "order-int-001", + customerId: "cust-int-001", + items: [ + { + productId: "prod-001", + productName: "Test Product", + quantity: 2, + unitPrice: 29.99, + discount: 10, + }, + ], + shippingAddress: { + street: "123 Test St", + city: "Test City", + state: "TC", + zipCode: "12345", + }, + paymentMethod: { + type: "credit_card", + cardNumber: "1234", + cardBrand: "visa", + }, + createdAt: new Date().toISOString(), + }); + results.complexTask = !!complexResult.id; + + // Test trigger type safety + const triggerResult = await testTriggerTypeSafety.triggerAndWait({}); + results.triggerTypes = triggerResult.ok && triggerResult.output.success; + + // Test batch operations + const batchResult = await testBatchOperations.triggerAndWait({}); + results.batchOps = batchResult.ok && batchResult.output.successfulRuns > 0; + + } catch (error) { + console.error("Integration test error:", error); + } + + const allPassed = Object.values(results).every(r => r); + + return { + success: allPassed, + results, + message: allPassed + ? "All JSON schema tests passed!" + : "Some tests failed - check results", + }; + }, +}); \ No newline at end of file diff --git a/references/json-schema-test/src/trigger/simple-type-test.ts b/references/json-schema-test/src/trigger/simple-type-test.ts new file mode 100644 index 0000000000..e8f8c7b2b2 --- /dev/null +++ b/references/json-schema-test/src/trigger/simple-type-test.ts @@ -0,0 +1,242 @@ +// This file tests the core JSON schema functionality without external dependencies +import { schemaTask, task, type JSONSchema } from "@trigger.dev/sdk/v3"; +import { z } from "zod"; + +// Test 1: Basic type inference with schemaTask +const userSchema = z.object({ + id: z.string(), + name: z.string(), + email: z.string().email(), + age: z.number(), +}); + +export const testZodTypeInference = schemaTask({ + id: "test-zod-type-inference", + schema: userSchema, + run: async (payload, { ctx }) => { + // These should all be properly typed without explicit type annotations + const id = payload.id; // string + const name = payload.name; // string + const email = payload.email; // string + const age = payload.age; // number + + // This would cause a TypeScript error if uncommented: + // const invalid = payload.nonExistentField; + + return { + userId: id, + userName: name, + userEmail: email, + userAge: age, + }; + }, +}); + +// Test 2: JSONSchema type is properly exported and usable +const jsonSchemaExample: JSONSchema = { + type: "object", + properties: { + message: { type: "string" }, + count: { type: "integer" }, + active: { type: "boolean" }, + }, + required: ["message", "count"], +}; + +export const testJSONSchemaType = task({ + id: "test-json-schema-type", + payloadSchema: jsonSchemaExample, + run: async (payload, { ctx }) => { + // payload is 'any' with plain task, but the schema is properly typed + return { + received: true, + message: payload.message, + count: payload.count, + active: payload.active ?? false, + }; + }, +}); + +// Test 3: Trigger type safety +export const testTriggerTypeSafety = task({ + id: "test-trigger-type-safety", + run: async (_, { ctx }) => { + // This should compile with proper type inference + const handle1 = await testZodTypeInference.trigger({ + id: "123", + name: "John Doe", + email: "john@example.com", + age: 30, + }); + + // This would cause TypeScript errors if uncommented: + // const handle2 = await testZodTypeInference.trigger({ + // id: 123, // wrong type + // name: "Jane", + // email: "not-an-email", // invalid format (caught at runtime) + // age: "thirty", // wrong type + // }); + + // Test triggerAndWait + const result = await testZodTypeInference.triggerAndWait({ + id: "456", + name: "Jane Smith", + email: "jane@example.com", + age: 25, + }); + + if (result.ok) { + // Type inference works on the output + const userId: string = result.output.userId; + const userName: string = result.output.userName; + const userEmail: string = result.output.userEmail; + const userAge: number = result.output.userAge; + + return { + success: true, + userId, + userName, + userEmail, + userAge, + }; + } else { + return { + success: false, + error: String(result.error), + }; + } + }, +}); + +// Test 4: Batch operations maintain type safety +export const testBatchTypeSafety = task({ + id: "test-batch-type-safety", + run: async (_, { ctx }) => { + // Batch trigger with type safety + const batchHandle = await testZodTypeInference.batchTrigger([ + { + payload: { + id: "1", + name: "User One", + email: "user1@example.com", + age: 20, + }, + }, + { + payload: { + id: "2", + name: "User Two", + email: "user2@example.com", + age: 30, + }, + }, + ]); + + // Batch trigger and wait + const batchResult = await testZodTypeInference.batchTriggerAndWait([ + { + payload: { + id: "3", + name: "User Three", + email: "user3@example.com", + age: 40, + }, + }, + { + payload: { + id: "4", + name: "User Four", + email: "user4@example.com", + age: 50, + }, + }, + ]); + + // Process results with type safety + const successfulUsers: string[] = []; + const failedUsers: string[] = []; + + for (const run of batchResult.runs) { + if (run.ok) { + // output is properly typed + successfulUsers.push(run.output.userId); + } else { + failedUsers.push(run.id); + } + } + + return { + batchId: batchHandle.batchId, + batchRunCount: batchHandle.runCount, + successfulUsers, + failedUsers, + totalProcessed: batchResult.runs.length, + }; + }, +}); + +// Test 5: Complex nested schema +const complexSchema = z.object({ + order: z.object({ + id: z.string(), + items: z.array(z.object({ + productId: z.string(), + quantity: z.number(), + price: z.number(), + })), + customer: z.object({ + id: z.string(), + email: z.string().email(), + address: z.object({ + street: z.string(), + city: z.string(), + country: z.string(), + }).optional(), + }), + }), + metadata: z.record(z.unknown()).optional(), +}); + +export const testComplexSchema = schemaTask({ + id: "test-complex-schema", + schema: complexSchema, + run: async (payload, { ctx }) => { + // Deep type inference works + const orderId = payload.order.id; + const firstItem = payload.order.items[0]; + const quantity = firstItem?.quantity ?? 0; + const customerEmail = payload.order.customer.email; + const city = payload.order.customer.address?.city; + + // Calculate total + const total = payload.order.items.reduce( + (sum, item) => sum + (item.quantity * item.price), + 0 + ); + + return { + orderId, + customerEmail, + itemCount: payload.order.items.length, + total, + hasAddress: !!payload.order.customer.address, + city: city ?? "Unknown", + }; + }, +}); + +// Test 6: Verify that JSON schema is properly set during task registration +export const verifySchemaRegistration = task({ + id: "verify-schema-registration", + run: async (_, { ctx }) => { + // This test verifies that when we create tasks with schemas, + // they properly register the payloadSchema for syncing to the server + + return { + test: "Schema registration", + message: "If this task runs, schema registration is working", + // The actual verification happens during indexing when the CLI + // reads the task metadata and sees the payloadSchema field + }; + }, +}); \ No newline at end of file diff --git a/references/json-schema-test/src/trigger/test-batch-operations.ts b/references/json-schema-test/src/trigger/test-batch-operations.ts new file mode 100644 index 0000000000..627981c81c --- /dev/null +++ b/references/json-schema-test/src/trigger/test-batch-operations.ts @@ -0,0 +1,311 @@ +import { schemaTask, task } from "@trigger.dev/sdk/v3"; +import { z } from "zod"; + +// Define schemas for batch operations +const emailSchema = z.object({ + to: z.string().email(), + subject: z.string(), + body: z.string(), + priority: z.enum(["low", "normal", "high"]).default("normal"), +}); + +const smsSchema = z.object({ + phoneNumber: z.string().regex(/^\+[1-9]\d{1,14}$/), + message: z.string().max(160), +}); + +const notificationSchema = z.object({ + userId: z.string(), + title: z.string(), + message: z.string(), + type: z.enum(["info", "warning", "error", "success"]), + metadata: z.record(z.unknown()).optional(), +}); + +// Create schema tasks +export const sendEmail = schemaTask({ + id: "send-email", + schema: emailSchema, + run: async (payload, { ctx }) => { + // Simulate sending email + await new Promise(resolve => setTimeout(resolve, Math.random() * 1000)); + + return { + messageId: `email_${ctx.run.id}`, + sentAt: new Date().toISOString(), + to: payload.to, + subject: payload.subject, + priority: payload.priority, + }; + }, +}); + +export const sendSms = schemaTask({ + id: "send-sms", + schema: smsSchema, + run: async (payload, { ctx }) => { + // Simulate sending SMS + await new Promise(resolve => setTimeout(resolve, Math.random() * 500)); + + return { + messageId: `sms_${ctx.run.id}`, + sentAt: new Date().toISOString(), + to: payload.phoneNumber, + characterCount: payload.message.length, + }; + }, +}); + +export const sendNotification = schemaTask({ + id: "send-notification", + schema: notificationSchema, + run: async (payload, { ctx }) => { + // Simulate sending notification + await new Promise(resolve => setTimeout(resolve, Math.random() * 300)); + + return { + notificationId: `notif_${ctx.run.id}`, + sentAt: new Date().toISOString(), + userId: payload.userId, + type: payload.type, + delivered: Math.random() > 0.1, // 90% success rate + }; + }, +}); + +// Test batch operations with schema tasks +export const testBatchTrigger = task({ + id: "test-batch-trigger", + run: async (_, { ctx }) => { + // Batch trigger emails + const emailBatch = await sendEmail.batchTrigger([ + { + payload: { + to: "user1@example.com", + subject: "Welcome!", + body: "Welcome to our service.", + priority: "high", + }, + }, + { + payload: { + to: "user2@example.com", + subject: "Weekly Update", + body: "Here's your weekly update.", + // priority will default to "normal" + }, + }, + { + payload: { + to: "user3@example.com", + subject: "Special Offer", + body: "Check out our special offer!", + priority: "low", + }, + }, + ]); + + // Batch trigger SMS messages + const smsBatch = await sendSms.batchTrigger([ + { + payload: { + phoneNumber: "+1234567890", + message: "Your verification code is 123456", + }, + }, + { + payload: { + phoneNumber: "+9876543210", + message: "Appointment reminder: Tomorrow at 2PM", + }, + }, + ]); + + return { + emailBatchId: emailBatch.batchId, + emailCount: emailBatch.runCount, + smsBatchId: smsBatch.batchId, + smsCount: smsBatch.runCount, + }; + }, +}); + +// Test batch trigger and wait +export const testBatchTriggerAndWait = task({ + id: "test-batch-trigger-and-wait", + run: async (_, { ctx }) => { + // Batch trigger and wait for notifications + const notificationResults = await sendNotification.batchTriggerAndWait([ + { + payload: { + userId: "user123", + title: "Info", + message: "This is an informational message", + type: "info", + }, + }, + { + payload: { + userId: "user456", + title: "Warning", + message: "This is a warning message", + type: "warning", + metadata: { + source: "system", + priority: "medium", + }, + }, + }, + { + payload: { + userId: "user789", + title: "Success", + message: "Operation completed successfully", + type: "success", + }, + }, + ]); + + // Process results + const successCount = notificationResults.runs.filter(run => run.ok).length; + const failureCount = notificationResults.runs.filter(run => !run.ok).length; + const deliveredCount = notificationResults.runs + .filter(run => run.ok && run.output.delivered) + .length; + + // Collect all notification IDs + const notificationIds = notificationResults.runs + .filter(run => run.ok) + .map(run => run.output.notificationId); + + // Type safety check - these should all be properly typed + for (const run of notificationResults.runs) { + if (run.ok) { + const notifId: string = run.output.notificationId; + const sentAt: string = run.output.sentAt; + const userId: string = run.output.userId; + const type: "info" | "warning" | "error" | "success" = run.output.type; + const delivered: boolean = run.output.delivered; + } + } + + return { + batchId: notificationResults.id, + totalRuns: notificationResults.runs.length, + successCount, + failureCount, + deliveredCount, + notificationIds, + }; + }, +}); + +// Test mixed batch operations +export const testMixedBatchOperations = task({ + id: "test-mixed-batch-operations", + run: async (_, { ctx }) => { + // Trigger different types of messages for the same user + const results = await Promise.all([ + // Send welcome email + sendEmail.trigger({ + to: "newuser@example.com", + subject: "Welcome to our platform!", + body: "Thanks for signing up. Here's what you need to know...", + priority: "high", + }), + + // Send SMS verification + sendSms.trigger({ + phoneNumber: "+1234567890", + message: "Welcome! Your verification code is 789012", + }), + + // Send in-app notification + sendNotification.trigger({ + userId: "newuser123", + title: "Account Created", + message: "Your account has been successfully created", + type: "success", + metadata: { + accountType: "premium", + referralCode: "WELCOME2024", + }, + }), + ]); + + // Wait for specific tasks using triggerAndWait + const criticalEmail = await sendEmail.triggerAndWait({ + to: "admin@example.com", + subject: "New User Alert", + body: "A new premium user has signed up", + priority: "high", + }); + + if (criticalEmail.ok) { + const messageId: string = criticalEmail.output.messageId; + const sentAt: string = criticalEmail.output.sentAt; + + return { + allMessagesSent: true, + emailId: results[0].id, + smsId: results[1].id, + notificationId: results[2].id, + criticalEmailId: messageId, + criticalEmailSentAt: sentAt, + }; + } else { + return { + allMessagesSent: false, + error: "Failed to send critical email", + }; + } + }, +}); + +// Test error handling in batch operations +export const testBatchErrorHandling = task({ + id: "test-batch-error-handling", + run: async (_, { ctx }) => { + // Create a batch with some invalid data to test error handling + const results = await sendEmail.batchTriggerAndWait([ + { + payload: { + to: "valid@example.com", + subject: "Valid Email", + body: "This should succeed", + }, + }, + { + payload: { + to: "another.valid@example.com", + subject: "Another Valid Email", + body: "This should also succeed", + priority: "normal", + }, + }, + // Note: We can't actually create invalid payloads at compile time + // because TypeScript prevents it! This is the power of schema tasks. + // If we tried to add { to: "invalid-email", ... }, TypeScript would error + ]); + + // Process results with proper type safety + const report = { + totalAttempts: results.runs.length, + successful: [] as string[], + failed: [] as { id: string; error: string }[], + }; + + for (const run of results.runs) { + if (run.ok) { + report.successful.push(run.output.messageId); + } else { + report.failed.push({ + id: run.id, + error: String(run.error), + }); + } + } + + return report; + }, +}); \ No newline at end of file diff --git a/references/json-schema-test/src/trigger/test-other-schemas.ts b/references/json-schema-test/src/trigger/test-other-schemas.ts new file mode 100644 index 0000000000..fda49154db --- /dev/null +++ b/references/json-schema-test/src/trigger/test-other-schemas.ts @@ -0,0 +1,383 @@ +import { schemaTask } from "@trigger.dev/sdk/v3"; +import { Type } from "@sinclair/typebox"; +import { array, object, string, number, boolean, optional, union, literal, record, Infer } from "superstruct"; +import * as S from "@effect/schema/Schema"; +import { type } from "arktype"; +import * as v from "valibot"; +import * as rt from "runtypes"; + +// Test TypeBox schema +const typeBoxSchema = Type.Object({ + id: Type.String({ pattern: "^[a-zA-Z0-9]+$" }), + title: Type.String({ minLength: 1, maxLength: 100 }), + content: Type.String({ minLength: 10 }), + author: Type.Object({ + name: Type.String(), + email: Type.String({ format: "email" }), + role: Type.Union([ + Type.Literal("admin"), + Type.Literal("editor"), + Type.Literal("viewer"), + ]), + }), + tags: Type.Array(Type.String(), { minItems: 1, maxItems: 5 }), + published: Type.Boolean(), + publishedAt: Type.Optional(Type.String({ format: "date-time" })), + metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown())), +}); + +export const typeBoxTask = schemaTask({ + id: "typebox-schema-task", + schema: typeBoxSchema, + run: async (payload, { ctx }) => { + // TypeBox provides static type inference + const id: string = payload.id; + const title: string = payload.title; + const authorEmail: string = payload.author.email; + const role: "admin" | "editor" | "viewer" = payload.author.role; + const tagCount = payload.tags.length; + const isPublished: boolean = payload.published; + + return { + documentId: id, + title, + authorEmail, + role, + tagCount, + status: isPublished ? "published" : "draft", + }; + }, +}); + +// Test Superstruct schema +const superstructSchema = object({ + transaction: object({ + id: string(), + amount: number(), + currency: union([literal("USD"), literal("EUR"), literal("GBP")]), + type: union([literal("credit"), literal("debit")]), + description: optional(string()), + tags: optional(array(string())), + metadata: optional(record(string(), string())), + }), + account: object({ + accountId: string(), + balance: number(), + overdraftLimit: optional(number()), + }), + timestamp: string(), + approved: boolean(), +}); + +type SuperstructTransaction = Infer; + +export const superstructTask = schemaTask({ + id: "superstruct-schema-task", + schema: superstructSchema, + run: async (payload: SuperstructTransaction, { ctx }) => { + // Superstruct infers types correctly + const transactionId = payload.transaction.id; + const amount = payload.transaction.amount; + const currency = payload.transaction.currency; + const accountBalance = payload.account.balance; + const isApproved = payload.approved; + + const newBalance = payload.transaction.type === "credit" + ? accountBalance + amount + : accountBalance - amount; + + return { + transactionId, + processedAt: new Date().toISOString(), + newBalance, + currency, + approved: isApproved, + requiresReview: newBalance < 0 && !payload.account.overdraftLimit, + }; + }, +}); + +// Test Effect Schema +const effectSchema = S.Struct({ + event: S.Struct({ + eventId: S.String, + eventType: S.Literal("click", "view", "purchase"), + timestamp: S.Date, + sessionId: S.String, + }), + user: S.Struct({ + userId: S.String, + email: S.String, + attributes: S.optional(S.Record(S.String, S.Unknown)), + }), + product: S.optional(S.Struct({ + productId: S.String, + name: S.String, + price: S.Number, + category: S.String, + })), + location: S.optional(S.Struct({ + country: S.String, + city: S.optional(S.String), + region: S.optional(S.String), + })), +}); + +type EffectEvent = S.Schema.Type; + +export const effectSchemaTask = schemaTask({ + id: "effect-schema-task", + schema: effectSchema, + run: async (payload: EffectEvent, { ctx }) => { + // Effect Schema provides type safety + const eventId = payload.event.eventId; + const eventType = payload.event.eventType; + const userId = payload.user.userId; + const hasProduct = !!payload.product; + const productName = payload.product?.name; + const country = payload.location?.country; + + return { + eventId, + eventType, + userId, + hasProduct, + productName, + country: country ?? "unknown", + processed: true, + }; + }, +}); + +// Test ArkType schema +const arkTypeSchema = type({ + request: { + method: "'GET' | 'POST' | 'PUT' | 'DELETE'", + path: "string", + headers: "Record", + "body?": "unknown", + "query?": "Record", + }, + response: { + status: "number", + "headers?": "Record", + "body?": "unknown", + }, + timing: { + start: "Date", + end: "Date", + duration: "number", + }, + "metadata?": { + "ip?": "string", + "userAgent?": "string", + "referer?": "string", + }, +}); + +export const arkTypeTask = schemaTask({ + id: "arktype-schema-task", + schema: arkTypeSchema, + run: async (payload, { ctx }) => { + // ArkType infers types + const method = payload.request.method; + const path = payload.request.path; + const status = payload.response.status; + const duration = payload.timing.duration; + const hasBody = !!payload.request.body; + const userAgent = payload.metadata?.userAgent; + + return { + logId: `log_${ctx.run.id}`, + method, + path, + status, + duration, + hasBody, + userAgent: userAgent ?? "unknown", + success: status >= 200 && status < 300, + }; + }, +}); + +// Test Valibot schema +const valibotSchema = v.object({ + form: v.object({ + name: v.pipe(v.string(), v.minLength(2), v.maxLength(50)), + email: v.pipe(v.string(), v.email()), + age: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(150)), + website: v.optional(v.pipe(v.string(), v.url())), + bio: v.optional(v.pipe(v.string(), v.maxLength(500))), + interests: v.array(v.string()), + preferences: v.object({ + theme: v.union([v.literal("light"), v.literal("dark"), v.literal("auto")]), + notifications: v.boolean(), + language: v.string(), + }), + }), + submittedAt: v.string(), + source: v.union([v.literal("web"), v.literal("mobile"), v.literal("api")]), +}); + +type ValibotForm = v.InferOutput; + +export const valibotTask = schemaTask({ + id: "valibot-schema-task", + schema: valibotSchema, + run: async (payload: ValibotForm, { ctx }) => { + // Valibot provides type inference + const name = payload.form.name; + const email = payload.form.email; + const age = payload.form.age; + const hasWebsite = !!payload.form.website; + const theme = payload.form.preferences.theme; + const source = payload.source; + + return { + submissionId: `sub_${ctx.run.id}`, + name, + email, + age, + hasWebsite, + theme, + source, + processed: true, + }; + }, +}); + +// Test Runtypes schema +const runtypesSchema = rt.Record({ + payment: rt.Record({ + paymentId: rt.String, + amount: rt.Number, + currency: rt.Union(rt.Literal("USD"), rt.Literal("EUR"), rt.Literal("GBP")), + method: rt.Union( + rt.Record({ + type: rt.Literal("card"), + last4: rt.String, + brand: rt.String, + }), + rt.Record({ + type: rt.Literal("bank"), + accountNumber: rt.String, + routingNumber: rt.String, + }), + ), + status: rt.Union( + rt.Literal("pending"), + rt.Literal("processing"), + rt.Literal("completed"), + rt.Literal("failed"), + ), + }), + customer: rt.Record({ + customerId: rt.String, + email: rt.String, + name: rt.String, + }), + metadata: rt.Optional(rt.Dictionary(rt.Unknown)), +}); + +type RuntypesPayment = rt.Static; + +export const runtypesTask = schemaTask({ + id: "runtypes-schema-task", + schema: runtypesSchema, + run: async (payload: RuntypesPayment, { ctx }) => { + // Runtypes provides static types + const paymentId = payload.payment.paymentId; + const amount = payload.payment.amount; + const currency = payload.payment.currency; + const status = payload.payment.status; + const customerEmail = payload.customer.email; + + // Discriminated union handling + const paymentDetails = payload.payment.method.type === "card" + ? `Card ending in ${payload.payment.method.last4}` + : `Bank account ${payload.payment.method.accountNumber}`; + + return { + paymentId, + amount, + currency, + status, + customerEmail, + paymentDetails, + requiresAction: status === "pending" || status === "processing", + }; + }, +}); + +// Test task that triggers all schema tasks +export const testAllSchemas = schemaTask({ + id: "test-all-schemas", + schema: z.object({ runAll: z.boolean() }), + run: async (payload, { ctx }) => { + const results = []; + + // Test TypeBox + const typeBoxResult = await typeBoxTask.trigger({ + id: "doc123", + title: "Test Document", + content: "This is a test document with sufficient content.", + author: { + name: "John Doe", + email: "john@example.com", + role: "editor", + }, + tags: ["test", "sample"], + published: true, + publishedAt: new Date().toISOString(), + }); + results.push({ task: "typebox", runId: typeBoxResult.id }); + + // Test Superstruct + const superstructResult = await superstructTask.trigger({ + transaction: { + id: "txn123", + amount: 100.50, + currency: "USD", + type: "credit", + description: "Test transaction", + }, + account: { + accountId: "acc456", + balance: 1000, + overdraftLimit: 500, + }, + timestamp: new Date().toISOString(), + approved: true, + }); + results.push({ task: "superstruct", runId: superstructResult.id }); + + // Test Effect Schema + const effectResult = await effectSchemaTask.trigger({ + event: { + eventId: "evt789", + eventType: "purchase", + timestamp: new Date(), + sessionId: "sess123", + }, + user: { + userId: "user456", + email: "user@example.com", + }, + product: { + productId: "prod789", + name: "Test Product", + price: 29.99, + category: "Electronics", + }, + }); + results.push({ task: "effect", runId: effectResult.id }); + + return { + tested: results.length, + results, + }; + }, +}); + +// Import zod for the test task +import { z } from "zod"; \ No newline at end of file diff --git a/references/json-schema-test/src/trigger/test-yup.ts b/references/json-schema-test/src/trigger/test-yup.ts new file mode 100644 index 0000000000..7c98796605 --- /dev/null +++ b/references/json-schema-test/src/trigger/test-yup.ts @@ -0,0 +1,189 @@ +import { schemaTask } from "@trigger.dev/sdk/v3"; +import * as yup from "yup"; + +// Test Yup schema conversion +const contactSchema = yup.object({ + firstName: yup.string().required().min(2).max(50), + lastName: yup.string().required().min(2).max(50), + email: yup.string().email().required(), + phone: yup.string().matches(/^[\d\s\-\+\(\)]+$/, "Invalid phone number").optional(), + age: yup.number().positive().integer().min(18).max(120), + preferences: yup.object({ + contactMethod: yup.string().oneOf(["email", "phone", "sms"]).default("email"), + newsletter: yup.boolean().default(false), + language: yup.string().oneOf(["en", "es", "fr", "de"]).default("en"), + }).default({}), + address: yup.object({ + street: yup.string().required(), + city: yup.string().required(), + state: yup.string().length(2).required(), + zip: yup.string().matches(/^\d{5}$/).required(), + }).optional(), + tags: yup.array().of(yup.string()).min(1).max(10), +}); + +export const yupSchemaTask = schemaTask({ + id: "yup-schema-task", + schema: contactSchema, + run: async (payload, { ctx }) => { + // Type checking: payload should be inferred from Yup schema + const fullName = `${payload.firstName} ${payload.lastName}`; + const email: string = payload.email; + const phone: string | undefined = payload.phone; + const age: number = payload.age; + + // Nested properties + const contactMethod = payload.preferences.contactMethod; + const newsletter: boolean = payload.preferences.newsletter; + + // Optional nested object + const hasAddress = !!payload.address; + const city = payload.address?.city; + + // Array + const tagCount = payload.tags?.length ?? 0; + + return { + contactId: `contact_${ctx.run.id}`, + fullName, + email, + hasPhone: !!phone, + hasAddress, + tagCount, + preferredContact: contactMethod, + }; + }, +}); + +// Test complex Yup validation with conditional logic +const orderValidationSchema = yup.object({ + orderType: yup.string().oneOf(["standard", "express", "same-day"]).required(), + items: yup.array().of( + yup.object({ + sku: yup.string().required(), + quantity: yup.number().positive().integer().required(), + price: yup.number().positive().required(), + }) + ).min(1).required(), + shipping: yup.object().when("orderType", { + is: "standard", + then: (schema) => schema.shape({ + method: yup.string().oneOf(["ground", "air"]).required(), + estimatedDays: yup.number().min(3).max(10).required(), + }), + otherwise: (schema) => schema.shape({ + method: yup.string().oneOf(["priority", "express"]).required(), + estimatedDays: yup.number().min(1).max(2).required(), + }), + }), + discount: yup.object({ + code: yup.string().optional(), + percentage: yup.number().min(0).max(100).when("code", { + is: (code: any) => !!code, + then: (schema) => schema.required(), + otherwise: (schema) => schema.optional(), + }), + }).optional(), + customerNotes: yup.string().max(500).optional(), +}); + +export const yupConditionalTask = schemaTask({ + id: "yup-conditional-task", + schema: orderValidationSchema, + run: async (payload, { ctx }) => { + // Type inference with conditional validation + const orderType = payload.orderType; + const itemCount = payload.items.length; + const totalQuantity = payload.items.reduce((sum, item) => sum + item.quantity, 0); + const totalPrice = payload.items.reduce((sum, item) => sum + (item.quantity * item.price), 0); + + // Shipping details based on order type + const shippingMethod = payload.shipping.method; + const estimatedDays = payload.shipping.estimatedDays; + + // Optional discount + const hasDiscount = !!payload.discount?.code; + const discountPercentage = payload.discount?.percentage ?? 0; + const discountAmount = hasDiscount ? (totalPrice * discountPercentage / 100) : 0; + + return { + orderId: `order_${ctx.run.id}`, + orderType, + itemCount, + totalQuantity, + subtotal: totalPrice, + discount: discountAmount, + total: totalPrice - discountAmount, + shipping: { + method: shippingMethod, + estimatedDelivery: new Date(Date.now() + estimatedDays * 24 * 60 * 60 * 1000).toISOString(), + }, + }; + }, +}); + +// Test Yup with custom validation and transforms +const userRegistrationSchema = yup.object({ + username: yup.string() + .required() + .min(3) + .max(20) + .matches(/^[a-zA-Z0-9_]+$/, "Username can only contain letters, numbers, and underscores") + .test("no-reserved", "Username is reserved", (value) => { + const reserved = ["admin", "root", "system", "user"]; + return !reserved.includes(value?.toLowerCase() ?? ""); + }), + email: yup.string() + .email() + .required() + .test("email-domain", "Email domain not allowed", (value) => { + const blockedDomains = ["tempmail.com", "throwaway.email"]; + const domain = value?.split("@")[1]?.toLowerCase(); + return !blockedDomains.includes(domain ?? ""); + }), + password: yup.string() + .required() + .min(8) + .matches(/[A-Z]/, "Password must contain at least one uppercase letter") + .matches(/[a-z]/, "Password must contain at least one lowercase letter") + .matches(/[0-9]/, "Password must contain at least one number") + .matches(/[^A-Za-z0-9]/, "Password must contain at least one special character"), + confirmPassword: yup.string() + .required() + .oneOf([yup.ref("password")], "Passwords must match"), + dateOfBirth: yup.date() + .required() + .max(new Date(), "Date of birth cannot be in the future") + .test("age", "Must be at least 18 years old", (value) => { + if (!value) return false; + const age = new Date().getFullYear() - value.getFullYear(); + return age >= 18; + }), + termsAccepted: yup.boolean() + .required() + .oneOf([true], "You must accept the terms and conditions"), +}); + +export const yupCustomValidationTask = schemaTask({ + id: "yup-custom-validation-task", + schema: userRegistrationSchema, + run: async (payload, { ctx }) => { + // All validations have passed if we get here + const username: string = payload.username; + const email: string = payload.email; + const dateOfBirth: Date = payload.dateOfBirth; + const termsAccepted: boolean = payload.termsAccepted; + + // Calculate age + const age = new Date().getFullYear() - dateOfBirth.getFullYear(); + + return { + userId: `user_${ctx.run.id}`, + username, + email, + age, + registeredAt: new Date().toISOString(), + welcomeEmailRequired: true, + }; + }, +}); \ No newline at end of file diff --git a/references/json-schema-test/src/trigger/test-zod.ts b/references/json-schema-test/src/trigger/test-zod.ts new file mode 100644 index 0000000000..34e483ad6d --- /dev/null +++ b/references/json-schema-test/src/trigger/test-zod.ts @@ -0,0 +1,282 @@ +import { schemaTask, task, type JSONSchema } from "@trigger.dev/sdk/v3"; +import { z } from "zod"; + +// Test 1: Basic Zod schema with schemaTask +const userSchema = z.object({ + id: z.string(), + name: z.string(), + email: z.string().email(), + age: z.number().int().min(0).max(150), + isActive: z.boolean(), + roles: z.array(z.enum(["admin", "user", "guest"])), + metadata: z.record(z.unknown()).optional(), +}); + +export const zodSchemaTask = schemaTask({ + id: "zod-schema-task", + schema: userSchema, + run: async (payload, { ctx }) => { + // Type checking: payload should be fully typed + const id: string = payload.id; + const name: string = payload.name; + const email: string = payload.email; + const age: number = payload.age; + const isActive: boolean = payload.isActive; + const roles: ("admin" | "user" | "guest")[] = payload.roles; + const metadata: Record | undefined = payload.metadata; + + return { + processed: true, + userId: payload.id, + userName: payload.name, + }; + }, +}); + +// Test 2: Complex nested Zod schema +const complexSchema = z.object({ + order: z.object({ + orderId: z.string().uuid(), + items: z.array(z.object({ + productId: z.string(), + quantity: z.number().positive(), + price: z.number().positive(), + discount: z.number().min(0).max(100).optional(), + })), + customer: z.object({ + customerId: z.string(), + email: z.string().email(), + shippingAddress: z.object({ + street: z.string(), + city: z.string(), + state: z.string().length(2), + zipCode: z.string().regex(/^\d{5}(-\d{4})?$/), + country: z.string().default("US"), + }), + }), + paymentMethod: z.discriminatedUnion("type", [ + z.object({ + type: z.literal("credit_card"), + last4: z.string().length(4), + brand: z.enum(["visa", "mastercard", "amex"]), + }), + z.object({ + type: z.literal("paypal"), + email: z.string().email(), + }), + ]), + createdAt: z.string().datetime(), + status: z.enum(["pending", "processing", "shipped", "delivered", "cancelled"]), + }), + notes: z.string().optional(), + priority: z.number().int().min(1).max(5).default(3), +}); + +export const complexZodTask = schemaTask({ + id: "complex-zod-task", + schema: complexSchema, + run: async (payload, { ctx }) => { + // Test type inference on nested properties + const orderId: string = payload.order.orderId; + const firstItem = payload.order.items[0]; + const quantity: number = firstItem.quantity; + const customerEmail: string = payload.order.customer.email; + const zipCode: string = payload.order.customer.shippingAddress.zipCode; + + // Discriminated union type checking + if (payload.order.paymentMethod.type === "credit_card") { + const brand: "visa" | "mastercard" | "amex" = payload.order.paymentMethod.brand; + const last4: string = payload.order.paymentMethod.last4; + } else { + const paypalEmail: string = payload.order.paymentMethod.email; + } + + return { + orderId: payload.order.orderId, + itemCount: payload.order.items.length, + status: payload.order.status, + }; + }, +}); + +// Test 3: Plain task with manual JSON schema +const manualJsonSchema: JSONSchema = { + type: "object", + properties: { + taskId: { type: "string", pattern: "^task_[a-zA-Z0-9]+$" }, + priority: { type: "integer", minimum: 1, maximum: 10 }, + tags: { + type: "array", + items: { type: "string" }, + minItems: 1, + maxItems: 5, + }, + config: { + type: "object", + properties: { + timeout: { type: "number" }, + retries: { type: "integer", minimum: 0 }, + async: { type: "boolean" }, + }, + required: ["timeout", "retries"], + }, + }, + required: ["taskId", "priority"], + additionalProperties: false, +}; + +export const plainJsonSchemaTask = task({ + id: "plain-json-schema-task", + payloadSchema: manualJsonSchema, + run: async (payload, { ctx }) => { + // With plain task, payload is 'any' so we need to manually type it + const taskId = payload.taskId as string; + const priority = payload.priority as number; + const tags = payload.tags as string[] | undefined; + const config = payload.config as { timeout: number; retries: number; async?: boolean } | undefined; + + return { + processed: true, + taskId, + priority, + hasConfig: !!config, + }; + }, +}); + +// Test 4: Testing trigger type safety +export const testTriggerTypeSafety = task({ + id: "test-trigger-type-safety", + run: async (_, { ctx }) => { + // This should compile successfully with proper types + const result1 = await zodSchemaTask.trigger({ + id: "user123", + name: "John Doe", + email: "john@example.com", + age: 30, + isActive: true, + roles: ["user", "admin"], + }); + + // This should show TypeScript errors if uncommented: + // const result2 = await zodSchemaTask.trigger({ + // id: "user123", + // name: "John Doe", + // email: "not-an-email", // Invalid email + // age: "thirty", // Wrong type + // isActive: "yes", // Wrong type + // roles: ["superuser"], // Invalid enum value + // }); + + // Test complex schema trigger + const result3 = await complexZodTask.trigger({ + order: { + orderId: "550e8400-e29b-41d4-a716-446655440000", + items: [ + { + productId: "prod123", + quantity: 2, + price: 29.99, + discount: 10, + }, + ], + customer: { + customerId: "cust456", + email: "customer@example.com", + shippingAddress: { + street: "123 Main St", + city: "Anytown", + state: "CA", + zipCode: "12345", + country: "US", + }, + }, + paymentMethod: { + type: "credit_card", + last4: "1234", + brand: "visa", + }, + createdAt: new Date().toISOString(), + status: "pending", + }, + priority: 5, + }); + + return { + triggered: true, + runIds: [result1.id, result3.id], + }; + }, +}); + +// Test 5: Testing triggerAndWait with proper unwrap +export const testTriggerAndWait = task({ + id: "test-trigger-and-wait", + run: async (_, { ctx }) => { + // Test type inference with triggerAndWait + const result = await zodSchemaTask.triggerAndWait({ + id: "user456", + name: "Jane Smith", + email: "jane@example.com", + age: 25, + isActive: false, + roles: ["guest"], + metadata: { + source: "api", + version: "1.0", + }, + }); + + if (result.ok) { + // result.output should be typed + const processed: boolean = result.output.processed; + const userId: string = result.output.userId; + const userName: string = result.output.userName; + + return { + success: true, + processedUserId: userId, + processedUserName: userName, + }; + } else { + return { + success: false, + error: String(result.error), + }; + } + }, +}); + +// Test 6: Using unwrap() method +export const testUnwrap = task({ + id: "test-unwrap", + run: async (_, { ctx }) => { + try { + // Using unwrap() for cleaner code + const output = await zodSchemaTask.triggerAndWait({ + id: "user789", + name: "Bob Johnson", + email: "bob@example.com", + age: 35, + isActive: true, + roles: ["user"], + }).unwrap(); + + // output is directly typed without needing to check result.ok + const processed: boolean = output.processed; + const userId: string = output.userId; + const userName: string = output.userName; + + return { + unwrapped: true, + userId, + userName, + }; + } catch (error) { + return { + unwrapped: false, + error: String(error), + }; + } + }, +}); \ No newline at end of file diff --git a/references/json-schema-test/trigger.config.ts b/references/json-schema-test/trigger.config.ts new file mode 100644 index 0000000000..8e1eeedf67 --- /dev/null +++ b/references/json-schema-test/trigger.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "@trigger.dev/sdk/v3"; + +export default defineConfig({ + project: "json-schema-test", + retries: { + enabledInDev: false, + }, + triggerDirectories: ["./src/trigger"], +}); \ No newline at end of file diff --git a/references/json-schema-test/tsconfig.json b/references/json-schema-test/tsconfig.json new file mode 100644 index 0000000000..b67ed39319 --- /dev/null +++ b/references/json-schema-test/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2022"], + "moduleResolution": "node", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "allowJs": false, + "noEmit": true, + "paths": { + "@trigger.dev/sdk": ["../../packages/trigger-sdk/src/index.ts"], + "@trigger.dev/sdk/v3": ["../../packages/trigger-sdk/src/v3/index.ts"], + "@trigger.dev/core": ["../../packages/core/src/index.ts"], + "@trigger.dev/core/v3": ["../../packages/core/src/v3/index.ts"], + "@trigger.dev/schema-to-json": ["../../packages/schema-to-json/src/index.ts"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} \ No newline at end of file diff --git a/references/json-schema-test/type-test.ts b/references/json-schema-test/type-test.ts new file mode 100644 index 0000000000..fd59e4e7af --- /dev/null +++ b/references/json-schema-test/type-test.ts @@ -0,0 +1,259 @@ +// Standalone type test to verify JSON schema implementation +// This imports directly from the source files to test compilation + +import { schemaTask, task } from "../../packages/trigger-sdk/src/v3/index.js"; +import type { JSONSchema } from "../../packages/trigger-sdk/src/v3/index.js"; +import { z } from "zod"; + +// Test 1: JSONSchema type is properly exported +const testJsonSchemaType: JSONSchema = { + type: "object", + properties: { + id: { type: "string" }, + name: { type: "string", minLength: 1 }, + age: { type: "integer", minimum: 0, maximum: 150 }, + email: { type: "string", format: "email" }, + tags: { + type: "array", + items: { type: "string" }, + minItems: 0, + maxItems: 10, + }, + active: { type: "boolean" }, + metadata: { + type: "object", + additionalProperties: true, + }, + }, + required: ["id", "name", "email"], + additionalProperties: false, +}; + +// Test 2: Plain task accepts JSONSchema type +const plainTask = task({ + id: "plain-task-with-schema", + payloadSchema: testJsonSchemaType, // This should compile without errors + run: async (payload, { ctx }) => { + return { processed: true }; + }, +}); + +// Test 3: Schema task with Zod +const zodSchema = z.object({ + userId: z.string(), + userName: z.string(), + userEmail: z.string().email(), + isActive: z.boolean(), + score: z.number(), + tags: z.array(z.string()), + metadata: z.record(z.unknown()).optional(), +}); + +const zodTask = schemaTask({ + id: "zod-schema-task", + schema: zodSchema, + run: async (payload, { ctx }) => { + // Type checking - all these should be properly typed + const userId: string = payload.userId; + const userName: string = payload.userName; + const userEmail: string = payload.userEmail; + const isActive: boolean = payload.isActive; + const score: number = payload.score; + const tags: string[] = payload.tags; + const metadata: Record | undefined = payload.metadata; + + return { + processedUserId: userId, + processedUserName: userName, + tagCount: tags.length, + }; + }, +}); + +// Test 4: Complex nested schemas +const nestedSchema = z.object({ + order: z.object({ + orderId: z.string().uuid(), + items: z.array(z.object({ + itemId: z.string(), + quantity: z.number().positive(), + unitPrice: z.number().positive(), + })), + customer: z.object({ + customerId: z.string(), + email: z.string().email(), + shipping: z.object({ + address: z.string(), + city: z.string(), + postalCode: z.string(), + country: z.string(), + }).optional(), + }), + status: z.enum(["pending", "processing", "shipped", "delivered"]), + }), + createdAt: z.string().datetime(), + notes: z.string().optional(), +}); + +const nestedTask = schemaTask({ + id: "nested-schema-task", + schema: nestedSchema, + run: async (payload, { ctx }) => { + // Deep property access with full type safety + const orderId = payload.order.orderId; + const firstItem = payload.order.items[0]; + const quantity = firstItem?.quantity ?? 0; + const email = payload.order.customer.email; + const city = payload.order.customer.shipping?.city; + const status = payload.order.status; + + // Status is properly typed as enum + const isShipped: boolean = status === "shipped" || status === "delivered"; + + return { + orderId, + customerEmail: email, + itemCount: payload.order.items.length, + isShipped, + shippingCity: city ?? "N/A", + }; + }, +}); + +// Test 5: Trigger type safety +async function testTriggerTypes() { + // Valid trigger calls - should compile + const handle1 = await zodTask.trigger({ + userId: "123", + userName: "John Doe", + userEmail: "john@example.com", + isActive: true, + score: 95.5, + tags: ["premium", "verified"], + metadata: { source: "web" }, + }); + + // The following would cause TypeScript errors if uncommented: + /* + const handle2 = await zodTask.trigger({ + userId: 123, // Error: Type 'number' is not assignable to type 'string' + userName: "Jane", + userEmail: "jane@example.com", + isActive: "yes", // Error: Type 'string' is not assignable to type 'boolean' + score: "high", // Error: Type 'string' is not assignable to type 'number' + tags: "single-tag", // Error: Type 'string' is not assignable to type 'string[]' + }); + + const handle3 = await zodTask.trigger({ + // Error: Missing required properties + userId: "456", + userName: "Bob", + }); + */ + + // triggerAndWait with result handling + const result = await zodTask.triggerAndWait({ + userId: "789", + userName: "Alice Smith", + userEmail: "alice@example.com", + isActive: false, + score: 88, + tags: ["new"], + }); + + if (result.ok) { + // Output is properly typed + const processedId: string = result.output.processedUserId; + const processedName: string = result.output.processedUserName; + const tagCount: number = result.output.tagCount; + } + + // Using unwrap + try { + const output = await zodTask.triggerAndWait({ + userId: "999", + userName: "Eve", + userEmail: "eve@example.com", + isActive: true, + score: 100, + tags: ["admin", "super"], + }).unwrap(); + + // Direct access to typed output + console.log(output.processedUserId); + console.log(output.processedUserName); + console.log(output.tagCount); + } catch (error) { + console.error("Task failed:", error); + } +} + +// Test 6: Batch operations type safety +async function testBatchTypes() { + // Batch trigger + const batchHandle = await zodTask.batchTrigger([ + { + payload: { + userId: "b1", + userName: "Batch User 1", + userEmail: "batch1@example.com", + isActive: true, + score: 75, + tags: ["batch"], + }, + }, + { + payload: { + userId: "b2", + userName: "Batch User 2", + userEmail: "batch2@example.com", + isActive: false, + score: 82, + tags: ["batch", "test"], + }, + }, + ]); + + // Batch trigger and wait + const batchResult = await zodTask.batchTriggerAndWait([ + { + payload: { + userId: "b3", + userName: "Batch User 3", + userEmail: "batch3@example.com", + isActive: true, + score: 90, + tags: [], + }, + }, + ]); + + // Process batch results with type safety + for (const run of batchResult.runs) { + if (run.ok) { + const userId: string = run.output.processedUserId; + const userName: string = run.output.processedUserName; + const tagCount: number = run.output.tagCount; + } + } +} + +// Test 7: Verify satisfies works for JSON Schema +const schemaWithSatisfies = { + type: "object", + properties: { + foo: { type: "string" }, + }, + required: ["foo"], +} satisfies JSONSchema; + +const taskWithSatisfies = task({ + id: "task-with-satisfies", + payloadSchema: schemaWithSatisfies, + run: async (payload) => { + return { foo: payload.foo }; + }, +}); + +// If this file compiles without errors, our implementation is working correctly! +console.log("Type tests completed successfully!"); \ No newline at end of file From 64fc703512c7ab9cf630bf9b685de95f09dfdbff Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 28 Jul 2025 08:48:41 +0100 Subject: [PATCH 10/19] Refactor JSON Schema test files for clarity Whitespace and formatting changes were applied across the `json-schema-test` reference project to enhance code readability and cohesion. This included removing unnecessary trailing spaces and ensuring consistent indentation patterns, which improves maintainability and readability by following the project's code style guidelines. - Renamed JSONSchema type annotations to adhere to TypeScript conventions, ensuring that all schema definitions properly satisfy the JSONSchema interface. - Restructured some object declarations for improved clarity, especially within complex schema definitions. - These adjustments are crucial for better future maintainability, reducing potential developer errors when interacting with these test schemas. --- .../src/trigger/simple-type-test.ts | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/references/json-schema-test/src/trigger/simple-type-test.ts b/references/json-schema-test/src/trigger/simple-type-test.ts index e8f8c7b2b2..d49635f376 100644 --- a/references/json-schema-test/src/trigger/simple-type-test.ts +++ b/references/json-schema-test/src/trigger/simple-type-test.ts @@ -19,10 +19,10 @@ export const testZodTypeInference = schemaTask({ const name = payload.name; // string const email = payload.email; // string const age = payload.age; // number - + // This would cause a TypeScript error if uncommented: // const invalid = payload.nonExistentField; - + return { userId: id, userName: name, @@ -33,7 +33,7 @@ export const testZodTypeInference = schemaTask({ }); // Test 2: JSONSchema type is properly exported and usable -const jsonSchemaExample: JSONSchema = { +const jsonSchemaExample = { type: "object", properties: { message: { type: "string" }, @@ -41,7 +41,7 @@ const jsonSchemaExample: JSONSchema = { active: { type: "boolean" }, }, required: ["message", "count"], -}; +} satisfies JSONSchema; export const testJSONSchemaType = task({ id: "test-json-schema-type", @@ -68,7 +68,7 @@ export const testTriggerTypeSafety = task({ email: "john@example.com", age: 30, }); - + // This would cause TypeScript errors if uncommented: // const handle2 = await testZodTypeInference.trigger({ // id: 123, // wrong type @@ -76,7 +76,7 @@ export const testTriggerTypeSafety = task({ // email: "not-an-email", // invalid format (caught at runtime) // age: "thirty", // wrong type // }); - + // Test triggerAndWait const result = await testZodTypeInference.triggerAndWait({ id: "456", @@ -84,14 +84,14 @@ export const testTriggerTypeSafety = task({ email: "jane@example.com", age: 25, }); - + if (result.ok) { // Type inference works on the output const userId: string = result.output.userId; const userName: string = result.output.userName; const userEmail: string = result.output.userEmail; const userAge: number = result.output.userAge; - + return { success: true, userId, @@ -124,14 +124,14 @@ export const testBatchTypeSafety = task({ }, { payload: { - id: "2", + id: "2", name: "User Two", email: "user2@example.com", age: 30, }, }, ]); - + // Batch trigger and wait const batchResult = await testZodTypeInference.batchTriggerAndWait([ { @@ -145,17 +145,17 @@ export const testBatchTypeSafety = task({ { payload: { id: "4", - name: "User Four", + name: "User Four", email: "user4@example.com", age: 50, }, }, ]); - + // Process results with type safety const successfulUsers: string[] = []; const failedUsers: string[] = []; - + for (const run of batchResult.runs) { if (run.ok) { // output is properly typed @@ -164,7 +164,7 @@ export const testBatchTypeSafety = task({ failedUsers.push(run.id); } } - + return { batchId: batchHandle.batchId, batchRunCount: batchHandle.runCount, @@ -179,19 +179,23 @@ export const testBatchTypeSafety = task({ const complexSchema = z.object({ order: z.object({ id: z.string(), - items: z.array(z.object({ - productId: z.string(), - quantity: z.number(), - price: z.number(), - })), + items: z.array( + z.object({ + productId: z.string(), + quantity: z.number(), + price: z.number(), + }) + ), customer: z.object({ id: z.string(), email: z.string().email(), - address: z.object({ - street: z.string(), - city: z.string(), - country: z.string(), - }).optional(), + address: z + .object({ + street: z.string(), + city: z.string(), + country: z.string(), + }) + .optional(), }), }), metadata: z.record(z.unknown()).optional(), @@ -207,13 +211,10 @@ export const testComplexSchema = schemaTask({ const quantity = firstItem?.quantity ?? 0; const customerEmail = payload.order.customer.email; const city = payload.order.customer.address?.city; - + // Calculate total - const total = payload.order.items.reduce( - (sum, item) => sum + (item.quantity * item.price), - 0 - ); - + const total = payload.order.items.reduce((sum, item) => sum + item.quantity * item.price, 0); + return { orderId, customerEmail, @@ -231,7 +232,7 @@ export const verifySchemaRegistration = task({ run: async (_, { ctx }) => { // This test verifies that when we create tasks with schemas, // they properly register the payloadSchema for syncing to the server - + return { test: "Schema registration", message: "If this task runs, schema registration is working", From 0f5647c0afc047cc3d90e41ad439079123bc64fc Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 28 Jul 2025 10:58:39 +0100 Subject: [PATCH 11/19] Fixed some stuff --- packages/core/package.json | 2 +- packages/core/src/v3/schemas/resources.ts | 4 +- packages/core/src/v3/schemas/schemas.ts | 4 +- packages/core/src/v3/types/jsonSchema.ts | 39 +- packages/core/src/v3/types/tasks.ts | 10 + packages/schema-to-json/package.json | 4 +- packages/schema-to-json/src/index.ts | 121 ++-- packages/trigger-sdk/src/v3/shared.ts | 24 +- pnpm-lock.yaml | 632 ++++++++++++++---- references/json-schema-test/README.md | 118 ++++ references/json-schema-test/package.json | 10 +- .../src/trigger/core-functionality-test.ts | 125 ++-- .../src/trigger/simple-type-test.ts | 2 +- .../src/trigger/test-other-schemas.ts | 71 +- 14 files changed, 857 insertions(+), 309 deletions(-) create mode 100644 references/json-schema-test/README.md diff --git a/packages/core/package.json b/packages/core/package.json index 42c76333a4..2f83f11c82 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -579,4 +579,4 @@ "main": "./dist/commonjs/index.js", "types": "./dist/commonjs/index.d.ts", "module": "./dist/esm/index.js" -} +} \ No newline at end of file diff --git a/packages/core/src/v3/schemas/resources.ts b/packages/core/src/v3/schemas/resources.ts index 845044c8b7..08764906ed 100644 --- a/packages/core/src/v3/schemas/resources.ts +++ b/packages/core/src/v3/schemas/resources.ts @@ -13,8 +13,8 @@ export const TaskResource = z.object({ triggerSource: z.string().optional(), schedule: ScheduleMetadata.optional(), maxDuration: z.number().optional(), - // JSONSchema type - using z.record for runtime validation - payloadSchema: z.record(z.unknown()).optional(), + // JSONSchema type - using z.unknown() for runtime validation to accept JSONSchema7 + payloadSchema: z.unknown().optional(), }); export type TaskResource = z.infer; diff --git a/packages/core/src/v3/schemas/schemas.ts b/packages/core/src/v3/schemas/schemas.ts index e65739b33a..721a7725a9 100644 --- a/packages/core/src/v3/schemas/schemas.ts +++ b/packages/core/src/v3/schemas/schemas.ts @@ -189,8 +189,8 @@ const taskMetadata = { triggerSource: z.string().optional(), schedule: ScheduleMetadata.optional(), maxDuration: z.number().optional(), - // JSONSchema type - using z.record for runtime validation - payloadSchema: z.record(z.unknown()).optional(), + // JSONSchema type - using z.unknown() for runtime validation to accept JSONSchema7 + payloadSchema: z.unknown().optional(), }; export const TaskMetadata = z.object(taskMetadata); diff --git a/packages/core/src/v3/types/jsonSchema.ts b/packages/core/src/v3/types/jsonSchema.ts index 54b544e7b5..7abf241a66 100644 --- a/packages/core/src/v3/types/jsonSchema.ts +++ b/packages/core/src/v3/types/jsonSchema.ts @@ -1,32 +1,30 @@ /** - * Basic JSON Schema type definition - * Based on JSON Schema Draft 7 + * JSON Schema type definition - compatible with JSON Schema Draft 7 + * Based on the JSONSchema7 type from @types/json-schema but defined inline to avoid import issues */ -export type JSONSchemaType = "string" | "number" | "integer" | "boolean" | "object" | "array" | "null"; - export interface JSONSchema { $id?: string; $ref?: string; $schema?: string; $comment?: string; - + type?: JSONSchemaType | JSONSchemaType[]; enum?: any[]; const?: any; - + // Number/Integer validations multipleOf?: number; maximum?: number; exclusiveMaximum?: number; minimum?: number; exclusiveMinimum?: number; - + // String validations maxLength?: number; minLength?: number; pattern?: string; format?: string; - + // Array validations items?: JSONSchema | JSONSchema[]; additionalItems?: JSONSchema | boolean; @@ -34,7 +32,7 @@ export interface JSONSchema { minItems?: number; uniqueItems?: boolean; contains?: JSONSchema; - + // Object validations maxProperties?: number; minProperties?: number; @@ -44,18 +42,18 @@ export interface JSONSchema { additionalProperties?: JSONSchema | boolean; dependencies?: Record; propertyNames?: JSONSchema; - - // Conditionals + + // Conditional schemas if?: JSONSchema; then?: JSONSchema; else?: JSONSchema; - + // Boolean logic allOf?: JSONSchema[]; anyOf?: JSONSchema[]; oneOf?: JSONSchema[]; not?: JSONSchema; - + // Metadata title?: string; description?: string; @@ -63,7 +61,16 @@ export interface JSONSchema { readOnly?: boolean; writeOnly?: boolean; examples?: any[]; - - // Additional properties + + // Additional properties for extensibility [key: string]: any; -} \ No newline at end of file +} + +export type JSONSchemaType = + | "string" + | "number" + | "integer" + | "boolean" + | "object" + | "array" + | "null"; diff --git a/packages/core/src/v3/types/tasks.ts b/packages/core/src/v3/types/tasks.ts index 40b6de6e7c..5c7a533eb3 100644 --- a/packages/core/src/v3/types/tasks.ts +++ b/packages/core/src/v3/types/tasks.ts @@ -355,6 +355,16 @@ export type TaskOptions< TInitOutput extends InitOutput = any, > = CommonTaskOptions; +// Task options when payloadSchema is provided - payload should be any +export type TaskOptionsWithSchema< + TIdentifier extends string, + TOutput = unknown, + TInitOutput extends InitOutput = any, +> = Omit, "run"> & { + payloadSchema: JSONSchema; + run: (payload: any, params: RunFnParams) => Promise; +}; + export type TaskWithSchemaOptions< TIdentifier extends string, TSchema extends TaskSchema | undefined = undefined, diff --git a/packages/schema-to-json/package.json b/packages/schema-to-json/package.json index a092a3e1db..4bf03fa735 100644 --- a/packages/schema-to-json/package.json +++ b/packages/schema-to-json/package.json @@ -43,7 +43,7 @@ "check-exports": "tshy --check-exports" }, "dependencies": { - "@types/json-schema": "^7.0.15", + "@trigger.dev/core": "workspace:*", "zod-to-json-schema": "^3.24.5", "@sodaru/yup-to-json-schema": "^2.0.1" }, @@ -65,7 +65,7 @@ "arktype": "^2.0.0", "effect": "^3.11.11", "runtypes": "^6.7.0", - "superstruct": "^2.0.2", + "superstruct": "^2.0.2", "@sinclair/typebox": "^0.34.3", "valibot": "^1.0.0-beta.8", "yup": "^1.6.1", diff --git a/packages/schema-to-json/src/index.ts b/packages/schema-to-json/src/index.ts index 3a5b7b487b..7a3e5899d5 100644 --- a/packages/schema-to-json/src/index.ts +++ b/packages/schema-to-json/src/index.ts @@ -1,18 +1,8 @@ -import type { JSONSchema7, JSONSchema7Definition, JSONSchema7Type, JSONSchema7TypeName, JSONSchema7Object, JSONSchema7Array } from '@types/json-schema'; +// Import JSONSchema from core to ensure compatibility +import type { JSONSchema } from "@trigger.dev/core/v3"; export type Schema = unknown; -export type JSONSchema = JSONSchema7; -export type JSONSchemaDefinition = JSONSchema7Definition; - -// Re-export the standard JSON Schema types for convenience -export type { - JSONSchema7, - JSONSchema7Type, - JSONSchema7TypeName, - JSONSchema7Definition, - JSONSchema7Object, - JSONSchema7Array, -} from '@types/json-schema'; +export type { JSONSchema }; export interface ConversionOptions { /** @@ -33,22 +23,34 @@ export interface ConversionResult { /** * The detected schema type */ - schemaType: 'zod' | 'yup' | 'arktype' | 'effect' | 'valibot' | 'superstruct' | 'runtypes' | 'typebox' | 'unknown'; + schemaType: + | "zod" + | "yup" + | "arktype" + | "effect" + | "valibot" + | "superstruct" + | "runtypes" + | "typebox" + | "unknown"; } /** * Convert a schema from various validation libraries to JSON Schema - * + * * This function attempts to convert schemas without requiring external dependencies to be bundled. * It will only succeed if: * 1. The schema has built-in JSON Schema conversion (ArkType, Zod 4, TypeBox) * 2. The required conversion library is available at runtime (zod-to-json-schema, @sodaru/yup-to-json-schema, etc.) - * + * * @param schema The schema to convert * @param options Optional conversion options * @returns The conversion result or undefined if conversion is not possible */ -export function schemaToJsonSchema(schema: Schema, options?: ConversionOptions): ConversionResult | undefined { +export function schemaToJsonSchema( + schema: Schema, + options?: ConversionOptions +): ConversionResult | undefined { const parser = schema as any; // Check if schema has a built-in toJsonSchema method (e.g., ArkType, Zod 4) @@ -56,10 +58,15 @@ export function schemaToJsonSchema(schema: Schema, options?: ConversionOptions): try { const jsonSchema = parser.toJsonSchema(); // Determine if it's Zod or ArkType based on other methods - const schemaType = (typeof parser.parseAsync === "function" || typeof parser.parse === "function") ? 'zod' : 'arktype'; + const schemaType = + typeof parser.parseAsync === "function" || typeof parser.parse === "function" + ? "zod" + : "arktype"; return { - jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, - schemaType + jsonSchema: options?.additionalProperties + ? { ...jsonSchema, ...options.additionalProperties } + : jsonSchema, + schemaType, }; } catch (error) { // If toJsonSchema fails, continue to other checks @@ -67,40 +74,46 @@ export function schemaToJsonSchema(schema: Schema, options?: ConversionOptions): } // Check if it's a TypeBox schema (has Static and Kind symbols) - if (parser[Symbol.for('TypeBox.Kind')] !== undefined) { + if (parser[Symbol.for("TypeBox.Kind")] !== undefined) { // TypeBox schemas are already JSON Schema compliant return { - jsonSchema: options?.additionalProperties ? { ...parser, ...options.additionalProperties } : parser, - schemaType: 'typebox' + jsonSchema: options?.additionalProperties + ? { ...parser, ...options.additionalProperties } + : parser, + schemaType: "typebox", }; } // For schemas that need external libraries, we need to check if they're available // This approach avoids bundling the dependencies while still allowing runtime usage - + // Check if it's a Zod schema (without built-in toJsonSchema) if (typeof parser.parseAsync === "function" || typeof parser.parse === "function") { try { // Try to access zod-to-json-schema if it's available // @ts-ignore - This is intentionally dynamic - if (typeof globalThis.__zodToJsonSchema !== 'undefined') { + if (typeof globalThis.__zodToJsonSchema !== "undefined") { // @ts-ignore const { zodToJsonSchema } = globalThis.__zodToJsonSchema; - const jsonSchema = options?.name + const jsonSchema = options?.name ? zodToJsonSchema(parser, options.name) : zodToJsonSchema(parser); - - if (jsonSchema && typeof jsonSchema === 'object' && '$schema' in jsonSchema) { + + if (jsonSchema && typeof jsonSchema === "object" && "$schema" in jsonSchema) { const { $schema, ...rest } = jsonSchema as any; return { - jsonSchema: options?.additionalProperties ? { ...rest, ...options.additionalProperties } : rest, - schemaType: 'zod' + jsonSchema: options?.additionalProperties + ? { ...rest, ...options.additionalProperties } + : rest, + schemaType: "zod", }; } - + return { - jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, - schemaType: 'zod' + jsonSchema: options?.additionalProperties + ? { ...jsonSchema, ...options.additionalProperties } + : jsonSchema, + schemaType: "zod", }; } } catch (error) { @@ -112,13 +125,15 @@ export function schemaToJsonSchema(schema: Schema, options?: ConversionOptions): if (typeof parser.validateSync === "function" && typeof parser.describe === "function") { try { // @ts-ignore - if (typeof globalThis.__yupToJsonSchema !== 'undefined') { + if (typeof globalThis.__yupToJsonSchema !== "undefined") { // @ts-ignore const { convertSchema } = globalThis.__yupToJsonSchema; const jsonSchema = convertSchema(parser); return { - jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, - schemaType: 'yup' + jsonSchema: options?.additionalProperties + ? { ...jsonSchema, ...options.additionalProperties } + : jsonSchema, + schemaType: "yup", }; } } catch (error) { @@ -127,16 +142,22 @@ export function schemaToJsonSchema(schema: Schema, options?: ConversionOptions): } // Check if it's an Effect schema - if (parser._tag === "Schema" || parser._tag === "SchemaClass" || typeof parser.ast === "function") { + if ( + parser._tag === "Schema" || + parser._tag === "SchemaClass" || + typeof parser.ast === "function" + ) { try { // @ts-ignore - if (typeof globalThis.__effectJsonSchema !== 'undefined') { + if (typeof globalThis.__effectJsonSchema !== "undefined") { // @ts-ignore const { JSONSchema } = globalThis.__effectJsonSchema; const jsonSchema = JSONSchema.make(parser); return { - jsonSchema: options?.additionalProperties ? { ...jsonSchema, ...options.additionalProperties } : jsonSchema, - schemaType: 'effect' + jsonSchema: options?.additionalProperties + ? { ...jsonSchema, ...options.additionalProperties } + : jsonSchema, + schemaType: "effect", }; } } catch (error) { @@ -158,14 +179,14 @@ export function schemaToJsonSchema(schema: Schema, options?: ConversionOptions): export async function initializeSchemaConverters(): Promise { try { // @ts-ignore - globalThis.__zodToJsonSchema = await import('zod-to-json-schema'); + globalThis.__zodToJsonSchema = await import("zod-to-json-schema"); } catch { // Zod conversion not available } try { // @ts-ignore - globalThis.__yupToJsonSchema = await import('@sodaru/yup-to-json-schema'); + globalThis.__yupToJsonSchema = await import("@sodaru/yup-to-json-schema"); } catch { // Yup conversion not available } @@ -174,9 +195,9 @@ export async function initializeSchemaConverters(): Promise { // Try Effect first, then @effect/schema let module; try { - module = await import('effect'); + module = await import("effect"); } catch { - module = await import('@effect/schema'); + module = await import("@effect/schema"); } if (module?.JSONSchema) { // @ts-ignore @@ -198,9 +219,9 @@ export function canConvertSchema(schema: Schema): boolean { /** * Get the detected schema type */ -export function detectSchemaType(schema: Schema): ConversionResult['schemaType'] { +export function detectSchemaType(schema: Schema): ConversionResult["schemaType"] { const result = schemaToJsonSchema(schema); - return result?.schemaType ?? 'unknown'; + return result?.schemaType ?? "unknown"; } /** @@ -213,10 +234,10 @@ export function areConvertersInitialized(): { } { return { // @ts-ignore - zod: typeof globalThis.__zodToJsonSchema !== 'undefined', + zod: typeof globalThis.__zodToJsonSchema !== "undefined", // @ts-ignore - yup: typeof globalThis.__yupToJsonSchema !== 'undefined', + yup: typeof globalThis.__yupToJsonSchema !== "undefined", // @ts-ignore - effect: typeof globalThis.__effectJsonSchema !== 'undefined', + effect: typeof globalThis.__effectJsonSchema !== "undefined", }; -} \ No newline at end of file +} diff --git a/packages/trigger-sdk/src/v3/shared.ts b/packages/trigger-sdk/src/v3/shared.ts index 7677da9358..d657745884 100644 --- a/packages/trigger-sdk/src/v3/shared.ts +++ b/packages/trigger-sdk/src/v3/shared.ts @@ -84,6 +84,7 @@ import type { TaskBatchOutputHandle, TaskIdentifier, TaskOptions, + TaskOptionsWithSchema, TaskOutput, TaskOutputHandle, TaskPayload, @@ -136,6 +137,16 @@ export function queue(options: QueueOptions): Queue { return options; } +// Overload: when payloadSchema is provided, payload type should be any +export function createTask< + TIdentifier extends string, + TOutput = unknown, + TInitOutput extends InitOutput = any, +>( + params: TaskOptionsWithSchema +): Task; + +// Overload: normal case without payloadSchema export function createTask< TIdentifier extends string, TInput = void, @@ -143,7 +154,18 @@ export function createTask< TInitOutput extends InitOutput = any, >( params: TaskOptions -): Task { +): Task; + +export function createTask< + TIdentifier extends string, + TInput = void, + TOutput = unknown, + TInitOutput extends InitOutput = any, +>( + params: + | TaskOptions + | TaskOptionsWithSchema +): Task | Task { const task: Task = { id: params.id, description: params.description, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 258ebd571a..428bc38513 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -385,22 +385,22 @@ importers: version: 3.7.1(react@18.2.0) '@remix-run/express': specifier: 2.1.0 - version: 2.1.0(express@4.20.0)(typescript@5.5.4) + version: 2.1.0(express@4.20.0)(typescript@5.8.3) '@remix-run/node': specifier: 2.1.0 - version: 2.1.0(typescript@5.5.4) + version: 2.1.0(typescript@5.8.3) '@remix-run/react': specifier: 2.1.0 - version: 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.5.4) + version: 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.8.3) '@remix-run/router': specifier: ^1.15.3 version: 1.15.3 '@remix-run/serve': specifier: 2.1.0 - version: 2.1.0(typescript@5.5.4) + version: 2.1.0(typescript@5.8.3) '@remix-run/server-runtime': specifier: 2.1.0 - version: 2.1.0(typescript@5.5.4) + version: 2.1.0(typescript@5.8.3) '@remix-run/v1-meta': specifier: ^0.1.3 version: 0.1.3(@remix-run/react@2.1.0)(@remix-run/server-runtime@2.1.0) @@ -478,7 +478,7 @@ importers: version: 1.0.18 class-variance-authority: specifier: ^0.5.2 - version: 0.5.2(typescript@5.5.4) + version: 0.5.2(typescript@5.8.3) clsx: specifier: ^1.2.1 version: 1.2.1 @@ -523,7 +523,7 @@ importers: version: 10.12.11(react-dom@18.2.0)(react@18.2.0) graphile-worker: specifier: 0.16.6 - version: 0.16.6(patch_hash=hdpetta7btqcc7xb5wfkcnanoa)(typescript@5.5.4) + version: 0.16.6(patch_hash=hdpetta7btqcc7xb5wfkcnanoa)(typescript@5.8.3) humanize-duration: specifier: ^3.27.3 version: 3.27.3 @@ -731,13 +731,13 @@ importers: version: link:../../internal-packages/testcontainers '@remix-run/dev': specifier: 2.1.0 - version: 2.1.0(@remix-run/serve@2.1.0)(@types/node@20.14.14)(ts-node@10.9.1)(typescript@5.5.4) + version: 2.1.0(@remix-run/serve@2.1.0)(@types/node@20.14.14)(ts-node@10.9.1)(typescript@5.8.3) '@remix-run/eslint-config': specifier: 2.1.0 - version: 2.1.0(eslint@8.31.0)(react@18.2.0)(typescript@5.5.4) + version: 2.1.0(eslint@8.31.0)(react@18.2.0)(typescript@5.8.3) '@remix-run/testing': specifier: ^2.1.0 - version: 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.5.4) + version: 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.8.3) '@sentry/cli': specifier: 2.50.2 version: 2.50.2 @@ -836,10 +836,10 @@ importers: version: 8.5.4 '@typescript-eslint/eslint-plugin': specifier: ^5.59.6 - version: 5.59.6(@typescript-eslint/parser@5.59.6)(eslint@8.31.0)(typescript@5.5.4) + version: 5.59.6(@typescript-eslint/parser@5.59.6)(eslint@8.31.0)(typescript@5.8.3) '@typescript-eslint/parser': specifier: ^5.59.6 - version: 5.59.6(eslint@8.31.0)(typescript@5.5.4) + version: 5.59.6(eslint@8.31.0)(typescript@5.8.3) autoevals: specifier: ^0.0.130 version: 0.0.130(ws@8.12.0) @@ -884,7 +884,7 @@ importers: version: 16.0.1(postcss@8.5.4) postcss-loader: specifier: ^8.1.1 - version: 8.1.1(postcss@8.5.4)(typescript@5.5.4)(webpack@5.99.9) + version: 8.1.1(postcss@8.5.4)(typescript@5.8.3)(webpack@5.99.9) prettier: specifier: ^2.8.8 version: 2.8.8 @@ -911,13 +911,13 @@ importers: version: 3.4.1(ts-node@10.9.1) ts-node: specifier: ^10.7.0 - version: 10.9.1(@swc/core@1.3.26)(@types/node@20.14.14)(typescript@5.5.4) + version: 10.9.1(@swc/core@1.3.26)(@types/node@20.14.14)(typescript@5.8.3) tsconfig-paths: specifier: ^3.14.1 version: 3.14.1 vite-tsconfig-paths: specifier: ^4.0.5 - version: 4.0.5(typescript@5.5.4) + version: 4.0.5(typescript@5.8.3) docs: {} @@ -1810,6 +1810,52 @@ importers: specifier: 4.17.0 version: 4.17.0 + packages/schema-to-json: + dependencies: + '@sodaru/yup-to-json-schema': + specifier: ^2.0.1 + version: 2.0.1 + '@trigger.dev/core': + specifier: workspace:* + version: link:../core + zod-to-json-schema: + specifier: ^3.24.5 + version: 3.24.5(zod@4.0.10) + devDependencies: + '@effect/schema': + specifier: ^0.75.5 + version: 0.75.5(effect@3.17.1) + '@sinclair/typebox': + specifier: ^0.34.3 + version: 0.34.38 + arktype: + specifier: ^2.0.0 + version: 2.1.20 + effect: + specifier: ^3.11.11 + version: 3.17.1 + runtypes: + specifier: ^6.7.0 + version: 6.7.0 + superstruct: + specifier: ^2.0.2 + version: 2.0.2 + tshy: + specifier: ^3.0.2 + version: 3.0.2 + valibot: + specifier: ^1.0.0-beta.8 + version: 1.1.0(typescript@5.5.4) + vitest: + specifier: ^2.1.8 + version: 2.1.9(@types/node@20.14.14) + yup: + specifier: ^1.6.1 + version: 1.6.1 + zod: + specifier: ^3.24.1 || ^4.0.0 + version: 4.0.10 + packages/trigger-sdk: dependencies: '@opentelemetry/api': @@ -1824,6 +1870,9 @@ importers: '@trigger.dev/core': specifier: workspace:4.0.0-v4-beta.25 version: link:../core + '@trigger.dev/schema-to-json': + specifier: workspace:4.0.0-v4-beta.25 + version: link:../schema-to-json chalk: specifier: ^5.2.0 version: 5.2.0 @@ -2114,12 +2163,18 @@ importers: references/hello-world: dependencies: + '@sinclair/typebox': + specifier: ^0.34.3 + version: 0.34.38 '@trigger.dev/build': specifier: workspace:* version: link:../../packages/build '@trigger.dev/sdk': specifier: workspace:* version: link:../../packages/trigger-sdk + arktype: + specifier: ^2.0.0 + version: 2.1.20 openai: specifier: ^4.97.0 version: 4.97.0(ws@8.12.0)(zod@3.23.8) @@ -2129,6 +2184,9 @@ importers: replicate: specifier: ^1.0.1 version: 1.0.1 + yup: + specifier: ^1.6.1 + version: 1.6.1 zod: specifier: 3.23.8 version: 3.23.8 @@ -2149,6 +2207,46 @@ importers: specifier: workspace:* version: link:../../packages/cli-v3 + references/json-schema-test: + dependencies: + '@effect/schema': + specifier: ^0.75.5 + version: 0.75.5(effect@3.17.1) + '@sinclair/typebox': + specifier: ^0.34.3 + version: 0.34.38 + '@trigger.dev/sdk': + specifier: workspace:* + version: link:../../packages/trigger-sdk + arktype: + specifier: ^2.0.0 + version: 2.1.20 + effect: + specifier: ^3.11.11 + version: 3.17.1 + runtypes: + specifier: ^6.7.0 + version: 6.7.0 + superstruct: + specifier: ^2.0.2 + version: 2.0.2 + valibot: + specifier: ^1.0.0-beta.8 + version: 1.1.0(typescript@5.8.3) + yup: + specifier: ^1.6.1 + version: 1.6.1 + zod: + specifier: 3.22.3 + version: 3.22.3 + devDependencies: + '@types/node': + specifier: ^20.14.8 + version: 20.14.14 + typescript: + specifier: ^5.7.2 + version: 5.8.3 + references/nextjs-realtime: dependencies: '@ai-sdk/openai': @@ -2330,10 +2428,10 @@ importers: version: 2.2.1 '@t3-oss/env-core': specifier: ^0.11.0 - version: 0.11.0(typescript@5.5.4)(zod@3.23.8) + version: 0.11.0(typescript@5.8.3)(zod@3.23.8) '@t3-oss/env-nextjs': specifier: ^0.10.1 - version: 0.10.1(typescript@5.5.4)(zod@3.23.8) + version: 0.10.1(typescript@5.8.3)(zod@3.23.8) '@traceloop/instrumentation-openai': specifier: ^0.10.0 version: 0.10.0(@opentelemetry/api@1.4.1) @@ -2375,7 +2473,7 @@ importers: version: 0.27.4 msw: specifier: ^2.2.1 - version: 2.3.5(typescript@5.5.4) + version: 2.3.5(typescript@5.8.3) mupdf: specifier: ^1.3.6 version: 1.3.6 @@ -2390,7 +2488,7 @@ importers: version: 1.50.1 puppeteer: specifier: ^23.4.0 - version: 23.4.0(typescript@5.5.4) + version: 23.4.0(typescript@5.8.3) react: specifier: 19.0.0-rc.0 version: 19.0.0-rc.0 @@ -2423,7 +2521,7 @@ importers: version: 0.3.20(pg@8.11.5)(sqlite3@5.1.7)(ts-node@10.9.2) valibot: specifier: ^0.42.1 - version: 0.42.1(typescript@5.5.4) + version: 0.42.1(typescript@5.8.3) wrangler: specifier: 3.70.0 version: 3.70.0 @@ -2514,7 +2612,7 @@ importers: version: link:../../packages/cli-v3 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.14.14)(typescript@5.5.4) + version: 10.9.2(@types/node@20.14.14)(typescript@5.8.3) tsconfig-paths: specifier: ^4.2.0 version: 4.2.0 @@ -2957,7 +3055,7 @@ packages: '@ai-sdk/provider-utils': 1.0.17(zod@3.23.8) '@ai-sdk/ui-utils': 0.0.40(zod@3.23.8) swrv: 1.0.4(vue@3.5.16) - vue: 3.5.16(typescript@5.5.4) + vue: 3.5.16(typescript@5.8.3) transitivePeerDependencies: - zod dev: false @@ -3059,10 +3157,18 @@ packages: '@ark/util': 0.18.0 dev: false + /@ark/schema@0.46.0: + resolution: {integrity: sha512-c2UQdKgP2eqqDArfBqQIJppxJHvNNXuQPeuSPlDML4rjw+f1cu0qAlzOG4b8ujgm9ctIDWwhpyw6gjG5ledIVQ==} + dependencies: + '@ark/util': 0.46.0 + /@ark/util@0.18.0: resolution: {integrity: sha512-TpHY532LKQwwYHui5NN/eO/6eSiSMvf652YNt1BsV7fya7RzXL27IiU9x4bm7jTFZxLQGYDQTB7nw41TqeuF4g==} dev: false + /@ark/util@0.46.0: + resolution: {integrity: sha512-JPy/NGWn/lvf1WmGCPw2VGpBg5utZraE84I7wli18EDF3p3zc/e9WolT35tINeZO3l7C77SjqRJeAUoT0CvMRg==} + /@arr/every@1.0.1: resolution: {integrity: sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg==} engines: {node: '>=4'} @@ -6196,6 +6302,7 @@ packages: /@effect/schema@0.72.2(effect@3.7.2): resolution: {integrity: sha512-/x1BIA2pqcUidNrOMmwYe6Z58KtSgHSc5iJu7bNwIxi2LHMVuUao1BvpI5x6i7T/zkoi4dd1S6qasZzJIYDjdw==} + deprecated: this package has been merged into the main effect package peerDependencies: effect: ^3.7.2 dependencies: @@ -6205,12 +6312,12 @@ packages: /@effect/schema@0.75.5(effect@3.17.1): resolution: {integrity: sha512-TQInulTVCuF+9EIbJpyLP6dvxbQJMphrnRqgexm/Ze39rSjfhJuufF7XvU3SxTgg3HnL7B/kpORTJbHhlE6thw==} + deprecated: this package has been merged into the main effect package peerDependencies: effect: ^3.9.2 dependencies: effect: 3.17.1 - fast-check: 3.22.0 - dev: false + fast-check: 3.23.2 /@electric-sql/client@0.4.0: resolution: {integrity: sha512-YVYSqHitqVIDC1RBTfmHMfAfqDNAKMK9/AFVTDFQQxN3Q85dIQS49zThAuJVecYiuYRJvTiqf40c4n39jZSNrQ==} @@ -9003,10 +9110,6 @@ packages: '@jridgewell/trace-mapping': 0.3.25 dev: true - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: false - /@jridgewell/sourcemap-codec@1.5.0: resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} @@ -16958,7 +17061,7 @@ packages: - encoding dev: false - /@remix-run/dev@2.1.0(@remix-run/serve@2.1.0)(@types/node@20.14.14)(ts-node@10.9.1)(typescript@5.5.4): + /@remix-run/dev@2.1.0(@remix-run/serve@2.1.0)(@types/node@20.14.14)(ts-node@10.9.1)(typescript@5.8.3): resolution: {integrity: sha512-Hn5lw46F+a48dp5uHKe68ckaHgdStW4+PmLod+LMFEqrMbkF0j4XD1ousebxlv989o0Uy/OLgfRMgMy4cBOvHg==} engines: {node: '>=18.0.0'} hasBin: true @@ -16980,8 +17083,8 @@ packages: '@babel/traverse': 7.22.17 '@mdx-js/mdx': 2.3.0 '@npmcli/package-json': 4.0.1 - '@remix-run/serve': 2.1.0(typescript@5.5.4) - '@remix-run/server-runtime': 2.1.0(typescript@5.5.4) + '@remix-run/serve': 2.1.0(typescript@5.8.3) + '@remix-run/server-runtime': 2.1.0(typescript@5.8.3) '@types/mdx': 2.0.5 '@vanilla-extract/integration': 6.2.1(@types/node@20.14.14) arg: 5.0.2 @@ -17019,7 +17122,7 @@ packages: semver: 7.6.3 tar-fs: 2.1.3 tsconfig-paths: 4.2.0 - typescript: 5.5.4 + typescript: 5.8.3 ws: 7.5.9 transitivePeerDependencies: - '@types/node' @@ -17037,7 +17140,7 @@ packages: - utf-8-validate dev: true - /@remix-run/eslint-config@2.1.0(eslint@8.31.0)(react@18.2.0)(typescript@5.5.4): + /@remix-run/eslint-config@2.1.0(eslint@8.31.0)(react@18.2.0)(typescript@5.8.3): resolution: {integrity: sha512-yfeUnHpUG+XveujMi6QODKMGhs5CvKWCKzASU397BPXiPWbMv6r2acfODSWK64ZdBMu9hcLbOb42GBFydVQeHA==} engines: {node: '>=18.0.0'} peerDependencies: @@ -17052,28 +17155,28 @@ packages: '@babel/eslint-parser': 7.21.8(@babel/core@7.22.17)(eslint@8.31.0) '@babel/preset-react': 7.18.6(@babel/core@7.22.17) '@rushstack/eslint-patch': 1.2.0 - '@typescript-eslint/eslint-plugin': 5.59.6(@typescript-eslint/parser@5.59.6)(eslint@8.31.0)(typescript@5.5.4) - '@typescript-eslint/parser': 5.59.6(eslint@8.31.0)(typescript@5.5.4) + '@typescript-eslint/eslint-plugin': 5.59.6(@typescript-eslint/parser@5.59.6)(eslint@8.31.0)(typescript@5.8.3) + '@typescript-eslint/parser': 5.59.6(eslint@8.31.0)(typescript@5.8.3) eslint: 8.31.0 eslint-import-resolver-node: 0.3.7 eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.29.1)(eslint@8.31.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-typescript@3.5.5)(eslint@8.31.0) - eslint-plugin-jest: 26.9.0(@typescript-eslint/eslint-plugin@5.59.6)(eslint@8.31.0)(typescript@5.5.4) + eslint-plugin-jest: 26.9.0(@typescript-eslint/eslint-plugin@5.59.6)(eslint@8.31.0)(typescript@5.8.3) eslint-plugin-jest-dom: 4.0.3(eslint@8.31.0) eslint-plugin-jsx-a11y: 6.7.1(eslint@8.31.0) eslint-plugin-node: 11.1.0(eslint@8.31.0) eslint-plugin-react: 7.32.2(eslint@8.31.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.31.0) - eslint-plugin-testing-library: 5.11.0(eslint@8.31.0)(typescript@5.5.4) + eslint-plugin-testing-library: 5.11.0(eslint@8.31.0)(typescript@5.8.3) react: 18.2.0 - typescript: 5.5.4 + typescript: 5.8.3 transitivePeerDependencies: - eslint-import-resolver-webpack - jest - supports-color dev: true - /@remix-run/express@2.1.0(express@4.20.0)(typescript@5.5.4): + /@remix-run/express@2.1.0(express@4.20.0)(typescript@5.8.3): resolution: {integrity: sha512-R5myPowQx6LYWY3+EqP42q19MOCT3+ZGwb2f0UKNs9a34R8U3nFpGWL7saXryC+To+EasujEScc8rTQw5Pftog==} engines: {node: '>=18.0.0'} peerDependencies: @@ -17083,11 +17186,11 @@ packages: typescript: optional: true dependencies: - '@remix-run/node': 2.1.0(typescript@5.5.4) + '@remix-run/node': 2.1.0(typescript@5.8.3) express: 4.20.0 - typescript: 5.5.4 + typescript: 5.8.3 - /@remix-run/node@2.1.0(typescript@5.5.4): + /@remix-run/node@2.1.0(typescript@5.8.3): resolution: {integrity: sha512-TeSgjXnZUUlmw5FVpBVnXY7MLpracjdnwFNwoJE5NQkiUEFnGD/Yhvk4F2fOCkszqc2Z25KRclc5noweyiFu6Q==} engines: {node: '>=18.0.0'} peerDependencies: @@ -17096,7 +17199,7 @@ packages: typescript: optional: true dependencies: - '@remix-run/server-runtime': 2.1.0(typescript@5.5.4) + '@remix-run/server-runtime': 2.1.0(typescript@5.8.3) '@remix-run/web-fetch': 4.4.1 '@remix-run/web-file': 3.1.0 '@remix-run/web-stream': 1.1.0 @@ -17104,9 +17207,9 @@ packages: cookie-signature: 1.2.0 source-map-support: 0.5.21 stream-slice: 0.1.2 - typescript: 5.5.4 + typescript: 5.8.3 - /@remix-run/react@2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.5.4): + /@remix-run/react@2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.8.3): resolution: {integrity: sha512-DeYgfsvNxHqNn29sGA3XsZCciMKo2EFTQ9hHkuVPTsJXC4ipHr6Dja1j6UzZYPe/ZuKppiuTjueWCQlE2jOe1w==} engines: {node: '>=18.0.0'} peerDependencies: @@ -17118,11 +17221,11 @@ packages: optional: true dependencies: '@remix-run/router': 1.10.0 - '@remix-run/server-runtime': 2.1.0(typescript@5.5.4) + '@remix-run/server-runtime': 2.1.0(typescript@5.8.3) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) react-router-dom: 6.17.0(react-dom@18.2.0)(react@18.2.0) - typescript: 5.5.4 + typescript: 5.8.3 /@remix-run/router@1.10.0: resolution: {integrity: sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw==} @@ -17133,13 +17236,13 @@ packages: engines: {node: '>=14.0.0'} dev: false - /@remix-run/serve@2.1.0(typescript@5.5.4): + /@remix-run/serve@2.1.0(typescript@5.8.3): resolution: {integrity: sha512-XHI+vPYz217qrg1QcV38TTPlEBTzMJzAt0SImPutyF0S2IBrZGZIFMEsspI0i0wNvdcdQz1IqmSx+mTghzW8eQ==} engines: {node: '>=18.0.0'} hasBin: true dependencies: - '@remix-run/express': 2.1.0(express@4.20.0)(typescript@5.5.4) - '@remix-run/node': 2.1.0(typescript@5.5.4) + '@remix-run/express': 2.1.0(express@4.20.0)(typescript@5.8.3) + '@remix-run/node': 2.1.0(typescript@5.8.3) chokidar: 3.6.0 compression: 1.7.4 express: 4.20.0 @@ -17150,7 +17253,7 @@ packages: - supports-color - typescript - /@remix-run/server-runtime@2.1.0(typescript@5.5.4): + /@remix-run/server-runtime@2.1.0(typescript@5.8.3): resolution: {integrity: sha512-Uz69yF4Gu6F3VYQub3JgDo9godN8eDMeZclkadBTAWN7bYLonu0ChR/GlFxS35OLeF7BDgudxOSZob0nE1WHNg==} engines: {node: '>=18.0.0'} peerDependencies: @@ -17165,9 +17268,9 @@ packages: cookie: 0.4.2 set-cookie-parser: 2.6.0 source-map: 0.7.4 - typescript: 5.5.4 + typescript: 5.8.3 - /@remix-run/testing@2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.5.4): + /@remix-run/testing@2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.8.3): resolution: {integrity: sha512-eLPx4Bmjt243kyRpQTong1eFo6nkvSfCr65bb5PfoF172DKnsSSCYWAmBmB72VwtAPESHxBm3g6AUbhwphkU6A==} engines: {node: '>=18.0.0'} peerDependencies: @@ -17177,12 +17280,12 @@ packages: typescript: optional: true dependencies: - '@remix-run/node': 2.1.0(typescript@5.5.4) - '@remix-run/react': 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.5.4) + '@remix-run/node': 2.1.0(typescript@5.8.3) + '@remix-run/react': 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.8.3) '@remix-run/router': 1.10.0 react: 18.2.0 react-router-dom: 6.17.0(react-dom@18.2.0)(react@18.2.0) - typescript: 5.5.4 + typescript: 5.8.3 transitivePeerDependencies: - react-dom dev: true @@ -17193,8 +17296,8 @@ packages: '@remix-run/react': ^1.15.0 || ^2.0.0 '@remix-run/server-runtime': ^1.15.0 || ^2.0.0 dependencies: - '@remix-run/react': 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.5.4) - '@remix-run/server-runtime': 2.1.0(typescript@5.5.4) + '@remix-run/react': 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.8.3) + '@remix-run/server-runtime': 2.1.0(typescript@5.8.3) dev: false /@remix-run/web-blob@3.1.0: @@ -17683,10 +17786,10 @@ packages: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.36.0 - '@remix-run/node': 2.1.0(typescript@5.5.4) - '@remix-run/react': 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.5.4) + '@remix-run/node': 2.1.0(typescript@5.8.3) + '@remix-run/react': 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.8.3) '@remix-run/router': 1.15.3 - '@remix-run/server-runtime': 2.1.0(typescript@5.5.4) + '@remix-run/server-runtime': 2.1.0(typescript@5.8.3) '@sentry/cli': 2.50.2 '@sentry/core': 9.40.0 '@sentry/node': 9.40.0 @@ -17717,6 +17820,9 @@ packages: resolution: {integrity: sha512-75232GRx3wp3P7NP+yc4nRK3XUAnaQShxTAzapgmQrgs0QvSq0/mOJGoZXRpH15cFCKyys+4laCPbBselqJ5Ag==} dev: false + /@sinclair/typebox@0.34.38: + resolution: {integrity: sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==} + /@sindresorhus/is@0.14.0: resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} engines: {node: '>=6'} @@ -18910,6 +19016,10 @@ packages: - supports-color dev: false + /@sodaru/yup-to-json-schema@2.0.1: + resolution: {integrity: sha512-lWb0Wiz8KZ9ip/dY1eUqt7fhTPmL24p6Hmv5Fd9pzlzAdw/YNcWZr+tiCT4oZ4Zyxzi9+1X4zv82o7jYvcFxYA==} + dev: false + /@splinetool/react-spline@2.2.6(@splinetool/runtime@1.9.98)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-y9L2VEbnC6FNZZu8XMmWM9YTTTWal6kJVfP05Amf0QqDNzCSumKsJxZyGUODvuCmiAvy0PfIfEsiVKnSxvhsDw==} peerDependencies: @@ -18937,7 +19047,6 @@ packages: /@standard-schema/spec@1.0.0: resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - dev: false /@stricli/auto-complete@1.2.0: resolution: {integrity: sha512-r9/msiloVmTF95mdhe04Uzqei1B0ZofhYRLeiPqpJ1W1RMCC8p9iW7kqBZEbALl2aRL5ZK9OEW3Q1cIejH7KEQ==} @@ -19209,7 +19318,7 @@ packages: defer-to-connect: 1.1.3 dev: true - /@t3-oss/env-core@0.10.1(typescript@5.5.4)(zod@3.23.8): + /@t3-oss/env-core@0.10.1(typescript@5.8.3)(zod@3.23.8): resolution: {integrity: sha512-GcKZiCfWks5CTxhezn9k5zWX3sMDIYf6Kaxy2Gx9YEQftFcz8hDRN56hcbylyAO3t4jQnQ5ifLawINsNgCDpOg==} peerDependencies: typescript: '>=5.0.0' @@ -19218,11 +19327,11 @@ packages: typescript: optional: true dependencies: - typescript: 5.5.4 + typescript: 5.8.3 zod: 3.23.8 dev: false - /@t3-oss/env-core@0.11.0(typescript@5.5.4)(zod@3.23.8): + /@t3-oss/env-core@0.11.0(typescript@5.8.3)(zod@3.23.8): resolution: {integrity: sha512-PSalC5bG0a7XbyoLydiQdAnx3gICX6IQNctvh+TyLrdFxsxgocdj9Ui7sd061UlBzi+z4aIGjnem1kZx9QtUgQ==} peerDependencies: typescript: '>=5.0.0' @@ -19231,11 +19340,11 @@ packages: typescript: optional: true dependencies: - typescript: 5.5.4 + typescript: 5.8.3 zod: 3.23.8 dev: false - /@t3-oss/env-nextjs@0.10.1(typescript@5.5.4)(zod@3.23.8): + /@t3-oss/env-nextjs@0.10.1(typescript@5.8.3)(zod@3.23.8): resolution: {integrity: sha512-iy2qqJLnFh1RjEWno2ZeyTu0ufomkXruUsOZludzDIroUabVvHsrSjtkHqwHp1/pgPUzN3yBRHMILW162X7x2Q==} peerDependencies: typescript: '>=5.0.0' @@ -19244,8 +19353,8 @@ packages: typescript: optional: true dependencies: - '@t3-oss/env-core': 0.10.1(typescript@5.5.4)(zod@3.23.8) - typescript: 5.5.4 + '@t3-oss/env-core': 0.10.1(typescript@5.8.3)(zod@3.23.8) + typescript: 5.8.3 zod: 3.23.8 dev: false @@ -19879,12 +19988,8 @@ packages: /@types/json-schema@7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} - /@types/json-schema@7.0.13: - resolution: {integrity: sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==} - /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - dev: true /@types/json5@0.0.29: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} @@ -20378,7 +20483,7 @@ packages: - '@types/json-schema' dev: false - /@typescript-eslint/eslint-plugin@5.59.6(@typescript-eslint/parser@5.59.6)(eslint@8.31.0)(typescript@5.5.4): + /@typescript-eslint/eslint-plugin@5.59.6(@typescript-eslint/parser@5.59.6)(eslint@8.31.0)(typescript@5.8.3): resolution: {integrity: sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -20390,23 +20495,23 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.5.1 - '@typescript-eslint/parser': 5.59.6(eslint@8.31.0)(typescript@5.5.4) + '@typescript-eslint/parser': 5.59.6(eslint@8.31.0)(typescript@5.8.3) '@typescript-eslint/scope-manager': 5.59.6 - '@typescript-eslint/type-utils': 5.59.6(eslint@8.31.0)(typescript@5.5.4) - '@typescript-eslint/utils': 5.59.6(eslint@8.31.0)(typescript@5.5.4) + '@typescript-eslint/type-utils': 5.59.6(eslint@8.31.0)(typescript@5.8.3) + '@typescript-eslint/utils': 5.59.6(eslint@8.31.0)(typescript@5.8.3) debug: 4.3.4 eslint: 8.31.0 grapheme-splitter: 1.0.4 ignore: 5.2.4 natural-compare-lite: 1.4.0 semver: 7.6.3 - tsutils: 3.21.0(typescript@5.5.4) - typescript: 5.5.4 + tsutils: 3.21.0(typescript@5.8.3) + typescript: 5.8.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@5.59.6(eslint@8.31.0)(typescript@5.5.4): + /@typescript-eslint/parser@5.59.6(eslint@8.31.0)(typescript@5.8.3): resolution: {integrity: sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -20418,10 +20523,10 @@ packages: dependencies: '@typescript-eslint/scope-manager': 5.59.6 '@typescript-eslint/types': 5.59.6 - '@typescript-eslint/typescript-estree': 5.59.6(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 5.59.6(typescript@5.8.3) debug: 4.4.0(supports-color@10.0.0) eslint: 8.31.0 - typescript: 5.5.4 + typescript: 5.8.3 transitivePeerDependencies: - supports-color dev: true @@ -20434,7 +20539,7 @@ packages: '@typescript-eslint/visitor-keys': 5.59.6 dev: true - /@typescript-eslint/type-utils@5.59.6(eslint@8.31.0)(typescript@5.5.4): + /@typescript-eslint/type-utils@5.59.6(eslint@8.31.0)(typescript@5.8.3): resolution: {integrity: sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -20444,12 +20549,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.59.6(typescript@5.5.4) - '@typescript-eslint/utils': 5.59.6(eslint@8.31.0)(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 5.59.6(typescript@5.8.3) + '@typescript-eslint/utils': 5.59.6(eslint@8.31.0)(typescript@5.8.3) debug: 4.4.0(supports-color@10.0.0) eslint: 8.31.0 - tsutils: 3.21.0(typescript@5.5.4) - typescript: 5.5.4 + tsutils: 3.21.0(typescript@5.8.3) + typescript: 5.8.3 transitivePeerDependencies: - supports-color dev: true @@ -20459,7 +20564,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree@5.59.6(typescript@5.5.4): + /@typescript-eslint/typescript-estree@5.59.6(typescript@5.8.3): resolution: {integrity: sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -20474,24 +20579,24 @@ packages: globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.3 - tsutils: 3.21.0(typescript@5.5.4) - typescript: 5.5.4 + tsutils: 3.21.0(typescript@5.8.3) + typescript: 5.8.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@5.59.6(eslint@8.31.0)(typescript@5.5.4): + /@typescript-eslint/utils@5.59.6(eslint@8.31.0)(typescript@5.8.3): resolution: {integrity: sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.31.0) - '@types/json-schema': 7.0.13 + '@types/json-schema': 7.0.15 '@types/semver': 7.5.1 '@typescript-eslint/scope-manager': 5.59.6 '@typescript-eslint/types': 5.59.6 - '@typescript-eslint/typescript-estree': 5.59.6(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 5.59.6(typescript@5.8.3) eslint: 8.31.0 eslint-scope: 5.1.1 semver: 7.6.3 @@ -20713,6 +20818,15 @@ packages: - supports-color dev: true + /@vitest/expect@2.1.9: + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.2.0 + tinyrainbow: 1.2.0 + dev: true + /@vitest/expect@3.1.4: resolution: {integrity: sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==} dependencies: @@ -20722,6 +20836,23 @@ packages: tinyrainbow: 2.0.0 dev: true + /@vitest/mocker@2.1.9(vite@5.2.7): + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.17 + vite: 5.2.7(@types/node@20.14.14) + dev: true + /@vitest/mocker@3.1.4(vite@5.2.7): resolution: {integrity: sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==} peerDependencies: @@ -20765,6 +20896,14 @@ packages: pathe: 2.0.3 dev: true + /@vitest/snapshot@2.1.9: + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.17 + pathe: 1.1.2 + dev: true + /@vitest/snapshot@3.1.4: resolution: {integrity: sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==} dependencies: @@ -20773,6 +20912,12 @@ packages: pathe: 2.0.3 dev: true + /@vitest/spy@2.1.9: + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + dependencies: + tinyspy: 3.0.2 + dev: true + /@vitest/spy@3.1.4: resolution: {integrity: sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==} dependencies: @@ -21736,6 +21881,12 @@ packages: '@ark/util': 0.18.0 dev: false + /arktype@2.1.20: + resolution: {integrity: sha512-IZCEEXaJ8g+Ijd59WtSYwtjnqXiwM8sWQ5EjGamcto7+HVN9eK0C4p0zDlCuAwWhpqr6fIBkxPuYDl4/Mcj/+Q==} + dependencies: + '@ark/schema': 0.46.0 + '@ark/util': 0.46.0 + /array-buffer-byte-length@1.0.1: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} engines: {node: '>= 0.4'} @@ -22784,7 +22935,7 @@ packages: /cjs-module-lexer@1.2.3: resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} - /class-variance-authority@0.5.2(typescript@5.5.4): + /class-variance-authority@0.5.2(typescript@5.8.3): resolution: {integrity: sha512-j7Qqw3NPbs4IpO80gvdACWmVvHiLLo5MECacUBLnJG17CrLpWaQ7/4OaWX6P0IO1j2nvZ7AuSfBS/ImtEUZJGA==} peerDependencies: typescript: '>= 4.5.5 < 6' @@ -22792,7 +22943,7 @@ packages: typescript: optional: true dependencies: - typescript: 5.5.4 + typescript: 5.8.3 dev: false /class-variance-authority@0.7.0: @@ -23246,7 +23397,23 @@ packages: typescript: 5.5.4 dev: false - /cosmiconfig@9.0.0(typescript@5.5.4): + /cosmiconfig@8.3.6(typescript@5.8.3): + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + typescript: 5.8.3 + dev: false + + /cosmiconfig@9.0.0(typescript@5.8.3): resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} peerDependencies: @@ -23259,7 +23426,7 @@ packages: import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 - typescript: 5.5.4 + typescript: 5.8.3 /cp-file@10.0.0: resolution: {integrity: sha512-vy2Vi1r2epK5WqxOLnskeKeZkdZvTKfFZQCplE3XWsP+SUJyd5XAUFC9lFgTjjXJF2GMne/UML14iEmkAaDfFg==} @@ -24174,7 +24341,6 @@ packages: dependencies: '@standard-schema/spec': 1.0.0 fast-check: 3.23.2 - dev: false /effect@3.7.2: resolution: {integrity: sha512-pV7l1+LSZFvVObj4zuy4nYiBaC7qZOfrKV6s/Ef4p3KueiQwZFgamazklwyZ+x7Nyj2etRDFvHE/xkThTfQD1w==} @@ -25068,7 +25234,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.59.6(eslint@8.31.0)(typescript@5.5.4) + '@typescript-eslint/parser': 5.59.6(eslint@8.31.0)(typescript@5.8.3) debug: 3.2.7 eslint: 8.31.0 eslint-import-resolver-node: 0.3.7 @@ -25098,7 +25264,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.59.6(eslint@8.31.0)(typescript@5.5.4) + '@typescript-eslint/parser': 5.59.6(eslint@8.31.0)(typescript@5.8.3) debug: 3.2.7 eslint: 8.31.0 eslint-import-resolver-node: 0.3.9 @@ -25128,7 +25294,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.59.6(eslint@8.31.0)(typescript@5.5.4) + '@typescript-eslint/parser': 5.59.6(eslint@8.31.0)(typescript@5.8.3) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 @@ -25165,7 +25331,7 @@ packages: requireindex: 1.2.0 dev: true - /eslint-plugin-jest@26.9.0(@typescript-eslint/eslint-plugin@5.59.6)(eslint@8.31.0)(typescript@5.5.4): + /eslint-plugin-jest@26.9.0(@typescript-eslint/eslint-plugin@5.59.6)(eslint@8.31.0)(typescript@5.8.3): resolution: {integrity: sha512-TWJxWGp1J628gxh2KhaH1H1paEdgE2J61BBF1I59c6xWeL5+D1BzMxGDN/nXAfX+aSkR5u80K+XhskK6Gwq9ng==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -25178,8 +25344,8 @@ packages: jest: optional: true dependencies: - '@typescript-eslint/eslint-plugin': 5.59.6(@typescript-eslint/parser@5.59.6)(eslint@8.31.0)(typescript@5.5.4) - '@typescript-eslint/utils': 5.59.6(eslint@8.31.0)(typescript@5.5.4) + '@typescript-eslint/eslint-plugin': 5.59.6(@typescript-eslint/parser@5.59.6)(eslint@8.31.0)(typescript@5.8.3) + '@typescript-eslint/utils': 5.59.6(eslint@8.31.0)(typescript@5.8.3) eslint: 8.31.0 transitivePeerDependencies: - supports-color @@ -25259,13 +25425,13 @@ packages: string.prototype.matchall: 4.0.8 dev: true - /eslint-plugin-testing-library@5.11.0(eslint@8.31.0)(typescript@5.5.4): + /eslint-plugin-testing-library@5.11.0(eslint@8.31.0)(typescript@5.8.3): resolution: {integrity: sha512-ELY7Gefo+61OfXKlQeXNIDVVLPcvKTeiQOoMZG9TeuWa7Ln4dUNRv8JdRWBQI9Mbb427XGlVB1aa1QPZxBJM8Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6'} peerDependencies: eslint: ^7.5.0 || ^8.0.0 dependencies: - '@typescript-eslint/utils': 5.59.6(eslint@8.31.0)(typescript@5.5.4) + '@typescript-eslint/utils': 5.59.6(eslint@8.31.0)(typescript@5.8.3) eslint: 8.31.0 transitivePeerDependencies: - supports-color @@ -25753,7 +25919,6 @@ packages: engines: {node: '>=8.0.0'} dependencies: pure-rand: 6.1.0 - dev: false /fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} @@ -26739,6 +26904,27 @@ packages: dev: false patched: true + /graphile-worker@0.16.6(patch_hash=hdpetta7btqcc7xb5wfkcnanoa)(typescript@5.8.3): + resolution: {integrity: sha512-e7gGYDmGqzju2l83MpzX8vNG/lOtVJiSzI3eZpAFubSxh/cxs7sRrRGBGjzBP1kNG0H+c95etPpNRNlH65PYhw==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + '@graphile/logger': 0.2.0 + '@types/debug': 4.1.12 + '@types/pg': 8.11.6 + cosmiconfig: 8.3.6(typescript@5.8.3) + graphile-config: 0.0.1-beta.8 + json5: 2.2.3 + pg: 8.11.5 + tslib: 2.6.2 + yargs: 17.7.2 + transitivePeerDependencies: + - pg-native + - supports-color + - typescript + dev: false + patched: true + /graphql@16.6.0: resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} @@ -28640,7 +28826,7 @@ packages: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} engines: {node: '>=12'} dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 dev: false /magicast@0.3.4: @@ -29631,7 +29817,7 @@ packages: workerd: 1.20240806.0 ws: 8.18.0(bufferutil@4.0.9) youch: 3.3.3 - zod: 3.23.8 + zod: 3.25.76 transitivePeerDependencies: - bufferutil - supports-color @@ -29929,7 +30115,7 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - /msw@2.3.5(typescript@5.5.4): + /msw@2.3.5(typescript@5.8.3): resolution: {integrity: sha512-+GUI4gX5YC5Bv33epBrD+BGdmDvBg2XGruiWnI3GbIbRmMMBeZ5gs3mJ51OWSGHgJKztZ8AtZeYMMNMVrje2/Q==} engines: {node: '>=18'} hasBin: true @@ -29956,7 +30142,7 @@ packages: path-to-regexp: 6.2.1 strict-event-emitter: 0.5.1 type-fest: 4.10.3 - typescript: 5.5.4 + typescript: 5.8.3 yargs: 17.7.2 dev: false @@ -31704,7 +31890,7 @@ packages: dependencies: lilconfig: 2.1.0 postcss: 8.4.29 - ts-node: 10.9.1(@swc/core@1.3.26)(@types/node@20.14.14)(typescript@5.5.4) + ts-node: 10.9.1(@swc/core@1.3.26)(@types/node@20.14.14)(typescript@5.8.3) yaml: 2.3.1 dev: true @@ -31722,7 +31908,7 @@ packages: dependencies: lilconfig: 3.1.3 postcss: 8.5.3 - ts-node: 10.9.1(@swc/core@1.3.26)(@types/node@20.14.14)(typescript@5.5.4) + ts-node: 10.9.1(@swc/core@1.3.26)(@types/node@20.14.14)(typescript@5.8.3) yaml: 2.7.1 /postcss-load-config@6.0.1(postcss@8.5.4)(tsx@4.17.0): @@ -31748,7 +31934,7 @@ packages: tsx: 4.17.0 dev: true - /postcss-loader@8.1.1(postcss@8.5.4)(typescript@5.5.4)(webpack@5.99.9): + /postcss-loader@8.1.1(postcss@8.5.4)(typescript@5.8.3)(webpack@5.99.9): resolution: {integrity: sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==} engines: {node: '>= 18.12.0'} peerDependencies: @@ -31761,7 +31947,7 @@ packages: webpack: optional: true dependencies: - cosmiconfig: 9.0.0(typescript@5.5.4) + cosmiconfig: 9.0.0(typescript@5.8.3) jiti: 1.21.0 postcss: 8.5.4 semver: 7.6.3 @@ -32192,8 +32378,8 @@ packages: '@mrleebo/prisma-ast': 0.7.0 '@prisma/generator-helper': 5.3.1 '@prisma/internals': 5.3.1 - typescript: 5.5.4 - zod: 3.23.8 + typescript: 5.8.3 + zod: 3.25.76 transitivePeerDependencies: - encoding - supports-color @@ -32313,7 +32499,6 @@ packages: /property-expr@2.0.6: resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} - dev: false /property-information@6.2.0: resolution: {integrity: sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==} @@ -32471,7 +32656,7 @@ packages: dependencies: '@puppeteer/browsers': 2.4.0 chromium-bidi: 0.6.5(devtools-protocol@0.0.1342118) - cosmiconfig: 9.0.0(typescript@5.5.4) + cosmiconfig: 9.0.0(typescript@5.8.3) devtools-protocol: 0.0.1342118 puppeteer-core: 23.4.0 typed-query-selector: 2.12.0 @@ -32485,7 +32670,6 @@ packages: /pure-rand@6.1.0: resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - dev: false /purgecss@2.3.0: resolution: {integrity: sha512-BE5CROfVGsx2XIhxGuZAT7rTH9lLeQx/6M0P7DTXQH4IUc3BBzs9JUzt4yzGf3JrH9enkeq6YJBe9CTtkm1WmQ==} @@ -33521,7 +33705,7 @@ packages: '@remix-run/server-runtime': ^1.1.1 remix-auth: ^3.2.1 dependencies: - '@remix-run/server-runtime': 2.1.0(typescript@5.5.4) + '@remix-run/server-runtime': 2.1.0(typescript@5.8.3) crypto-js: 4.1.1 remix-auth: 3.6.0(@remix-run/react@2.1.0)(@remix-run/server-runtime@2.1.0) dev: false @@ -33532,7 +33716,7 @@ packages: '@remix-run/server-runtime': ^1.0.0 remix-auth: ^3.4.0 dependencies: - '@remix-run/server-runtime': 2.1.0(typescript@5.5.4) + '@remix-run/server-runtime': 2.1.0(typescript@5.8.3) remix-auth: 3.6.0(@remix-run/react@2.1.0)(@remix-run/server-runtime@2.1.0) remix-auth-oauth2: 1.11.0(@remix-run/server-runtime@2.1.0)(remix-auth@3.6.0) transitivePeerDependencies: @@ -33545,7 +33729,7 @@ packages: '@remix-run/server-runtime': ^1.0.0 || ^2.0.0 remix-auth: ^3.6.0 dependencies: - '@remix-run/server-runtime': 2.1.0(typescript@5.5.4) + '@remix-run/server-runtime': 2.1.0(typescript@5.8.3) debug: 4.4.0(supports-color@10.0.0) remix-auth: 3.6.0(@remix-run/react@2.1.0)(@remix-run/server-runtime@2.1.0) transitivePeerDependencies: @@ -33558,8 +33742,8 @@ packages: '@remix-run/react': ^1.0.0 || ^2.0.0 '@remix-run/server-runtime': ^1.0.0 || ^2.0.0 dependencies: - '@remix-run/react': 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.5.4) - '@remix-run/server-runtime': 2.1.0(typescript@5.5.4) + '@remix-run/react': 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.8.3) + '@remix-run/server-runtime': 2.1.0(typescript@5.8.3) uuid: 8.3.2 dev: false @@ -33570,8 +33754,8 @@ packages: '@remix-run/server-runtime': ^1.16.0 || ^2.0 react: ^17.0.2 || ^18.0.0 dependencies: - '@remix-run/react': 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.5.4) - '@remix-run/server-runtime': 2.1.0(typescript@5.5.4) + '@remix-run/react': 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.8.3) + '@remix-run/server-runtime': 2.1.0(typescript@5.8.3) react: 18.2.0 dev: false @@ -33608,8 +33792,8 @@ packages: zod: optional: true dependencies: - '@remix-run/node': 2.1.0(typescript@5.5.4) - '@remix-run/react': 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.5.4) + '@remix-run/node': 2.1.0(typescript@5.8.3) + '@remix-run/react': 2.1.0(react-dom@18.2.0)(react@18.2.0)(typescript@5.8.3) '@remix-run/router': 1.15.3 intl-parse-accept-language: 1.0.0 react: 18.2.0 @@ -33926,7 +34110,6 @@ packages: /runtypes@6.7.0: resolution: {integrity: sha512-3TLdfFX8YHNFOhwHrSJza6uxVBmBrEjnNQlNXvXCdItS0Pdskfg5vVXUTWIN+Y23QR09jWpSl99UHkA83m4uWA==} - dev: false /rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} @@ -34013,7 +34196,7 @@ packages: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/json-schema': 7.0.13 + '@types/json-schema': 7.0.15 ajv: 6.12.6 ajv-keywords: 3.5.2(ajv@6.12.6) dev: false @@ -35219,7 +35402,6 @@ packages: /superstruct@2.0.2: resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} engines: {node: '>=14.0.0'} - dev: false /supertest@7.0.0: resolution: {integrity: sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==} @@ -35810,7 +35992,6 @@ packages: /tiny-case@1.0.3: resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} - dev: false /tiny-glob@0.2.9: resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} @@ -35977,7 +36158,6 @@ packages: /toposort@2.0.2: resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} - dev: false /totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} @@ -36056,7 +36236,7 @@ packages: /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - /ts-node@10.9.1(@swc/core@1.3.26)(@types/node@20.14.14)(typescript@5.5.4): + /ts-node@10.9.1(@swc/core@1.3.26)(@types/node@20.14.14)(typescript@5.8.3): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -36083,11 +36263,11 @@ packages: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.5.4 + typescript: 5.8.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - /ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4): + /ts-node@10.9.2(@types/node@20.14.14)(typescript@5.8.3): resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true peerDependencies: @@ -36113,7 +36293,7 @@ packages: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.5.4 + typescript: 5.8.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -36161,6 +36341,19 @@ packages: typescript: 5.5.4 dev: true + /tsconfck@2.1.2(typescript@5.8.3): + resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==} + engines: {node: ^14.13.1 || ^16 || >=18} + hasBin: true + peerDependencies: + typescript: ^4.3.5 || ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + typescript: 5.8.3 + dev: true + /tsconfck@3.1.3(typescript@5.5.4): resolution: {integrity: sha512-ulNZP1SVpRDesxeMLON/LtWM8HIgAJEIVpVVhBM6gsmvQ8+Rh+ZG7FWGvHh7Ah3pRABwVJWklWCr/BTZSv0xnQ==} engines: {node: ^18 || >=20} @@ -36280,14 +36473,14 @@ packages: - yaml dev: true - /tsutils@3.21.0(typescript@5.5.4): + /tsutils@3.21.0(typescript@5.8.3): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.5.4 + typescript: 5.8.3 dev: true /tsx@3.12.2: @@ -36461,7 +36654,6 @@ packages: /type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - dev: false /type-fest@4.10.3: resolution: {integrity: sha512-JLXyjizi072smKGGcZiAJDCNweT8J+AuRxmPZ1aG7TERg4ijx9REl8CNhbr36RV4qXqL1gO1FF9HL8OkVmmrsA==} @@ -36622,7 +36814,7 @@ packages: reflect-metadata: 0.2.2 sha.js: 2.4.11 sqlite3: 5.1.7 - ts-node: 10.9.2(@types/node@20.14.14)(typescript@5.5.4) + ts-node: 10.9.2(@types/node@20.14.14)(typescript@5.8.3) tslib: 2.6.2 uuid: 9.0.1 yargs: 17.7.2 @@ -36647,6 +36839,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + /typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + /ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} @@ -37175,8 +37372,19 @@ packages: engines: {node: '>=0.10.0'} dev: false - /valibot@0.42.1(typescript@5.5.4): + /valibot@0.42.1(typescript@5.8.3): resolution: {integrity: sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + typescript: 5.8.3 + dev: false + + /valibot@1.1.0(typescript@5.5.4): + resolution: {integrity: sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==} peerDependencies: typescript: '>=5' peerDependenciesMeta: @@ -37184,6 +37392,17 @@ packages: optional: true dependencies: typescript: 5.5.4 + dev: true + + /valibot@1.1.0(typescript@5.8.3): + resolution: {integrity: sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + typescript: 5.8.3 dev: false /validate-npm-package-license@3.0.4: @@ -37306,6 +37525,27 @@ packages: - terser dev: true + /vite-node@2.1.9(@types/node@20.14.14): + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.4.0(supports-color@10.0.0) + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.2.7(@types/node@20.14.14) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vite-node@3.1.4(@types/node@20.14.14): resolution: {integrity: sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -37338,6 +37578,17 @@ packages: - typescript dev: true + /vite-tsconfig-paths@4.0.5(typescript@5.8.3): + resolution: {integrity: sha512-/L/eHwySFYjwxoYt1WRJniuK/jPv+WGwgRGBYx3leciR5wBeqntQpUE6Js6+TJemChc+ter7fDBKieyEWDx4yQ==} + dependencies: + debug: 4.3.7(supports-color@10.0.0) + globrex: 0.1.2 + tsconfck: 2.1.2(typescript@5.8.3) + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /vite@4.1.4(@types/node@20.14.14): resolution: {integrity: sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -37438,12 +37689,69 @@ packages: dependencies: '@types/node': 20.14.14 esbuild: 0.20.2 - postcss: 8.5.3 + postcss: 8.5.4 rollup: 4.36.0 optionalDependencies: fsevents: 2.3.3 dev: true + /vitest@2.1.9(@types/node@20.14.14): + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/node': 20.14.14 + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.2.7) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.2.0 + debug: 4.4.0(supports-color@10.0.0) + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 1.1.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 1.2.0 + vite: 5.2.7(@types/node@20.14.14) + vite-node: 2.1.9(@types/node@20.14.14) + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vitest@3.1.4(@types/node@20.14.14): resolution: {integrity: sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -37520,6 +37828,22 @@ packages: '@vue/shared': 3.5.16 typescript: 5.5.4 + /vue@3.5.16(typescript@5.8.3): + resolution: {integrity: sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@vue/compiler-dom': 3.5.16 + '@vue/compiler-sfc': 3.5.16 + '@vue/runtime-dom': 3.5.16 + '@vue/server-renderer': 3.5.16(vue@3.5.16) + '@vue/shared': 3.5.16 + typescript: 5.8.3 + dev: false + /w3c-keyname@2.2.6: resolution: {integrity: sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==} dev: false @@ -38124,6 +38448,14 @@ packages: type-fest: 2.19.0 dev: false + /yup@1.6.1: + resolution: {integrity: sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==} + dependencies: + property-expr: 2.0.6 + tiny-case: 1.0.3 + toposort: 2.0.2 + type-fest: 2.19.0 + /zimmerframe@1.1.2: resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} @@ -38182,6 +38514,14 @@ packages: dependencies: zod: 3.23.8 + /zod-to-json-schema@3.24.5(zod@4.0.10): + resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} + peerDependencies: + zod: ^3.24.1 + dependencies: + zod: 4.0.10 + dev: false + /zod-validation-error@1.5.0(zod@3.23.8): resolution: {integrity: sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw==} engines: {node: '>=16.0.0'} @@ -38191,6 +38531,10 @@ packages: zod: 3.23.8 dev: false + /zod@3.22.3: + resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} + dev: false + /zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} diff --git a/references/json-schema-test/README.md b/references/json-schema-test/README.md new file mode 100644 index 0000000000..bbde286f24 --- /dev/null +++ b/references/json-schema-test/README.md @@ -0,0 +1,118 @@ +# JSON Schema Test Reference Project + +This project demonstrates and tests the JSON schema functionality in Trigger.dev v3. + +## Features Implemented + +### 1. JSONSchema Type Export +- ✅ Proper `JSONSchema` type based on JSON Schema Draft 7 +- ✅ Exported from `@trigger.dev/sdk/v3` +- ✅ Can be used with TypeScript's `satisfies` operator + +### 2. Plain Task with payloadSchema +- ✅ Tasks accept a `payloadSchema` property +- ✅ Schema is stored and will be synced during indexing +- ✅ Type-safe schema definition + +### 3. Schema Task with Automatic Conversion +- ✅ `schemaTask` automatically converts Zod schemas to JSON Schema +- ✅ Full TypeScript type inference from schema +- ✅ Runtime validation built-in + +### 4. Type Safety +- ✅ `trigger()` and `triggerAndWait()` have proper type inference +- ✅ Batch operations maintain type safety +- ✅ Output types are properly inferred + +### 5. Schema Conversion Package +- ✅ `@trigger.dev/schema-to-json` package created +- ✅ Supports multiple schema libraries (Zod, Yup, ArkType, etc.) +- ✅ Bundle-safe with dynamic imports +- ✅ Auto-initialized by SDK (no user configuration needed) + +## Example Usage + +```typescript +import { schemaTask, task, type JSONSchema } from "@trigger.dev/sdk/v3"; +import { z } from "zod"; + +// Option 1: Using schemaTask with Zod (recommended) +const userSchema = z.object({ + id: z.string(), + name: z.string(), + email: z.string().email(), +}); + +export const mySchemaTask = schemaTask({ + id: "my-schema-task", + schema: userSchema, + run: async (payload, { ctx }) => { + // payload is fully typed! + console.log(payload.id, payload.name, payload.email); + return { processed: true }; + }, +}); + +// Option 2: Using plain task with manual JSON schema +const jsonSchema: JSONSchema = { + type: "object", + properties: { + message: { type: "string" }, + }, + required: ["message"], +}; + +export const myPlainTask = task({ + id: "my-plain-task", + payloadSchema: jsonSchema, + run: async (payload, { ctx }) => { + // payload is untyped, but schema is stored + return { received: payload.message }; + }, +}); +``` + +## Architecture + +1. **Core Package** (`@trigger.dev/core`): + - Defines `JSONSchema` type + - Includes `payloadSchema` in task metadata + +2. **SDK Package** (`@trigger.dev/sdk`): + - Re-exports `JSONSchema` type + - Auto-initializes schema converters + - Registers `payloadSchema` during task creation + +3. **Schema Conversion Package** (`@trigger.dev/schema-to-json`): + - Converts various schema libraries to JSON Schema + - Uses dynamic imports for bundle safety + - Encapsulated as implementation detail + +4. **Webapp**: + - Saves `payloadSchema` to `BackgroundWorkerTask` model + - Schema available for API documentation, validation, etc. + +## Testing + +Run the integration test to verify all functionality: + +```bash +npm run dev +# Then trigger the integration test task +``` + +The integration test covers: +- Plain task with JSON schema +- Zod schema conversion +- Complex nested schemas +- Trigger type safety +- Batch operations +- Error handling + +## Benefits + +1. **Documentation**: Schemas visible in Trigger.dev dashboard +2. **Validation**: Invalid payloads rejected before execution +3. **Type Safety**: Full TypeScript support with schemaTask +4. **API Generation**: Can generate OpenAPI specs +5. **Client SDKs**: Can generate typed clients for other languages \ No newline at end of file diff --git a/references/json-schema-test/package.json b/references/json-schema-test/package.json index 577cac465f..ccb807988d 100644 --- a/references/json-schema-test/package.json +++ b/references/json-schema-test/package.json @@ -10,7 +10,15 @@ }, "dependencies": { "@trigger.dev/sdk": "workspace:*", - "zod": "3.22.3" + "zod": "3.22.3", + "@sinclair/typebox": "^0.34.3", + "superstruct": "^2.0.2", + "@effect/schema": "^0.75.5", + "effect": "^3.11.11", + "arktype": "^2.0.0", + "valibot": "^1.0.0-beta.8", + "runtypes": "^6.7.0", + "yup": "^1.6.1" }, "devDependencies": { "@types/node": "^20.14.8", diff --git a/references/json-schema-test/src/trigger/core-functionality-test.ts b/references/json-schema-test/src/trigger/core-functionality-test.ts index e18187664e..f1136a42c2 100644 --- a/references/json-schema-test/src/trigger/core-functionality-test.ts +++ b/references/json-schema-test/src/trigger/core-functionality-test.ts @@ -31,7 +31,7 @@ export const plainJsonSchemaTask = task({ run: async (payload, { ctx }) => { // payload is any, but schema is properly stored console.log("Received payload:", payload); - + return { taskId: ctx.task.id, runId: ctx.run.id, @@ -64,7 +64,7 @@ export const zodSchemaTask = schemaTask({ run: async (payload, { ctx }) => { // Full type inference from Zod schema console.log("Processing user:", payload.userName); - + // All these are properly typed const id: string = payload.userId; const name: string = payload.userName; @@ -73,7 +73,7 @@ export const zodSchemaTask = schemaTask({ const theme: "light" | "dark" | "auto" = payload.preferences.theme; const notifications: boolean = payload.preferences.notifications; const tagCount: number = payload.tags.length; - + return { processedUserId: id, processedUserName: name, @@ -90,13 +90,17 @@ export const zodSchemaTask = schemaTask({ const orderSchema = z.object({ orderId: z.string(), customerId: z.string(), - items: z.array(z.object({ - productId: z.string(), - productName: z.string(), - quantity: z.number().positive(), - unitPrice: z.number().positive(), - discount: z.number().min(0).max(100).default(0), - })).min(1), + items: z + .array( + z.object({ + productId: z.string(), + productName: z.string(), + quantity: z.number().positive(), + unitPrice: z.number().positive(), + discount: z.number().min(0).max(100).default(0), + }) + ) + .min(1), shippingAddress: z.object({ street: z.string(), city: z.string(), @@ -104,13 +108,15 @@ const orderSchema = z.object({ zipCode: z.string(), country: z.string().default("US"), }), - billingAddress: z.object({ - street: z.string(), - city: z.string(), - state: z.string(), - zipCode: z.string(), - country: z.string(), - }).optional(), + billingAddress: z + .object({ + street: z.string(), + city: z.string(), + state: z.string(), + zipCode: z.string(), + country: z.string(), + }) + .optional(), paymentMethod: z.discriminatedUnion("type", [ z.object({ type: z.literal("credit_card"), @@ -127,7 +133,9 @@ const orderSchema = z.object({ routingNumber: z.string(), }), ]), - orderStatus: z.enum(["pending", "processing", "shipped", "delivered", "cancelled"]).default("pending"), + orderStatus: z + .enum(["pending", "processing", "shipped", "delivered", "cancelled"]) + .default("pending"), createdAt: z.string().datetime(), notes: z.string().optional(), }); @@ -141,14 +149,14 @@ export const complexOrderTask = schemaTask({ const firstItem = payload.items[0]; const productName = firstItem.productName; const quantity = firstItem.quantity; - + // Calculate totals with full type safety const subtotal = payload.items.reduce((sum, item) => { const itemTotal = item.quantity * item.unitPrice; const discount = itemTotal * (item.discount / 100); return sum + (itemTotal - discount); }, 0); - + // Discriminated union handling let paymentSummary: string; switch (payload.paymentMethod.type) { @@ -162,11 +170,11 @@ export const complexOrderTask = schemaTask({ paymentSummary = `Bank transfer ending in ${payload.paymentMethod.accountNumber.slice(-4)}`; break; } - + // Optional field handling const hasBillingAddress = !!payload.billingAddress; const billingCity = payload.billingAddress?.city ?? payload.shippingAddress.city; - + return { orderId, customerId: payload.customerId, @@ -187,7 +195,7 @@ export const testTriggerTypeSafety = task({ id: "test-trigger-type-safety", run: async (_, { ctx }) => { console.log("Testing trigger type safety..."); - + // Valid trigger - should compile const handle1 = await zodSchemaTask.trigger({ userId: "550e8400-e29b-41d4-a716-446655440000", @@ -202,7 +210,7 @@ export const testTriggerTypeSafety = task({ tags: ["customer", "premium"], createdAt: new Date().toISOString(), }); - + // Using defaults - should also compile const handle2 = await zodSchemaTask.trigger({ userId: "550e8400-e29b-41d4-a716-446655440001", @@ -212,7 +220,7 @@ export const testTriggerTypeSafety = task({ preferences: {}, // Will use defaults // tags will default to [] }); - + // Test triggerAndWait with result handling const result = await zodSchemaTask.triggerAndWait({ userId: "550e8400-e29b-41d4-a716-446655440002", @@ -223,13 +231,13 @@ export const testTriggerTypeSafety = task({ theme: "light", }, }); - + if (result.ok) { // Type-safe access to output console.log("Processed user:", result.output.processedUserName); console.log("User email:", result.output.processedUserEmail); console.log("Theme:", result.output.theme); - + return { success: true, processedUserId: result.output.processedUserId, @@ -249,7 +257,7 @@ export const testBatchOperations = task({ id: "test-batch-operations", run: async (_, { ctx }) => { console.log("Testing batch operations..."); - + // Batch trigger const batchHandle = await zodSchemaTask.batchTrigger([ { @@ -277,9 +285,9 @@ export const testBatchOperations = task({ }, }, ]); - + console.log(`Triggered batch ${batchHandle.batchId} with ${batchHandle.runCount} runs`); - + // Batch trigger and wait const batchResult = await zodSchemaTask.batchTriggerAndWait([ { @@ -304,9 +312,9 @@ export const testBatchOperations = task({ }, }, ]); - + // Process results with type safety - const processed = batchResult.runs.map(run => { + const processed = batchResult.runs.map((run) => { if (run.ok) { return { success: true, @@ -322,11 +330,11 @@ export const testBatchOperations = task({ }; } }); - + return { batchId: batchResult.id, totalRuns: batchResult.runs.length, - successfulRuns: processed.filter(p => p.success).length, + successfulRuns: processed.filter((p) => p.success).length, processed, }; }, @@ -337,7 +345,7 @@ export const integrationTest = task({ id: "json-schema-integration-test", run: async (_, { ctx }) => { console.log("Running integration test..."); - + const results = { plainTask: false, zodTask: false, @@ -345,7 +353,7 @@ export const integrationTest = task({ triggerTypes: false, batchOps: false, }; - + try { // Test plain JSON schema task const plainResult = await plainJsonSchemaTask.trigger({ @@ -358,19 +366,21 @@ export const integrationTest = task({ metadata: { source: "integration-test" }, }); results.plainTask = !!plainResult.id; - + // Test Zod schema task - const zodResult = await zodSchemaTask.triggerAndWait({ - userId: "int-test-001", - userName: "Integration Test User", - userEmail: "integration@example.com", - age: 35, - preferences: { - theme: "auto", - }, - }).unwrap(); + const zodResult = await zodSchemaTask + .triggerAndWait({ + userId: "int-test-001", + userName: "Integration Test User", + userEmail: "integration@example.com", + age: 35, + preferences: { + theme: "auto", + }, + }) + .unwrap(); results.zodTask = zodResult.processedUserId === "int-test-001"; - + // Test complex schema const complexResult = await complexOrderTask.trigger({ orderId: "order-int-001", @@ -398,27 +408,24 @@ export const integrationTest = task({ createdAt: new Date().toISOString(), }); results.complexTask = !!complexResult.id; - + // Test trigger type safety - const triggerResult = await testTriggerTypeSafety.triggerAndWait({}); + const triggerResult = await testTriggerTypeSafety.triggerAndWait(undefined); results.triggerTypes = triggerResult.ok && triggerResult.output.success; - + // Test batch operations - const batchResult = await testBatchOperations.triggerAndWait({}); + const batchResult = await testBatchOperations.triggerAndWait(undefined); results.batchOps = batchResult.ok && batchResult.output.successfulRuns > 0; - } catch (error) { console.error("Integration test error:", error); } - - const allPassed = Object.values(results).every(r => r); - + + const allPassed = Object.values(results).every((r) => r); + return { success: allPassed, results, - message: allPassed - ? "All JSON schema tests passed!" - : "Some tests failed - check results", + message: allPassed ? "All JSON schema tests passed!" : "Some tests failed - check results", }; }, -}); \ No newline at end of file +}); diff --git a/references/json-schema-test/src/trigger/simple-type-test.ts b/references/json-schema-test/src/trigger/simple-type-test.ts index d49635f376..458942be51 100644 --- a/references/json-schema-test/src/trigger/simple-type-test.ts +++ b/references/json-schema-test/src/trigger/simple-type-test.ts @@ -240,4 +240,4 @@ export const verifySchemaRegistration = task({ // reads the task metadata and sees the payloadSchema field }; }, -}); \ No newline at end of file +}); diff --git a/references/json-schema-test/src/trigger/test-other-schemas.ts b/references/json-schema-test/src/trigger/test-other-schemas.ts index fda49154db..d82d1483d6 100644 --- a/references/json-schema-test/src/trigger/test-other-schemas.ts +++ b/references/json-schema-test/src/trigger/test-other-schemas.ts @@ -1,6 +1,17 @@ import { schemaTask } from "@trigger.dev/sdk/v3"; import { Type } from "@sinclair/typebox"; -import { array, object, string, number, boolean, optional, union, literal, record, Infer } from "superstruct"; +import { + array, + object, + string, + number, + boolean, + optional, + union, + literal, + record, + Infer, +} from "superstruct"; import * as S from "@effect/schema/Schema"; import { type } from "arktype"; import * as v from "valibot"; @@ -14,11 +25,7 @@ const typeBoxSchema = Type.Object({ author: Type.Object({ name: Type.String(), email: Type.String({ format: "email" }), - role: Type.Union([ - Type.Literal("admin"), - Type.Literal("editor"), - Type.Literal("viewer"), - ]), + role: Type.Union([Type.Literal("admin"), Type.Literal("editor"), Type.Literal("viewer")]), }), tags: Type.Array(Type.String(), { minItems: 1, maxItems: 5 }), published: Type.Boolean(), @@ -28,7 +35,7 @@ const typeBoxSchema = Type.Object({ export const typeBoxTask = schemaTask({ id: "typebox-schema-task", - schema: typeBoxSchema, + schema: typeBoxSchema as any, run: async (payload, { ctx }) => { // TypeBox provides static type inference const id: string = payload.id; @@ -82,9 +89,8 @@ export const superstructTask = schemaTask({ const accountBalance = payload.account.balance; const isApproved = payload.approved; - const newBalance = payload.transaction.type === "credit" - ? accountBalance + amount - : accountBalance - amount; + const newBalance = + payload.transaction.type === "credit" ? accountBalance + amount : accountBalance - amount; return { transactionId, @@ -110,25 +116,29 @@ const effectSchema = S.Struct({ email: S.String, attributes: S.optional(S.Record(S.String, S.Unknown)), }), - product: S.optional(S.Struct({ - productId: S.String, - name: S.String, - price: S.Number, - category: S.String, - })), - location: S.optional(S.Struct({ - country: S.String, - city: S.optional(S.String), - region: S.optional(S.String), - })), + product: S.optional( + S.Struct({ + productId: S.String, + name: S.String, + price: S.Number, + category: S.String, + }) + ), + location: S.optional( + S.Struct({ + country: S.String, + city: S.optional(S.String), + region: S.optional(S.String), + }) + ), }); type EffectEvent = S.Schema.Type; export const effectSchemaTask = schemaTask({ id: "effect-schema-task", - schema: effectSchema, - run: async (payload: EffectEvent, { ctx }) => { + schema: effectSchema as any, + run: async (payload: any, { ctx }) => { // Effect Schema provides type safety const eventId = payload.event.eventId; const eventType = payload.event.eventType; @@ -262,13 +272,13 @@ const runtypesSchema = rt.Record({ type: rt.Literal("bank"), accountNumber: rt.String, routingNumber: rt.String, - }), + }) ), status: rt.Union( rt.Literal("pending"), rt.Literal("processing"), rt.Literal("completed"), - rt.Literal("failed"), + rt.Literal("failed") ), }), customer: rt.Record({ @@ -291,11 +301,12 @@ export const runtypesTask = schemaTask({ const currency = payload.payment.currency; const status = payload.payment.status; const customerEmail = payload.customer.email; - + // Discriminated union handling - const paymentDetails = payload.payment.method.type === "card" - ? `Card ending in ${payload.payment.method.last4}` - : `Bank account ${payload.payment.method.accountNumber}`; + const paymentDetails = + payload.payment.method.type === "card" + ? `Card ending in ${payload.payment.method.last4}` + : `Bank account ${payload.payment.method.accountNumber}`; return { paymentId, @@ -336,7 +347,7 @@ export const testAllSchemas = schemaTask({ const superstructResult = await superstructTask.trigger({ transaction: { id: "txn123", - amount: 100.50, + amount: 100.5, currency: "USD", type: "credit", description: "Test transaction", From def3ee0f8189d87cf3014d3877186d87f925af4a Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Wed, 30 Jul 2025 10:27:24 +0100 Subject: [PATCH 12/19] WIP --- .../migration.sql | 2 + packages/core/package.json | 2 +- packages/core/src/v3/types/tasks.ts | 7 +- packages/schema-to-json/examples/usage.ts | 81 ---- packages/schema-to-json/package.json | 16 +- .../schema-to-json/scripts/updateVersion.js | 24 -- .../src/__tests__/index.test.ts | 350 ----------------- .../schema-to-json/src/tests/index.test.ts | 351 ++++++++++++++++++ packages/schema-to-json/tsconfig.src.json | 16 +- packages/schema-to-json/tsconfig.test.json | 15 +- packages/trigger-sdk/package.json | 2 +- packages/trigger-sdk/src/v3/shared.ts | 2 +- pnpm-lock.yaml | 96 ++--- .../hello-world/src/trigger/jsonSchema.ts | 102 ++--- .../src/trigger/jsonSchemaSimple.ts | 50 ++- .../src/trigger/core-functionality-test.ts | 6 +- .../src/trigger/simple-type-test.ts | 4 +- .../src/trigger/test-other-schemas.ts | 9 +- .../json-schema-test/src/trigger/test-zod.ts | 42 ++- 19 files changed, 554 insertions(+), 623 deletions(-) create mode 100644 internal-packages/database/prisma/migrations/20250730084611_add_payload_schema_to_background_worker_task/migration.sql delete mode 100644 packages/schema-to-json/examples/usage.ts delete mode 100644 packages/schema-to-json/scripts/updateVersion.js delete mode 100644 packages/schema-to-json/src/__tests__/index.test.ts create mode 100644 packages/schema-to-json/src/tests/index.test.ts diff --git a/internal-packages/database/prisma/migrations/20250730084611_add_payload_schema_to_background_worker_task/migration.sql b/internal-packages/database/prisma/migrations/20250730084611_add_payload_schema_to_background_worker_task/migration.sql new file mode 100644 index 0000000000..bbbb6694fd --- /dev/null +++ b/internal-packages/database/prisma/migrations/20250730084611_add_payload_schema_to_background_worker_task/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "BackgroundWorkerTask" ADD COLUMN "payloadSchema" JSONB; \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json index 2f83f11c82..42c76333a4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -579,4 +579,4 @@ "main": "./dist/commonjs/index.js", "types": "./dist/commonjs/index.d.ts", "module": "./dist/esm/index.js" -} \ No newline at end of file +} diff --git a/packages/core/src/v3/types/tasks.ts b/packages/core/src/v3/types/tasks.ts index 5c7a533eb3..acc322099a 100644 --- a/packages/core/src/v3/types/tasks.ts +++ b/packages/core/src/v3/types/tasks.ts @@ -345,7 +345,7 @@ type CommonTaskOptions< * JSON Schema for the task payload. This will be synced to the server during indexing. * Should be a valid JSON Schema Draft 7 object. */ - payloadSchema?: JSONSchema; + jsonSchema?: JSONSchema; }; export type TaskOptions< @@ -360,9 +360,8 @@ export type TaskOptionsWithSchema< TIdentifier extends string, TOutput = unknown, TInitOutput extends InitOutput = any, -> = Omit, "run"> & { - payloadSchema: JSONSchema; - run: (payload: any, params: RunFnParams) => Promise; +> = CommonTaskOptions & { + jsonSchema: JSONSchema; }; export type TaskWithSchemaOptions< diff --git a/packages/schema-to-json/examples/usage.ts b/packages/schema-to-json/examples/usage.ts deleted file mode 100644 index 5d136fc673..0000000000 --- a/packages/schema-to-json/examples/usage.ts +++ /dev/null @@ -1,81 +0,0 @@ -// This file shows how @trigger.dev/schema-to-json is used INTERNALLY by the SDK -// Regular users should NOT import this package directly! - -import { schemaToJsonSchema, type JSONSchema } from '@trigger.dev/schema-to-json'; -import { z } from 'zod'; - -// Example of how the SDK uses this internally: - -const userSchema = z.object({ - id: z.string(), - name: z.string(), - email: z.string().email(), - age: z.number().int().min(0), -}); - -// This is what happens internally in the SDK's schemaTask: -const result = schemaToJsonSchema(userSchema); -if (result) { - console.log('Converted Zod schema to JSON Schema:', result.jsonSchema); - console.log('Detected schema type:', result.schemaType); - // The SDK then includes this JSON Schema in the task metadata -} - -// Example: How different schema libraries are detected and converted - -// Yup schema -import * as y from 'yup'; -const yupSchema = y.object({ - name: y.string().required(), - age: y.number().required(), -}); - -const yupResult = schemaToJsonSchema(yupSchema); -console.log('Yup conversion:', yupResult); - -// ArkType schema (has built-in toJsonSchema) -import { type } from 'arktype'; -const arkSchema = type({ - name: 'string', - age: 'number', -}); - -const arkResult = schemaToJsonSchema(arkSchema); -console.log('ArkType conversion:', arkResult); - -// TypeBox (already JSON Schema) -import { Type } from '@sinclair/typebox'; -const typeBoxSchema = Type.Object({ - name: Type.String(), - age: Type.Number(), -}); - -const typeBoxResult = schemaToJsonSchema(typeBoxSchema); -console.log('TypeBox conversion:', typeBoxResult); - -// Example: Initialization (done automatically by the SDK) -import { initializeSchemaConverters, areConvertersInitialized } from '@trigger.dev/schema-to-json'; - -// The SDK calls this once when it loads -await initializeSchemaConverters(); - -// Check which converters are available -const status = areConvertersInitialized(); -console.log('Converter status:', status); -// { zod: true, yup: true, effect: true } - -// Example: How the SDK determines if a schema can be converted -import { canConvertSchema, detectSchemaType } from '@trigger.dev/schema-to-json'; - -const zodSchema = z.string(); -console.log('Can convert Zod?', canConvertSchema(zodSchema)); // true -console.log('Schema type:', detectSchemaType(zodSchema)); // 'zod' - -// For users: Just use the SDK! -// import { schemaTask } from '@trigger.dev/sdk/v3'; -// -// export const myTask = schemaTask({ -// id: 'my-task', -// schema: zodSchema, -// run: async (payload) => { /* ... */ } -// }); \ No newline at end of file diff --git a/packages/schema-to-json/package.json b/packages/schema-to-json/package.json index 4bf03fa735..753c045d16 100644 --- a/packages/schema-to-json/package.json +++ b/packages/schema-to-json/package.json @@ -21,12 +21,10 @@ "exports": { ".": { "import": { - "@triggerdotdev/source": "./src/index.ts", "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" }, "require": { - "@triggerdotdev/source": "./src/index.ts", "types": "./dist/commonjs/index.d.ts", "default": "./dist/commonjs/index.js" } @@ -39,8 +37,8 @@ "dev": "tshy --watch", "typecheck": "tsc -p tsconfig.src.json --noEmit", "test": "vitest", - "update-version": "changeset version && node scripts/updateVersion.js", - "check-exports": "tshy --check-exports" + "update-version": "tsx ../../scripts/updateVersion.ts", + "check-exports": "attw --pack ." }, "dependencies": { "@trigger.dev/core": "workspace:*", @@ -58,7 +56,8 @@ "valibot": "^1.0.0-beta.8", "vitest": "^2.1.8", "yup": "^1.6.1", - "zod": "^3.24.1 || ^4.0.0" + "zod": "^3.24.1 || ^4.0.0", + "rimraf": "6.0.1" }, "peerDependencies": { "@effect/schema": "^0.75.5", @@ -106,5 +105,8 @@ ".": "./src/index.ts" }, "project": "./tsconfig.src.json" - } -} \ No newline at end of file + }, + "main": "./dist/commonjs/index.js", + "types": "./dist/commonjs/index.d.ts", + "module": "./dist/esm/index.js" +} diff --git a/packages/schema-to-json/scripts/updateVersion.js b/packages/schema-to-json/scripts/updateVersion.js deleted file mode 100644 index ed0ab53841..0000000000 --- a/packages/schema-to-json/scripts/updateVersion.js +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env node - -import { readFileSync, writeFileSync } from "fs"; -import { fileURLToPath } from "url"; -import { dirname, join } from "path"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -// Read the package.json -const packageJsonPath = join(__dirname, "..", "package.json"); -const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")); - -// Read the root package.json to get the version -const rootPackageJsonPath = join(__dirname, "..", "..", "..", "package.json"); -const rootPackageJson = JSON.parse(readFileSync(rootPackageJsonPath, "utf-8")); - -// Update the version -packageJson.version = rootPackageJson.version; - -// Write back the package.json -writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n"); - -console.log(`Updated @trigger.dev/schema-to-json version to ${packageJson.version}`); \ No newline at end of file diff --git a/packages/schema-to-json/src/__tests__/index.test.ts b/packages/schema-to-json/src/__tests__/index.test.ts deleted file mode 100644 index 6e71a5b149..0000000000 --- a/packages/schema-to-json/src/__tests__/index.test.ts +++ /dev/null @@ -1,350 +0,0 @@ -import { describe, it, expect, beforeAll } from 'vitest'; -import { z } from 'zod'; -import * as y from 'yup'; -import { type } from 'arktype'; -import { Schema } from '@effect/schema'; -import { Type } from '@sinclair/typebox'; -import { - schemaToJsonSchema, - canConvertSchema, - detectSchemaType, - initializeSchemaConverters, - areConvertersInitialized -} from '../index.js'; - -// Initialize converters before running tests -beforeAll(async () => { - await initializeSchemaConverters(); -}); - -describe('schemaToJsonSchema', () => { - describe('Initialization', () => { - it('should have converters initialized', () => { - const status = areConvertersInitialized(); - expect(status.zod).toBe(true); - expect(status.yup).toBe(true); - expect(status.effect).toBe(true); - }); - }); - - describe('Zod schemas', () => { - it('should convert a simple Zod object schema', () => { - const schema = z.object({ - name: z.string(), - age: z.number(), - email: z.string().email(), - }); - - const result = schemaToJsonSchema(schema); - - expect(result).toBeDefined(); - expect(result?.schemaType).toBe('zod'); - expect(result?.jsonSchema).toMatchObject({ - type: 'object', - properties: { - name: { type: 'string' }, - age: { type: 'number' }, - email: { type: 'string', format: 'email' }, - }, - required: ['name', 'age', 'email'], - }); - }); - - it('should convert a Zod schema with optional fields', () => { - const schema = z.object({ - id: z.string(), - description: z.string().optional(), - tags: z.array(z.string()).optional(), - }); - - const result = schemaToJsonSchema(schema); - - expect(result).toBeDefined(); - expect(result?.jsonSchema).toMatchObject({ - type: 'object', - properties: { - id: { type: 'string' }, - description: { type: 'string' }, - tags: { type: 'array', items: { type: 'string' } }, - }, - required: ['id'], - }); - }); - - it('should handle Zod schema with name option', () => { - const schema = z.object({ - value: z.number(), - }); - - const result = schemaToJsonSchema(schema, { name: 'MySchema' }); - - expect(result).toBeDefined(); - expect(result?.jsonSchema).toBeDefined(); - // The exact structure depends on zod-to-json-schema implementation - }); - - it('should handle Zod 4 schema with built-in toJsonSchema method', () => { - // Mock a Zod 4 schema with toJsonSchema method - const mockZod4Schema = { - parse: (val: unknown) => val, - parseAsync: async (val: unknown) => val, - toJsonSchema: () => ({ - type: 'object', - properties: { - id: { type: 'string' }, - count: { type: 'number' } - }, - required: ['id', 'count'] - }) - }; - - const result = schemaToJsonSchema(mockZod4Schema); - - expect(result).toBeDefined(); - expect(result?.schemaType).toBe('zod'); - expect(result?.jsonSchema).toEqual({ - type: 'object', - properties: { - id: { type: 'string' }, - count: { type: 'number' } - }, - required: ['id', 'count'] - }); - }); - }); - - describe('Yup schemas', () => { - it('should convert a simple Yup object schema', () => { - const schema = y.object({ - name: y.string().required(), - age: y.number().required(), - email: y.string().email().required(), - }); - - const result = schemaToJsonSchema(schema); - - expect(result).toBeDefined(); - expect(result?.schemaType).toBe('yup'); - expect(result?.jsonSchema).toMatchObject({ - type: 'object', - properties: { - name: { type: 'string' }, - age: { type: 'number' }, - email: { type: 'string', format: 'email' }, - }, - required: ['name', 'age', 'email'], - }); - }); - - it('should convert a Yup schema with optional fields', () => { - const schema = y.object({ - id: y.string().required(), - description: y.string(), - count: y.number().min(0).max(100), - }); - - const result = schemaToJsonSchema(schema); - - expect(result).toBeDefined(); - expect(result?.jsonSchema).toMatchObject({ - type: 'object', - properties: { - id: { type: 'string' }, - description: { type: 'string' }, - count: { type: 'number', minimum: 0, maximum: 100 }, - }, - required: ['id'], - }); - }); - }); - - describe('ArkType schemas', () => { - it('should convert a simple ArkType schema', () => { - const schema = type({ - name: 'string', - age: 'number', - active: 'boolean', - }); - - const result = schemaToJsonSchema(schema); - - expect(result).toBeDefined(); - expect(result?.schemaType).toBe('arktype'); - expect(result?.jsonSchema).toBeDefined(); - expect(result?.jsonSchema.type).toBe('object'); - }); - - it('should convert an ArkType schema with optional fields', () => { - const schema = type({ - id: 'string', - 'description?': 'string', - 'tags?': 'string[]', - }); - - const result = schemaToJsonSchema(schema); - - expect(result).toBeDefined(); - expect(result?.jsonSchema).toBeDefined(); - expect(result?.jsonSchema.type).toBe('object'); - }); - }); - - describe('Effect schemas', () => { - it('should convert a simple Effect schema', () => { - const schema = Schema.Struct({ - name: Schema.String, - age: Schema.Number, - active: Schema.Boolean, - }); - - const result = schemaToJsonSchema(schema); - - expect(result).toBeDefined(); - expect(result?.schemaType).toBe('effect'); - expect(result?.jsonSchema).toMatchObject({ - type: 'object', - properties: { - name: { type: 'string' }, - age: { type: 'number' }, - active: { type: 'boolean' }, - }, - required: ['name', 'age', 'active'], - }); - }); - - it('should convert an Effect schema with optional fields', () => { - const schema = Schema.Struct({ - id: Schema.String, - description: Schema.optional(Schema.String), - count: Schema.optional(Schema.Number), - }); - - const result = schemaToJsonSchema(schema); - - expect(result).toBeDefined(); - expect(result?.jsonSchema).toBeDefined(); - expect(result?.jsonSchema.type).toBe('object'); - }); - }); - - describe('TypeBox schemas', () => { - it('should convert a simple TypeBox schema', () => { - const schema = Type.Object({ - name: Type.String(), - age: Type.Number(), - active: Type.Boolean(), - }); - - const result = schemaToJsonSchema(schema); - - expect(result).toBeDefined(); - expect(result?.schemaType).toBe('typebox'); - expect(result?.jsonSchema).toMatchObject({ - type: 'object', - properties: { - name: { type: 'string' }, - age: { type: 'number' }, - active: { type: 'boolean' }, - }, - required: ['name', 'age', 'active'], - }); - }); - - it('should convert a TypeBox schema with optional fields', () => { - const schema = Type.Object({ - id: Type.String(), - description: Type.Optional(Type.String()), - tags: Type.Optional(Type.Array(Type.String())), - }); - - const result = schemaToJsonSchema(schema); - - expect(result).toBeDefined(); - expect(result?.jsonSchema).toMatchObject({ - type: 'object', - properties: { - id: { type: 'string' }, - description: { type: 'string' }, - tags: { type: 'array', items: { type: 'string' } }, - }, - required: ['id'], - }); - }); - }); - - describe('Additional options', () => { - it('should merge additional properties', () => { - const schema = z.object({ - value: z.number(), - }); - - const result = schemaToJsonSchema(schema, { - additionalProperties: { - title: 'My Schema', - description: 'A test schema', - 'x-custom': 'custom value', - }, - }); - - expect(result).toBeDefined(); - expect(result?.jsonSchema.title).toBe('My Schema'); - expect(result?.jsonSchema.description).toBe('A test schema'); - expect(result?.jsonSchema['x-custom']).toBe('custom value'); - }); - }); - - describe('Unsupported schemas', () => { - it('should return undefined for unsupported schema types', () => { - const invalidSchema = { notASchema: true }; - const result = schemaToJsonSchema(invalidSchema); - expect(result).toBeUndefined(); - }); - - it('should return undefined for plain functions', () => { - const fn = (value: unknown) => typeof value === 'string'; - const result = schemaToJsonSchema(fn); - expect(result).toBeUndefined(); - }); - }); -}); - -describe('canConvertSchema', () => { - it('should return true for supported schemas', () => { - expect(canConvertSchema(z.string())).toBe(true); - expect(canConvertSchema(y.string())).toBe(true); - expect(canConvertSchema(type('string'))).toBe(true); - expect(canConvertSchema(Schema.String)).toBe(true); - expect(canConvertSchema(Type.String())).toBe(true); - }); - - it('should return false for unsupported schemas', () => { - expect(canConvertSchema({ notASchema: true })).toBe(false); - expect(canConvertSchema(() => true)).toBe(false); - }); -}); - -describe('detectSchemaType', () => { - it('should detect Zod schemas', () => { - expect(detectSchemaType(z.string())).toBe('zod'); - }); - - it('should detect Yup schemas', () => { - expect(detectSchemaType(y.string())).toBe('yup'); - }); - - it('should detect ArkType schemas', () => { - expect(detectSchemaType(type('string'))).toBe('arktype'); - }); - - it('should detect Effect schemas', () => { - expect(detectSchemaType(Schema.String)).toBe('effect'); - }); - - it('should detect TypeBox schemas', () => { - expect(detectSchemaType(Type.String())).toBe('typebox'); - }); - - it('should return unknown for unsupported schemas', () => { - expect(detectSchemaType({ notASchema: true })).toBe('unknown'); - }); -}); \ No newline at end of file diff --git a/packages/schema-to-json/src/tests/index.test.ts b/packages/schema-to-json/src/tests/index.test.ts new file mode 100644 index 0000000000..3bc3111d63 --- /dev/null +++ b/packages/schema-to-json/src/tests/index.test.ts @@ -0,0 +1,351 @@ +import { describe, it, expect, beforeAll } from "vitest"; +import { z } from "zod"; +import * as y from "yup"; +// @ts-ignore +import { type } from "arktype"; +import { Schema } from "@effect/schema"; +import { Type } from "@sinclair/typebox"; +import { + schemaToJsonSchema, + canConvertSchema, + detectSchemaType, + initializeSchemaConverters, + areConvertersInitialized, +} from "../index.js"; + +// Initialize converters before running tests +beforeAll(async () => { + await initializeSchemaConverters(); +}); + +describe("schemaToJsonSchema", () => { + describe("Initialization", () => { + it("should have converters initialized", () => { + const status = areConvertersInitialized(); + expect(status.zod).toBe(true); + expect(status.yup).toBe(true); + expect(status.effect).toBe(true); + }); + }); + + describe("Zod schemas", () => { + it("should convert a simple Zod object schema", () => { + const schema = z.object({ + name: z.string(), + age: z.number(), + email: z.string().email(), + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.schemaType).toBe("zod"); + expect(result?.jsonSchema).toMatchObject({ + type: "object", + properties: { + name: { type: "string" }, + age: { type: "number" }, + email: { type: "string", format: "email" }, + }, + required: ["name", "age", "email"], + }); + }); + + it("should convert a Zod schema with optional fields", () => { + const schema = z.object({ + id: z.string(), + description: z.string().optional(), + tags: z.array(z.string()).optional(), + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.jsonSchema).toMatchObject({ + type: "object", + properties: { + id: { type: "string" }, + description: { type: "string" }, + tags: { type: "array", items: { type: "string" } }, + }, + required: ["id"], + }); + }); + + it("should handle Zod schema with name option", () => { + const schema = z.object({ + value: z.number(), + }); + + const result = schemaToJsonSchema(schema, { name: "MySchema" }); + + expect(result).toBeDefined(); + expect(result?.jsonSchema).toBeDefined(); + // The exact structure depends on zod-to-json-schema implementation + }); + + it("should handle Zod 4 schema with built-in toJsonSchema method", () => { + // Mock a Zod 4 schema with toJsonSchema method + const mockZod4Schema = { + parse: (val: unknown) => val, + parseAsync: async (val: unknown) => val, + toJsonSchema: () => ({ + type: "object", + properties: { + id: { type: "string" }, + count: { type: "number" }, + }, + required: ["id", "count"], + }), + }; + + const result = schemaToJsonSchema(mockZod4Schema); + + expect(result).toBeDefined(); + expect(result?.schemaType).toBe("zod"); + expect(result?.jsonSchema).toEqual({ + type: "object", + properties: { + id: { type: "string" }, + count: { type: "number" }, + }, + required: ["id", "count"], + }); + }); + }); + + describe("Yup schemas", () => { + it("should convert a simple Yup object schema", () => { + const schema = y.object({ + name: y.string().required(), + age: y.number().required(), + email: y.string().email().required(), + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.schemaType).toBe("yup"); + expect(result?.jsonSchema).toMatchObject({ + type: "object", + properties: { + name: { type: "string" }, + age: { type: "number" }, + email: { type: "string", format: "email" }, + }, + required: ["name", "age", "email"], + }); + }); + + it("should convert a Yup schema with optional fields", () => { + const schema = y.object({ + id: y.string().required(), + description: y.string(), + count: y.number().min(0).max(100), + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.jsonSchema).toMatchObject({ + type: "object", + properties: { + id: { type: "string" }, + description: { type: "string" }, + count: { type: "number", minimum: 0, maximum: 100 }, + }, + required: ["id"], + }); + }); + }); + + describe("ArkType schemas", () => { + it("should convert a simple ArkType schema", () => { + const schema = type({ + name: "string", + age: "number", + active: "boolean", + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.schemaType).toBe("arktype"); + expect(result?.jsonSchema).toBeDefined(); + expect(result?.jsonSchema.type).toBe("object"); + }); + + it("should convert an ArkType schema with optional fields", () => { + const schema = type({ + id: "string", + "description?": "string", + "tags?": "string[]", + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.jsonSchema).toBeDefined(); + expect(result?.jsonSchema.type).toBe("object"); + }); + }); + + describe("Effect schemas", () => { + it("should convert a simple Effect schema", () => { + const schema = Schema.Struct({ + name: Schema.String, + age: Schema.Number, + active: Schema.Boolean, + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.schemaType).toBe("effect"); + expect(result?.jsonSchema).toMatchObject({ + type: "object", + properties: { + name: { type: "string" }, + age: { type: "number" }, + active: { type: "boolean" }, + }, + required: ["name", "age", "active"], + }); + }); + + it("should convert an Effect schema with optional fields", () => { + const schema = Schema.Struct({ + id: Schema.String, + description: Schema.optional(Schema.String), + count: Schema.optional(Schema.Number), + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.jsonSchema).toBeDefined(); + expect(result?.jsonSchema.type).toBe("object"); + }); + }); + + describe("TypeBox schemas", () => { + it("should convert a simple TypeBox schema", () => { + const schema = Type.Object({ + name: Type.String(), + age: Type.Number(), + active: Type.Boolean(), + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.schemaType).toBe("typebox"); + expect(result?.jsonSchema).toMatchObject({ + type: "object", + properties: { + name: { type: "string" }, + age: { type: "number" }, + active: { type: "boolean" }, + }, + required: ["name", "age", "active"], + }); + }); + + it("should convert a TypeBox schema with optional fields", () => { + const schema = Type.Object({ + id: Type.String(), + description: Type.Optional(Type.String()), + tags: Type.Optional(Type.Array(Type.String())), + }); + + const result = schemaToJsonSchema(schema); + + expect(result).toBeDefined(); + expect(result?.jsonSchema).toMatchObject({ + type: "object", + properties: { + id: { type: "string" }, + description: { type: "string" }, + tags: { type: "array", items: { type: "string" } }, + }, + required: ["id"], + }); + }); + }); + + describe("Additional options", () => { + it("should merge additional properties", () => { + const schema = z.object({ + value: z.number(), + }); + + const result = schemaToJsonSchema(schema, { + additionalProperties: { + title: "My Schema", + description: "A test schema", + "x-custom": "custom value", + }, + }); + + expect(result).toBeDefined(); + expect(result?.jsonSchema.title).toBe("My Schema"); + expect(result?.jsonSchema.description).toBe("A test schema"); + expect(result?.jsonSchema["x-custom"]).toBe("custom value"); + }); + }); + + describe("Unsupported schemas", () => { + it("should return undefined for unsupported schema types", () => { + const invalidSchema = { notASchema: true }; + const result = schemaToJsonSchema(invalidSchema); + expect(result).toBeUndefined(); + }); + + it("should return undefined for plain functions", () => { + const fn = (value: unknown) => typeof value === "string"; + const result = schemaToJsonSchema(fn); + expect(result).toBeUndefined(); + }); + }); +}); + +describe("canConvertSchema", () => { + it("should return true for supported schemas", () => { + expect(canConvertSchema(z.string())).toBe(true); + expect(canConvertSchema(y.string())).toBe(true); + expect(canConvertSchema(type("string"))).toBe(true); + expect(canConvertSchema(Schema.String)).toBe(true); + expect(canConvertSchema(Type.String())).toBe(true); + }); + + it("should return false for unsupported schemas", () => { + expect(canConvertSchema({ notASchema: true })).toBe(false); + expect(canConvertSchema(() => true)).toBe(false); + }); +}); + +describe("detectSchemaType", () => { + it("should detect Zod schemas", () => { + expect(detectSchemaType(z.string())).toBe("zod"); + }); + + it("should detect Yup schemas", () => { + expect(detectSchemaType(y.string())).toBe("yup"); + }); + + it("should detect ArkType schemas", () => { + expect(detectSchemaType(type("string"))).toBe("arktype"); + }); + + it("should detect Effect schemas", () => { + expect(detectSchemaType(Schema.String)).toBe("effect"); + }); + + it("should detect TypeBox schemas", () => { + expect(detectSchemaType(Type.String())).toBe("typebox"); + }); + + it("should return unknown for unsupported schemas", () => { + expect(detectSchemaType({ notASchema: true })).toBe("unknown"); + }); +}); diff --git a/packages/schema-to-json/tsconfig.src.json b/packages/schema-to-json/tsconfig.src.json index 18167f8eda..db06c53317 100644 --- a/packages/schema-to-json/tsconfig.src.json +++ b/packages/schema-to-json/tsconfig.src.json @@ -1,10 +1,10 @@ { - "extends": "../../.configs/tsconfig.base.json", - "compilerOptions": { - "rootDir": "./src", - "outDir": "./dist", - "types": ["node"] - }, + "extends": "./tsconfig.json", "include": ["./src/**/*.ts"], - "exclude": ["./src/**/*.test.ts"] -} \ No newline at end of file + "compilerOptions": { + "isolatedDeclarations": false, + "composite": true, + "sourceMap": true, + "customConditions": ["@triggerdotdev/source"] + } +} diff --git a/packages/schema-to-json/tsconfig.test.json b/packages/schema-to-json/tsconfig.test.json index 1db15ed6ce..fb90f380ec 100644 --- a/packages/schema-to-json/tsconfig.test.json +++ b/packages/schema-to-json/tsconfig.test.json @@ -1,8 +1,11 @@ { - "extends": "../../.configs/tsconfig.base.json", + "extends": "./tsconfig.json", + "include": ["./test/**/*.ts"], + "references": [{ "path": "./tsconfig.src.json" }], "compilerOptions": { - "rootDir": "./", - "types": ["node", "vitest/globals"] - }, - "include": ["./src/**/*.test.ts", "./test/**/*.ts"] -} \ No newline at end of file + "isolatedDeclarations": false, + "composite": true, + "sourceMap": true, + "types": ["vitest/globals"] + } +} diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index e37691fe94..519d5eb47a 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -79,7 +79,7 @@ "zod": "3.23.8" }, "peerDependencies": { - "zod": "^3.0.0", + "zod": "^3.0.0 || ^4.0.0", "ai": "^4.2.0" }, "peerDependenciesMeta": { diff --git a/packages/trigger-sdk/src/v3/shared.ts b/packages/trigger-sdk/src/v3/shared.ts index d657745884..913b9487e0 100644 --- a/packages/trigger-sdk/src/v3/shared.ts +++ b/packages/trigger-sdk/src/v3/shared.ts @@ -234,7 +234,7 @@ export function createTask< retry: params.retry ? { ...defaultRetryOptions, ...params.retry } : undefined, machine: typeof params.machine === "string" ? { preset: params.machine } : params.machine, maxDuration: params.maxDuration, - payloadSchema: params.payloadSchema, + payloadSchema: params.jsonSchema, fns: { run: params.run, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 428bc38513..10babd2cac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1820,7 +1820,7 @@ importers: version: link:../core zod-to-json-schema: specifier: ^3.24.5 - version: 3.24.5(zod@4.0.10) + version: 3.24.5(zod@3.25.76) devDependencies: '@effect/schema': specifier: ^0.75.5 @@ -1834,6 +1834,9 @@ importers: effect: specifier: ^3.11.11 version: 3.17.1 + rimraf: + specifier: 6.0.1 + version: 6.0.1 runtypes: specifier: ^6.7.0 version: 6.7.0 @@ -1854,7 +1857,7 @@ importers: version: 1.6.1 zod: specifier: ^3.24.1 || ^4.0.0 - version: 4.0.10 + version: 3.25.76 packages/trigger-sdk: dependencies: @@ -4945,7 +4948,7 @@ packages: '@babel/traverse': 7.24.7 '@babel/types': 7.27.0 convert-source-map: 2.0.0 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -5540,7 +5543,7 @@ packages: '@babel/helper-split-export-declaration': 7.24.7 '@babel/parser': 7.27.0 '@babel/types': 7.27.0 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -5554,7 +5557,7 @@ packages: '@babel/parser': 7.27.0 '@babel/template': 7.25.0 '@babel/types': 7.27.0 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -11530,7 +11533,7 @@ packages: engines: {node: '>=18'} hasBin: true dependencies: - debug: 4.4.1 + debug: 4.4.1(supports-color@10.0.0) extract-zip: 2.0.1 progress: 2.0.3 proxy-agent: 6.5.0 @@ -11547,7 +11550,7 @@ packages: engines: {node: '>=18'} hasBin: true dependencies: - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) extract-zip: 2.0.1 progress: 2.0.3 proxy-agent: 6.4.0 @@ -19988,8 +19991,12 @@ packages: /@types/json-schema@7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} + /@types/json-schema@7.0.13: + resolution: {integrity: sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==} + /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + dev: true /@types/json5@0.0.29: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} @@ -20592,7 +20599,7 @@ packages: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.31.0) - '@types/json-schema': 7.0.15 + '@types/json-schema': 7.0.13 '@types/semver': 7.5.1 '@typescript-eslint/scope-manager': 5.59.6 '@typescript-eslint/types': 5.59.6 @@ -21421,7 +21428,7 @@ packages: engines: {node: '>= 6.0.0'} requiresBuild: true dependencies: - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) transitivePeerDependencies: - supports-color @@ -21429,7 +21436,7 @@ packages: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} dependencies: - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) transitivePeerDependencies: - supports-color @@ -22367,7 +22374,7 @@ packages: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) http-errors: 2.0.0 iconv-lite: 0.6.3 on-finished: 2.4.1 @@ -22728,7 +22735,7 @@ packages: /capnp-ts@0.7.0: resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==} dependencies: - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -23870,7 +23877,7 @@ packages: ms: 2.1.3 supports-color: 10.0.0 - /debug@4.4.1: + /debug@4.4.1(supports-color@10.0.0): resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} peerDependencies: @@ -23880,7 +23887,7 @@ packages: optional: true dependencies: ms: 2.1.3 - dev: false + supports-color: 10.0.0 /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} @@ -25893,7 +25900,7 @@ packages: engines: {node: '>= 10.17.0'} hasBin: true dependencies: - debug: 4.4.1 + debug: 4.4.1(supports-color@10.0.0) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -26163,7 +26170,7 @@ packages: resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} engines: {node: '>= 0.8'} dependencies: - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -26644,7 +26651,7 @@ packages: dependencies: basic-ftp: 5.0.3 data-uri-to-buffer: 5.0.1 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) fs-extra: 8.1.0 transitivePeerDependencies: - supports-color @@ -27194,7 +27201,7 @@ packages: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) transitivePeerDependencies: - supports-color dev: false @@ -27205,7 +27212,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.1 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) transitivePeerDependencies: - supports-color @@ -27214,7 +27221,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.4 - debug: 4.4.1 + debug: 4.4.1(supports-color@10.0.0) transitivePeerDependencies: - supports-color dev: false @@ -27242,7 +27249,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.1 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) transitivePeerDependencies: - supports-color dev: true @@ -27252,7 +27259,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.1 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) transitivePeerDependencies: - supports-color dev: false @@ -27262,7 +27269,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.4 - debug: 4.4.1 + debug: 4.4.1(supports-color@10.0.0) transitivePeerDependencies: - supports-color dev: false @@ -27956,7 +27963,7 @@ packages: engines: {node: '>=10'} dependencies: '@jridgewell/trace-mapping': 0.3.25 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -29686,7 +29693,7 @@ packages: resolution: {integrity: sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==} dependencies: '@types/debug': 4.1.12 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.0.6 micromark-factory-space: 1.0.0 @@ -29710,7 +29717,7 @@ packages: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} dependencies: '@types/debug': 4.1.12 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 @@ -31320,7 +31327,7 @@ packages: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.1 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) get-uri: 6.0.1 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.5 @@ -31336,7 +31343,7 @@ packages: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.4 - debug: 4.4.1 + debug: 4.4.1(supports-color@10.0.0) get-uri: 6.0.1 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 @@ -32542,7 +32549,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.1 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.5 lru-cache: 7.18.3 @@ -32558,7 +32565,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.4 - debug: 4.4.1 + debug: 4.4.1(supports-color@10.0.0) http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -32620,7 +32627,7 @@ packages: dependencies: '@puppeteer/browsers': 2.4.0 chromium-bidi: 0.6.5(devtools-protocol@0.0.1342118) - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) devtools-protocol: 0.0.1342118 typed-query-selector: 2.12.0 ws: 8.18.0(bufferutil@4.0.9) @@ -32637,7 +32644,7 @@ packages: dependencies: '@puppeteer/browsers': 2.10.6 chromium-bidi: 7.2.0(devtools-protocol@0.0.1464554) - debug: 4.4.1 + debug: 4.4.1(supports-color@10.0.0) devtools-protocol: 0.0.1464554 typed-query-selector: 2.12.0 ws: 8.18.3 @@ -32648,7 +32655,7 @@ packages: - utf-8-validate dev: false - /puppeteer@23.4.0(typescript@5.5.4): + /puppeteer@23.4.0(typescript@5.8.3): resolution: {integrity: sha512-FxgFFJI7NAsX8uebiEDSjS86vufz9TaqERQHShQT0lCbSRI3jUPEcz/0HdwLiYvfYNsc1zGjqY3NsGZya4PvUA==} engines: {node: '>=18'} hasBin: true @@ -34196,7 +34203,7 @@ packages: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/json-schema': 7.0.15 + '@types/json-schema': 7.0.13 ajv: 6.12.6 ajv-keywords: 3.5.2(ajv@6.12.6) dev: false @@ -34314,7 +34321,7 @@ packages: resolution: {integrity: sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==} engines: {node: '>= 18'} dependencies: - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) destroy: 1.2.0 encodeurl: 2.0.0 escape-html: 1.0.3 @@ -34767,7 +34774,7 @@ packages: requiresBuild: true dependencies: agent-base: 6.0.2 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -34779,7 +34786,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.1 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -34790,7 +34797,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.4 - debug: 4.4.1 + debug: 4.4.1(supports-color@10.0.0) socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -37507,7 +37514,7 @@ packages: hasBin: true dependencies: cac: 6.7.14 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) mlly: 1.7.1 pathe: 1.1.2 picocolors: 1.1.1 @@ -37531,7 +37538,7 @@ packages: hasBin: true dependencies: cac: 6.7.14 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) es-module-lexer: 1.7.0 pathe: 1.1.2 vite: 5.2.7(@types/node@20.14.14) @@ -37552,7 +37559,7 @@ packages: hasBin: true dependencies: cac: 6.7.14 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) es-module-lexer: 1.7.0 pathe: 2.0.3 vite: 5.2.7(@types/node@20.14.14) @@ -37729,7 +37736,7 @@ packages: '@vitest/spy': 2.1.9 '@vitest/utils': 2.1.9 chai: 5.2.0 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) expect-type: 1.2.1 magic-string: 0.30.17 pathe: 1.1.2 @@ -38514,12 +38521,12 @@ packages: dependencies: zod: 3.23.8 - /zod-to-json-schema@3.24.5(zod@4.0.10): + /zod-to-json-schema@3.24.5(zod@3.25.76): resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} peerDependencies: zod: ^3.24.1 dependencies: - zod: 4.0.10 + zod: 3.25.76 dev: false /zod-validation-error@1.5.0(zod@3.23.8): @@ -38540,7 +38547,6 @@ packages: /zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - dev: false /zustand@4.5.5(@types/react@18.2.69)(react@18.2.0): resolution: {integrity: sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==} diff --git a/references/hello-world/src/trigger/jsonSchema.ts b/references/hello-world/src/trigger/jsonSchema.ts index e194001c01..f347f9d389 100644 --- a/references/hello-world/src/trigger/jsonSchema.ts +++ b/references/hello-world/src/trigger/jsonSchema.ts @@ -12,10 +12,12 @@ const userSchema = z.object({ name: z.string().min(1), email: z.string().email(), age: z.number().int().min(0).max(150), - preferences: z.object({ - newsletter: z.boolean().default(false), - theme: z.enum(["light", "dark"]).default("light"), - }).optional(), + preferences: z + .object({ + newsletter: z.boolean().default(false), + theme: z.enum(["light", "dark"]).default("light"), + }) + .optional(), }); export const processUserWithZod = schemaTask({ @@ -23,9 +25,9 @@ export const processUserWithZod = schemaTask({ schema: userSchema, run: async (payload, { ctx }) => { // payload is fully typed based on the Zod schema - logger.info("Processing user with Zod schema", { - userId: payload.id, - userName: payload.name + logger.info("Processing user with Zod schema", { + userId: payload.id, + userName: payload.name, }); // The schema is automatically converted to JSON Schema and synced @@ -43,20 +45,20 @@ export const processUserWithZod = schemaTask({ export const processOrderManualSchema = task({ id: "json-schema-manual-example", // Manually provide JSON Schema for the payload - payloadSchema: { + jsonSchema: { $schema: "http://json-schema.org/draft-07/schema#", type: "object", title: "Order Processing Request", description: "Schema for processing customer orders", properties: { - orderId: { - type: "string", + orderId: { + type: "string", pattern: "^ORD-[0-9]+$", - description: "Order ID in format ORD-XXXXX" + description: "Order ID in format ORD-XXXXX", }, - customerId: { - type: "string", - format: "uuid" + customerId: { + type: "string", + format: "uuid", }, items: { type: "array", @@ -72,8 +74,8 @@ export const processOrderManualSchema = task({ additionalProperties: false, }, }, - totalAmount: { - type: "number", + totalAmount: { + type: "number", minimum: 0, multipleOf: 0.01, }, @@ -87,8 +89,8 @@ export const processOrderManualSchema = task({ additionalProperties: false, } satisfies JSONSchema, run: async (payload, { ctx }) => { - logger.info("Processing order with manual JSON Schema", { - orderId: payload.orderId + logger.info("Processing order with manual JSON Schema", { + orderId: payload.orderId, }); // Note: With plain tasks, the payload is typed as 'any' @@ -105,7 +107,10 @@ export const processOrderManualSchema = task({ // Example 3: Using schemaTask with Yup // =========================================== const productSchema = y.object({ - sku: y.string().required().matches(/^[A-Z]{3}-[0-9]{5}$/), + sku: y + .string() + .required() + .matches(/^[A-Z]{3}-[0-9]{5}$/), name: y.string().required().min(3).max(100), description: y.string().max(500), price: y.number().required().positive(), @@ -117,7 +122,7 @@ export const processProductWithYup = schemaTask({ id: "json-schema-yup-example", schema: productSchema, run: async (payload, { ctx }) => { - logger.info("Processing product with Yup schema", { + logger.info("Processing product with Yup schema", { sku: payload.sku, name: payload.name, }); @@ -138,11 +143,13 @@ const invoiceSchema = type({ date: "Date", dueDate: "Date", "discount?": "number", - lineItems: [{ - description: "string", - quantity: "integer", - unitPrice: "number", - }], + lineItems: [ + { + description: "string", + quantity: "number", + unitPrice: "number", + }, + ], customer: { id: "string", name: "string", @@ -154,15 +161,12 @@ export const processInvoiceWithArkType = schemaTask({ id: "json-schema-arktype-example", schema: invoiceSchema, run: async (payload, { ctx }) => { - logger.info("Processing invoice with ArkType schema", { + logger.info("Processing invoice with ArkType schema", { invoiceNumber: payload.invoiceNumber, customerName: payload.customer.name, }); - const total = payload.lineItems.reduce( - (sum, item) => sum + (item.quantity * item.unitPrice), - 0 - ); + const total = payload.lineItems.reduce((sum, item) => sum + item.quantity * item.unitPrice, 0); const discount = payload.discount || 0; const finalAmount = total * (1 - discount / 100); @@ -198,12 +202,12 @@ type EventType = Static; export const processEventWithTypeBox = task({ id: "json-schema-typebox-example", // TypeBox schemas are already JSON Schema compliant - payloadSchema: eventSchema, - run: async (payload, { ctx }) => { + jsonSchema: eventSchema, + run: async (payload: EventType, { ctx }) => { // Cast to get TypeScript type safety - const event = payload as EventType; - - logger.info("Processing event with TypeBox schema", { + const event = payload; + + logger.info("Processing event with TypeBox schema", { eventId: event.eventId, eventType: event.eventType, userId: event.userId, @@ -252,7 +256,7 @@ export const sendNotificationBadExample = task({ run: async (payload, { ctx }) => { // You'd have to manually validate const notification = notificationSchema.parse(payload); - + logger.info("This is not ideal - use schemaTask instead!"); return { sent: true }; @@ -265,14 +269,14 @@ export const sendNotificationGoodExample = schemaTask({ schema: notificationSchema, run: async (notification, { ctx }) => { // notification is already validated and typed! - logger.info("Sending notification", { + logger.info("Sending notification", { recipientId: notification.recipientId, type: notification.type, priority: notification.priority, }); // Simulate sending notification - await new Promise(resolve => setTimeout(resolve, 1000)); + await new Promise((resolve) => setTimeout(resolve, 1000)); return { sent: true, @@ -302,12 +306,16 @@ const companySchema = z.object({ billing: addressSchema, shipping: addressSchema.optional(), }), - contacts: z.array(z.object({ - name: z.string(), - email: z.string().email(), - phone: z.string().optional(), - role: z.enum(["primary", "billing", "technical"]), - })).min(1), + contacts: z + .array( + z.object({ + name: z.string(), + email: z.string().email(), + phone: z.string().optional(), + role: z.enum(["primary", "billing", "technical"]), + }) + ) + .min(1), settings: z.object({ invoicePrefix: z.string().default("INV"), paymentTerms: z.number().int().min(0).max(90).default(30), @@ -324,7 +332,7 @@ export const processCompanyWithComplexSchema = schemaTask({ factor: 2, }, run: async (payload, { ctx }) => { - logger.info("Processing company with complex schema", { + logger.info("Processing company with complex schema", { companyId: payload.companyId, name: payload.name, contactCount: payload.contacts.length, @@ -332,7 +340,7 @@ export const processCompanyWithComplexSchema = schemaTask({ // Process each contact for (const contact of payload.contacts) { - logger.info("Processing contact", { + logger.info("Processing contact", { name: contact.name, role: contact.role, }); @@ -342,7 +350,7 @@ export const processCompanyWithComplexSchema = schemaTask({ processed: true, companyId: payload.companyId, name: payload.name, - primaryContact: payload.contacts.find(c => c.role === "primary"), + primaryContact: payload.contacts.find((c) => c.role === "primary"), }; }, }); diff --git a/references/hello-world/src/trigger/jsonSchemaSimple.ts b/references/hello-world/src/trigger/jsonSchemaSimple.ts index 58812fc34a..a844b14034 100644 --- a/references/hello-world/src/trigger/jsonSchemaSimple.ts +++ b/references/hello-world/src/trigger/jsonSchemaSimple.ts @@ -13,10 +13,14 @@ const emailSchema = z.object({ to: z.string().email(), subject: z.string(), body: z.string(), - attachments: z.array(z.object({ - filename: z.string(), - url: z.string().url(), - })).optional(), + attachments: z + .array( + z.object({ + filename: z.string(), + url: z.string().url(), + }) + ) + .optional(), }); export const sendEmailSchemaTask = schemaTask({ @@ -30,15 +34,15 @@ export const sendEmailSchemaTask = schemaTask({ // body: string; // attachments?: Array<{ filename: string; url: string; }>; // } - - logger.info("Sending email", { + + logger.info("Sending email", { to: payload.to, subject: payload.subject, hasAttachments: !!payload.attachments?.length, }); // Your email sending logic here... - + return { sent: true, messageId: `msg_${ctx.run.id}`, @@ -53,19 +57,19 @@ export const sendEmailSchemaTask = schemaTask({ // - Good for when you already have JSON Schema definitions export const sendEmailPlainTask = task({ id: "send-email-plain-task", - payloadSchema: { + jsonSchema: { type: "object", properties: { - to: { - type: "string", + to: { + type: "string", format: "email", description: "Recipient email address", }, - subject: { + subject: { type: "string", maxLength: 200, }, - body: { + body: { type: "string", }, attachments: { @@ -84,13 +88,13 @@ export const sendEmailPlainTask = task({ } satisfies JSONSchema, // Use 'satisfies' for type checking run: async (payload, { ctx }) => { // payload is typed as 'any' - you need to validate/cast it yourself - logger.info("Sending email", { + logger.info("Sending email", { to: payload.to, subject: payload.subject, }); // Your email sending logic here... - + return { sent: true, messageId: `msg_${ctx.run.id}`, @@ -147,16 +151,22 @@ export const demonstrateBenefits = task({ // =========================================== const userRegistrationSchema = z.object({ email: z.string().email(), - username: z.string().min(3).max(20).regex(/^[a-zA-Z0-9_]+$/), + username: z + .string() + .min(3) + .max(20) + .regex(/^[a-zA-Z0-9_]+$/), password: z.string().min(8), profile: z.object({ firstName: z.string(), lastName: z.string(), dateOfBirth: z.string().optional(), // ISO date string - preferences: z.object({ - newsletter: z.boolean().default(false), - notifications: z.boolean().default(true), - }).default({}), + preferences: z + .object({ + newsletter: z.boolean().default(false), + notifications: z.boolean().default(true), + }) + .default({}), }), referralCode: z.string().optional(), }); @@ -170,7 +180,7 @@ export const registerUser = schemaTask({ maxTimeoutInMs: 10000, }, run: async (payload, { ctx }) => { - logger.info("Registering new user", { + logger.info("Registering new user", { email: payload.email, username: payload.username, }); diff --git a/references/json-schema-test/src/trigger/core-functionality-test.ts b/references/json-schema-test/src/trigger/core-functionality-test.ts index f1136a42c2..1ec4997802 100644 --- a/references/json-schema-test/src/trigger/core-functionality-test.ts +++ b/references/json-schema-test/src/trigger/core-functionality-test.ts @@ -24,11 +24,11 @@ const manualSchema: JSONSchema = { required: ["id", "name", "email"], }; -// Test 2: Plain task accepts payloadSchema +// Test 2: Plain task accepts jsonSchema export const plainJsonSchemaTask = task({ id: "plain-json-schema-task", - payloadSchema: manualSchema, - run: async (payload, { ctx }) => { + jsonSchema: manualSchema, + run: async (payload: any, { ctx }) => { // payload is any, but schema is properly stored console.log("Received payload:", payload); diff --git a/references/json-schema-test/src/trigger/simple-type-test.ts b/references/json-schema-test/src/trigger/simple-type-test.ts index 458942be51..f0c2e4fc5e 100644 --- a/references/json-schema-test/src/trigger/simple-type-test.ts +++ b/references/json-schema-test/src/trigger/simple-type-test.ts @@ -1,5 +1,5 @@ // This file tests the core JSON schema functionality without external dependencies -import { schemaTask, task, type JSONSchema } from "@trigger.dev/sdk/v3"; +import { schemaTask, task, type JSONSchema } from "@trigger.dev/sdk"; import { z } from "zod"; // Test 1: Basic type inference with schemaTask @@ -45,7 +45,7 @@ const jsonSchemaExample = { export const testJSONSchemaType = task({ id: "test-json-schema-type", - payloadSchema: jsonSchemaExample, + jsonSchema: jsonSchemaExample, run: async (payload, { ctx }) => { // payload is 'any' with plain task, but the schema is properly typed return { diff --git a/references/json-schema-test/src/trigger/test-other-schemas.ts b/references/json-schema-test/src/trigger/test-other-schemas.ts index d82d1483d6..94508ed2cc 100644 --- a/references/json-schema-test/src/trigger/test-other-schemas.ts +++ b/references/json-schema-test/src/trigger/test-other-schemas.ts @@ -1,4 +1,4 @@ -import { schemaTask } from "@trigger.dev/sdk/v3"; +import { schemaTask } from "@trigger.dev/sdk"; import { Type } from "@sinclair/typebox"; import { array, @@ -35,7 +35,7 @@ const typeBoxSchema = Type.Object({ export const typeBoxTask = schemaTask({ id: "typebox-schema-task", - schema: typeBoxSchema as any, + schema: typeBoxSchema, run: async (payload, { ctx }) => { // TypeBox provides static type inference const id: string = payload.id; @@ -114,7 +114,6 @@ const effectSchema = S.Struct({ user: S.Struct({ userId: S.String, email: S.String, - attributes: S.optional(S.Record(S.String, S.Unknown)), }), product: S.optional( S.Struct({ @@ -137,8 +136,8 @@ type EffectEvent = S.Schema.Type; export const effectSchemaTask = schemaTask({ id: "effect-schema-task", - schema: effectSchema as any, - run: async (payload: any, { ctx }) => { + schema: effectSchema, + run: async (payload, { ctx }) => { // Effect Schema provides type safety const eventId = payload.event.eventId; const eventType = payload.event.eventType; diff --git a/references/json-schema-test/src/trigger/test-zod.ts b/references/json-schema-test/src/trigger/test-zod.ts index 34e483ad6d..2519fcca8a 100644 --- a/references/json-schema-test/src/trigger/test-zod.ts +++ b/references/json-schema-test/src/trigger/test-zod.ts @@ -37,12 +37,14 @@ export const zodSchemaTask = schemaTask({ const complexSchema = z.object({ order: z.object({ orderId: z.string().uuid(), - items: z.array(z.object({ - productId: z.string(), - quantity: z.number().positive(), - price: z.number().positive(), - discount: z.number().min(0).max(100).optional(), - })), + items: z.array( + z.object({ + productId: z.string(), + quantity: z.number().positive(), + price: z.number().positive(), + discount: z.number().min(0).max(100).optional(), + }) + ), customer: z.object({ customerId: z.string(), email: z.string().email(), @@ -82,7 +84,7 @@ export const complexZodTask = schemaTask({ const quantity: number = firstItem.quantity; const customerEmail: string = payload.order.customer.email; const zipCode: string = payload.order.customer.shippingAddress.zipCode; - + // Discriminated union type checking if (payload.order.paymentMethod.type === "credit_card") { const brand: "visa" | "mastercard" | "amex" = payload.order.paymentMethod.brand; @@ -127,13 +129,15 @@ const manualJsonSchema: JSONSchema = { export const plainJsonSchemaTask = task({ id: "plain-json-schema-task", - payloadSchema: manualJsonSchema, + jsonSchema: manualJsonSchema, run: async (payload, { ctx }) => { // With plain task, payload is 'any' so we need to manually type it const taskId = payload.taskId as string; const priority = payload.priority as number; const tags = payload.tags as string[] | undefined; - const config = payload.config as { timeout: number; retries: number; async?: boolean } | undefined; + const config = payload.config as + | { timeout: number; retries: number; async?: boolean } + | undefined; return { processed: true, @@ -232,7 +236,7 @@ export const testTriggerAndWait = task({ const processed: boolean = result.output.processed; const userId: string = result.output.userId; const userName: string = result.output.userName; - + return { success: true, processedUserId: userId, @@ -253,14 +257,16 @@ export const testUnwrap = task({ run: async (_, { ctx }) => { try { // Using unwrap() for cleaner code - const output = await zodSchemaTask.triggerAndWait({ - id: "user789", - name: "Bob Johnson", - email: "bob@example.com", - age: 35, - isActive: true, - roles: ["user"], - }).unwrap(); + const output = await zodSchemaTask + .triggerAndWait({ + id: "user789", + name: "Bob Johnson", + email: "bob@example.com", + age: 35, + isActive: true, + roles: ["user"], + }) + .unwrap(); // output is directly typed without needing to check result.ok const processed: boolean = output.processed; From 580f95a464212528105a2b1d56101b751be06130 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Wed, 30 Jul 2025 11:47:10 +0100 Subject: [PATCH 13/19] we now convert schema to jsonSchema on the CLI side via the indexing --- .../services/createBackgroundWorker.server.ts | 4 ++-- ...eateDeploymentBackgroundWorkerV3.server.ts | 2 +- ...eateDeploymentBackgroundWorkerV4.server.ts | 2 +- packages/cli-v3/package.json | 1 + .../src/entryPoints/dev-index-worker.ts | 20 ++++++++++++++++++- .../src/entryPoints/managed-index-worker.ts | 20 ++++++++++++++++++- .../core/src/v3/resource-catalog/catalog.ts | 3 ++- .../core/src/v3/resource-catalog/index.ts | 6 +++++- .../resource-catalog/noopResourceCatalog.ts | 6 +++++- .../standardResourceCatalog.ts | 17 +++++++++++++--- packages/core/src/v3/schemas/schemas.ts | 1 - packages/core/src/v3/types/tasks.ts | 1 + packages/trigger-sdk/package.json | 1 - packages/trigger-sdk/src/v3/shared.ts | 10 +--------- pnpm-lock.yaml | 6 +++--- references/hello-world/src/trigger/example.ts | 2 +- 16 files changed, 75 insertions(+), 27 deletions(-) diff --git a/apps/webapp/app/v3/services/createBackgroundWorker.server.ts b/apps/webapp/app/v3/services/createBackgroundWorker.server.ts index f1aad74fe5..f7489879ea 100644 --- a/apps/webapp/app/v3/services/createBackgroundWorker.server.ts +++ b/apps/webapp/app/v3/services/createBackgroundWorker.server.ts @@ -77,7 +77,7 @@ export class CreateBackgroundWorkerService extends BaseService { version: nextVersion, runtimeEnvironmentId: environment.id, projectId: project.id, - metadata: body.metadata, + metadata: body.metadata as any, contentHash: body.metadata.contentHash, cliVersion: body.metadata.cliPackageVersion, sdkVersion: body.metadata.packageVersion, @@ -280,7 +280,7 @@ async function createWorkerTask( fileId: tasksToBackgroundFiles?.get(task.id) ?? null, maxDurationInSeconds: task.maxDuration ? clampMaxDuration(task.maxDuration) : null, queueId: queue.id, - payloadSchema: task.payloadSchema, + payloadSchema: task.payloadSchema as any, }, }); } catch (error) { diff --git a/apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV3.server.ts b/apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV3.server.ts index 76be016528..9ac42478aa 100644 --- a/apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV3.server.ts +++ b/apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV3.server.ts @@ -48,7 +48,7 @@ export class CreateDeploymentBackgroundWorkerServiceV3 extends BaseService { version: deployment.version, runtimeEnvironmentId: environment.id, projectId: environment.projectId, - metadata: body.metadata, + metadata: body.metadata as any, contentHash: body.metadata.contentHash, cliVersion: body.metadata.cliPackageVersion, sdkVersion: body.metadata.packageVersion, diff --git a/apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV4.server.ts b/apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV4.server.ts index 2fb32966de..d17d409536 100644 --- a/apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV4.server.ts +++ b/apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV4.server.ts @@ -65,7 +65,7 @@ export class CreateDeploymentBackgroundWorkerServiceV4 extends BaseService { version: deployment.version, runtimeEnvironmentId: environment.id, projectId: environment.projectId, - metadata: body.metadata, + metadata: body.metadata as any, contentHash: body.metadata.contentHash, cliVersion: body.metadata.cliPackageVersion, sdkVersion: body.metadata.packageVersion, diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json index 846c067598..7f27f9e2cc 100644 --- a/packages/cli-v3/package.json +++ b/packages/cli-v3/package.json @@ -95,6 +95,7 @@ "@opentelemetry/semantic-conventions": "1.25.1", "@trigger.dev/build": "workspace:4.0.0-v4-beta.25", "@trigger.dev/core": "workspace:4.0.0-v4-beta.25", + "@trigger.dev/schema-to-json": "workspace:4.0.0-v4-beta.25", "ansi-escapes": "^7.0.0", "braces": "^3.0.3", "c12": "^1.11.1", diff --git a/packages/cli-v3/src/entryPoints/dev-index-worker.ts b/packages/cli-v3/src/entryPoints/dev-index-worker.ts index d44ac53fe5..d47c01cbfc 100644 --- a/packages/cli-v3/src/entryPoints/dev-index-worker.ts +++ b/packages/cli-v3/src/entryPoints/dev-index-worker.ts @@ -18,6 +18,7 @@ import { registerResources } from "../indexing/registerResources.js"; import { env } from "std-env"; import { normalizeImportPath } from "../utilities/normalizeImportPath.js"; import { detectRuntimeVersion } from "@trigger.dev/core/v3/build"; +import { schemaToJsonSchema, initializeSchemaConverters } from "@trigger.dev/schema-to-json"; sourceMapSupport.install({ handleUncaughtExceptions: false, @@ -100,7 +101,7 @@ async function bootstrap() { const { buildManifest, importErrors, config, timings } = await bootstrap(); -let tasks = resourceCatalog.listTaskManifests(); +let tasks = await convertSchemasToJsonSchemas(resourceCatalog.listTaskManifests()); // If the config has retry defaults, we need to apply them to all tasks that don't have any retry settings if (config.retries?.default) { @@ -190,3 +191,20 @@ await new Promise((resolve) => { resolve(); }, 10); }); + +async function convertSchemasToJsonSchemas(tasks: TaskManifest[]): Promise { + await initializeSchemaConverters(); + + const convertedTasks = tasks.map((task) => { + const schema = resourceCatalog.getTaskSchema(task.id); + + if (schema) { + const result = schemaToJsonSchema(schema); + return { ...task, payloadSchema: result?.jsonSchema }; + } + + return task; + }); + + return convertedTasks; +} diff --git a/packages/cli-v3/src/entryPoints/managed-index-worker.ts b/packages/cli-v3/src/entryPoints/managed-index-worker.ts index 845ece47af..30f33433e0 100644 --- a/packages/cli-v3/src/entryPoints/managed-index-worker.ts +++ b/packages/cli-v3/src/entryPoints/managed-index-worker.ts @@ -18,6 +18,7 @@ import { registerResources } from "../indexing/registerResources.js"; import { env } from "std-env"; import { normalizeImportPath } from "../utilities/normalizeImportPath.js"; import { detectRuntimeVersion } from "@trigger.dev/core/v3/build"; +import { schemaToJsonSchema, initializeSchemaConverters } from "@trigger.dev/schema-to-json"; sourceMapSupport.install({ handleUncaughtExceptions: false, @@ -100,7 +101,7 @@ async function bootstrap() { const { buildManifest, importErrors, config, timings } = await bootstrap(); -let tasks = resourceCatalog.listTaskManifests(); +let tasks = await convertSchemasToJsonSchemas(resourceCatalog.listTaskManifests()); // If the config has retry defaults, we need to apply them to all tasks that don't have any retry settings if (config.retries?.default) { @@ -196,3 +197,20 @@ await new Promise((resolve) => { resolve(); }, 10); }); + +async function convertSchemasToJsonSchemas(tasks: TaskManifest[]): Promise { + await initializeSchemaConverters(); + + const convertedTasks = tasks.map((task) => { + const schema = resourceCatalog.getTaskSchema(task.id); + + if (schema) { + const result = schemaToJsonSchema(schema); + return { ...task, payloadSchema: result?.jsonSchema }; + } + + return task; + }); + + return convertedTasks; +} diff --git a/packages/core/src/v3/resource-catalog/catalog.ts b/packages/core/src/v3/resource-catalog/catalog.ts index 725899c0d1..9ad14dd848 100644 --- a/packages/core/src/v3/resource-catalog/catalog.ts +++ b/packages/core/src/v3/resource-catalog/catalog.ts @@ -1,5 +1,5 @@ import { QueueManifest, TaskManifest, WorkerManifest } from "../schemas/index.js"; -import { TaskMetadataWithFunctions } from "../types/index.js"; +import { TaskMetadataWithFunctions, TaskSchema } from "../types/index.js"; export interface ResourceCatalog { setCurrentFileContext(filePath: string, entryPoint: string): void; @@ -13,4 +13,5 @@ export interface ResourceCatalog { registerWorkerManifest(workerManifest: WorkerManifest): void; registerQueueMetadata(queue: QueueManifest): void; listQueueManifests(): Array; + getTaskSchema(id: string): TaskSchema | undefined; } diff --git a/packages/core/src/v3/resource-catalog/index.ts b/packages/core/src/v3/resource-catalog/index.ts index 6773f1b621..a564648fcc 100644 --- a/packages/core/src/v3/resource-catalog/index.ts +++ b/packages/core/src/v3/resource-catalog/index.ts @@ -1,7 +1,7 @@ const API_NAME = "resource-catalog"; import { QueueManifest, TaskManifest, WorkerManifest } from "../schemas/index.js"; -import { TaskMetadataWithFunctions } from "../types/index.js"; +import { TaskMetadataWithFunctions, TaskSchema } from "../types/index.js"; import { getGlobal, registerGlobal, unregisterGlobal } from "../utils/globals.js"; import { type ResourceCatalog } from "./catalog.js"; import { NoopResourceCatalog } from "./noopResourceCatalog.js"; @@ -65,6 +65,10 @@ export class ResourceCatalogAPI { return this.#getCatalog().getTask(id); } + public getTaskSchema(id: string): TaskSchema | undefined { + return this.#getCatalog().getTaskSchema(id); + } + public taskExists(id: string): boolean { return this.#getCatalog().taskExists(id); } diff --git a/packages/core/src/v3/resource-catalog/noopResourceCatalog.ts b/packages/core/src/v3/resource-catalog/noopResourceCatalog.ts index b0e0f73056..53a953393a 100644 --- a/packages/core/src/v3/resource-catalog/noopResourceCatalog.ts +++ b/packages/core/src/v3/resource-catalog/noopResourceCatalog.ts @@ -1,5 +1,5 @@ import { QueueManifest, TaskManifest, WorkerManifest } from "../schemas/index.js"; -import { TaskMetadataWithFunctions } from "../types/index.js"; +import { TaskMetadataWithFunctions, TaskSchema } from "../types/index.js"; import { ResourceCatalog } from "./catalog.js"; export class NoopResourceCatalog implements ResourceCatalog { @@ -31,6 +31,10 @@ export class NoopResourceCatalog implements ResourceCatalog { return undefined; } + getTaskSchema(id: string): TaskSchema | undefined { + return undefined; + } + taskExists(id: string): boolean { return false; } diff --git a/packages/core/src/v3/resource-catalog/standardResourceCatalog.ts b/packages/core/src/v3/resource-catalog/standardResourceCatalog.ts index 7468b63d80..3b8eaa7d67 100644 --- a/packages/core/src/v3/resource-catalog/standardResourceCatalog.ts +++ b/packages/core/src/v3/resource-catalog/standardResourceCatalog.ts @@ -5,10 +5,11 @@ import { WorkerManifest, QueueManifest, } from "../schemas/index.js"; -import { TaskMetadataWithFunctions } from "../types/index.js"; +import { TaskMetadataWithFunctions, TaskSchema } from "../types/index.js"; import { ResourceCatalog } from "./catalog.js"; export class StandardResourceCatalog implements ResourceCatalog { + private _taskSchemas: Map = new Map(); private _taskMetadata: Map = new Map(); private _taskFunctions: Map = new Map(); private _taskFileMetadata: Map = new Map(); @@ -72,6 +73,10 @@ export class StandardResourceCatalog implements ResourceCatalog { this._taskMetadata.set(task.id, metadata); this._taskFunctions.set(task.id, fns); + + if (task.schema) { + this._taskSchemas.set(task.id, task.schema); + } } updateTaskMetadata(id: string, updates: Partial): void { @@ -107,15 +112,21 @@ export class StandardResourceCatalog implements ResourceCatalog { continue; } - result.push({ + const taskManifest = { ...metadata, ...fileMetadata, - }); + }; + + result.push(taskManifest); } return result; } + getTaskSchema(id: string): TaskSchema | undefined { + return this._taskSchemas.get(id); + } + listQueueManifests(): Array { return Array.from(this._queueMetadata.values()); } diff --git a/packages/core/src/v3/schemas/schemas.ts b/packages/core/src/v3/schemas/schemas.ts index 721a7725a9..31bb410086 100644 --- a/packages/core/src/v3/schemas/schemas.ts +++ b/packages/core/src/v3/schemas/schemas.ts @@ -189,7 +189,6 @@ const taskMetadata = { triggerSource: z.string().optional(), schedule: ScheduleMetadata.optional(), maxDuration: z.number().optional(), - // JSONSchema type - using z.unknown() for runtime validation to accept JSONSchema7 payloadSchema: z.unknown().optional(), }; diff --git a/packages/core/src/v3/types/tasks.ts b/packages/core/src/v3/types/tasks.ts index acc322099a..66c9d98d5f 100644 --- a/packages/core/src/v3/types/tasks.ts +++ b/packages/core/src/v3/types/tasks.ts @@ -897,6 +897,7 @@ export type TaskMetadataWithFunctions = TaskMetadata & { onStart?: (payload: any, params: StartFnParams) => Promise; parsePayload?: AnySchemaParseFn; }; + schema?: TaskSchema; }; export type RunTypes = { diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index 519d5eb47a..4c64fc1131 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -53,7 +53,6 @@ "@opentelemetry/api-logs": "0.52.1", "@opentelemetry/semantic-conventions": "1.25.1", "@trigger.dev/core": "workspace:4.0.0-v4-beta.25", - "@trigger.dev/schema-to-json": "workspace:4.0.0-v4-beta.25", "chalk": "^5.2.0", "cronstrue": "^2.21.0", "debug": "^4.3.4", diff --git a/packages/trigger-sdk/src/v3/shared.ts b/packages/trigger-sdk/src/v3/shared.ts index 913b9487e0..05300dc4f9 100644 --- a/packages/trigger-sdk/src/v3/shared.ts +++ b/packages/trigger-sdk/src/v3/shared.ts @@ -28,17 +28,9 @@ import { TaskRunExecutionResult, TaskRunPromise, } from "@trigger.dev/core/v3"; -import { schemaToJsonSchema, initializeSchemaConverters } from "@trigger.dev/schema-to-json"; import { PollOptions, runs } from "./runs.js"; import { tracer } from "./tracer.js"; -// Initialize schema converters once when the module loads -// This happens automatically, users don't need to know about it -initializeSchemaConverters().catch(() => { - // Silently fail if converters can't be initialized - // Built-in conversions will still work -}); - import type { AnyOnCatchErrorHookFunction, AnyOnCleanupHookFunction, @@ -365,11 +357,11 @@ export function createSchemaTask< retry: params.retry ? { ...defaultRetryOptions, ...params.retry } : undefined, machine: typeof params.machine === "string" ? { preset: params.machine } : params.machine, maxDuration: params.maxDuration, - payloadSchema: params.schema ? schemaToJsonSchema(params.schema)?.jsonSchema : undefined, fns: { run: params.run, parsePayload, }, + schema: params.schema, }); const queue = params.queue; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10babd2cac..10abc36b93 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1314,6 +1314,9 @@ importers: '@trigger.dev/core': specifier: workspace:4.0.0-v4-beta.25 version: link:../core + '@trigger.dev/schema-to-json': + specifier: workspace:4.0.0-v4-beta.25 + version: link:../schema-to-json ansi-escapes: specifier: ^7.0.0 version: 7.0.0 @@ -1873,9 +1876,6 @@ importers: '@trigger.dev/core': specifier: workspace:4.0.0-v4-beta.25 version: link:../core - '@trigger.dev/schema-to-json': - specifier: workspace:4.0.0-v4-beta.25 - version: link:../schema-to-json chalk: specifier: ^5.2.0 version: 5.2.0 diff --git a/references/hello-world/src/trigger/example.ts b/references/hello-world/src/trigger/example.ts index 1eb7f18916..91827e145c 100644 --- a/references/hello-world/src/trigger/example.ts +++ b/references/hello-world/src/trigger/example.ts @@ -19,7 +19,7 @@ export const helloWorldTask = task({ env: process.env, }); - logger.debug("debug: Hello, world!", { payload }); + logger.debug("debug: Hello, worlds!", { payload }); logger.info("info: Hello, world!", { payload }); logger.log("log: Hello, world!", { payload }); logger.warn("warn: Hello, world!", { payload }); From ac8aaf566db90e3dbd20e2d318d4fc20b220864a Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Wed, 30 Jul 2025 13:45:13 +0100 Subject: [PATCH 14/19] Start MCP server --- packages/cli-v3/package.json | 2 +- packages/cli-v3/src/commands/mcp.ts | 62 ++++++++++++ packages/cli-v3/src/dev/mcpServer.ts | 2 +- pnpm-lock.yaml | 146 +++++++++++++++++++-------- 4 files changed, 169 insertions(+), 43 deletions(-) create mode 100644 packages/cli-v3/src/commands/mcp.ts diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json index 7f27f9e2cc..8d9c4ecbc3 100644 --- a/packages/cli-v3/package.json +++ b/packages/cli-v3/package.json @@ -80,7 +80,7 @@ "dependencies": { "@clack/prompts": "^0.10.0", "@depot/cli": "0.0.1-cli.2.80.0", - "@modelcontextprotocol/sdk": "^1.6.1", + "@modelcontextprotocol/sdk": "^1.17.0", "@opentelemetry/api": "1.9.0", "@opentelemetry/api-logs": "0.52.1", "@opentelemetry/exporter-logs-otlp-http": "0.52.1", diff --git a/packages/cli-v3/src/commands/mcp.ts b/packages/cli-v3/src/commands/mcp.ts new file mode 100644 index 0000000000..db34262383 --- /dev/null +++ b/packages/cli-v3/src/commands/mcp.ts @@ -0,0 +1,62 @@ +import { Command } from "commander"; +import { z } from "zod"; +import { CommonCommandOptions, commonOptions, wrapCommandAction } from "../cli/common.js"; +import { chalkError } from "../utilities/cliOutput.js"; +import { logger } from "../utilities/logger.js"; +import { login } from "./login.js"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; + +const McpCommandOptions = CommonCommandOptions.extend({ + config: z.string().optional(), + projectRef: z.string().optional(), + mcpPort: z.coerce.number().optional().default(3333), +}); + +export type McpCommandOptions = z.infer; + +export function configureMcpCommand(program: Command) { + return commonOptions( + program + .command("mcp") + .description("Run the MCP server") + .option("-p, --project-ref ", "The project ref to use") + .option("--mcp-port", "The port to run the MCP server on", "3333") + ).action(async (options) => { + wrapCommandAction("mcp", McpCommandOptions, options, async (opts) => { + await mcpCommand(opts); + }); + }); +} + +export async function mcpCommand(options: McpCommandOptions) { + const authorization = await login({ + embedded: true, + silent: true, + defaultApiUrl: options.apiUrl, + profile: options.profile, + }); + + if (!authorization.ok) { + if (authorization.error === "fetch failed") { + logger.log( + `${chalkError( + "X Error:" + )} Connecting to the server failed. Please check your internet connection or contact eric@trigger.dev for help.` + ); + } else { + logger.log( + `${chalkError("X Error:")} You must login first. Use the \`login\` CLI command.\n\n${ + authorization.error + }` + ); + } + process.exitCode = 1; + return; + } + + const server = new McpServer({ + name: "trigger.dev", + version: "1.0.0", + }); +} diff --git a/packages/cli-v3/src/dev/mcpServer.ts b/packages/cli-v3/src/dev/mcpServer.ts index 8c4e57da34..849e87f38b 100644 --- a/packages/cli-v3/src/dev/mcpServer.ts +++ b/packages/cli-v3/src/dev/mcpServer.ts @@ -27,7 +27,7 @@ const server = new McpServer({ // This could be a good fit for the `resource` entity in MCP. // Also, a custom `prompt` entity could be useful to instruct the LLM to prompt the user // for selecting a task from a list of matching tasks, when the confidence for an exact match is low. -server.tool("list-all-tasks", "List all available task IDs in the worker.", async () => { +server.tool("list-all-tasks", "List all available task IDs in the worker.", async (params) => { return { content: [ { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10abc36b93..f438d55569 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -304,7 +304,7 @@ importers: version: 0.52.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': specifier: 0.52.1 - version: 0.52.1(@opentelemetry/api@1.9.0)(supports-color@10.0.0) + version: 0.52.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-express': specifier: ^0.36.1 version: 0.36.1(@opentelemetry/api@1.9.0) @@ -322,7 +322,7 @@ importers: version: 1.25.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-node': specifier: 0.52.1 - version: 0.52.1(@opentelemetry/api@1.9.0)(supports-color@10.0.0) + version: 0.52.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': specifier: 1.25.1 version: 1.25.1(@opentelemetry/api@1.9.0) @@ -1270,8 +1270,8 @@ importers: specifier: 0.0.1-cli.2.80.0 version: 0.0.1-cli.2.80.0 '@modelcontextprotocol/sdk': - specifier: ^1.6.1 - version: 1.6.1(supports-color@10.0.0) + specifier: ^1.17.0 + version: 1.17.0(supports-color@10.0.0) '@opentelemetry/api': specifier: 1.9.0 version: 1.9.0 @@ -1542,7 +1542,7 @@ importers: version: 0.52.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': specifier: 0.52.1 - version: 0.52.1(@opentelemetry/api@1.9.0)(supports-color@10.0.0) + version: 0.52.1(@opentelemetry/api@1.9.0) '@opentelemetry/resources': specifier: 1.25.1 version: 1.25.1(@opentelemetry/api@1.9.0) @@ -1551,7 +1551,7 @@ importers: version: 0.52.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-node': specifier: 0.52.1 - version: 0.52.1(@opentelemetry/api@1.9.0)(supports-color@10.0.0) + version: 0.52.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': specifier: 1.25.1 version: 1.25.1(@opentelemetry/api@1.9.0) @@ -4926,7 +4926,7 @@ packages: '@babel/traverse': 7.24.7 '@babel/types': 7.24.0 convert-source-map: 1.9.0 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -5525,7 +5525,7 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.24.7 '@babel/types': 7.24.0 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -8065,7 +8065,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 espree: 9.6.0 globals: 13.19.0 ignore: 5.2.4 @@ -8442,7 +8442,7 @@ packages: engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 1.2.1 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -9347,19 +9347,22 @@ packages: - supports-color dev: true - /@modelcontextprotocol/sdk@1.6.1(supports-color@10.0.0): - resolution: {integrity: sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA==} + /@modelcontextprotocol/sdk@1.17.0(supports-color@10.0.0): + resolution: {integrity: sha512-qFfbWFA7r1Sd8D697L7GkTd36yqDuTkvz0KfOGkgXR8EUhQn3/EDNIR/qUdQNMT8IjmasBvHWuXeisxtXTQT2g==} engines: {node: '>=18'} dependencies: + ajv: 6.12.6 content-type: 1.0.5 cors: 2.8.5 + cross-spawn: 7.0.6 eventsource: 3.0.5 + eventsource-parser: 3.0.0 express: 5.0.1(supports-color@10.0.0) express-rate-limit: 7.5.0(express@5.0.1) - pkce-challenge: 4.1.0 + pkce-challenge: 5.0.0 raw-body: 3.0.0 - zod: 3.23.8 - zod-to-json-schema: 3.24.3(zod@3.23.8) + zod: 3.25.76 + zod-to-json-schema: 3.24.5(zod@3.25.76) transitivePeerDependencies: - supports-color dev: false @@ -10366,7 +10369,7 @@ packages: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0)(supports-color@10.0.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.25.1 semver: 7.6.3 transitivePeerDependencies: @@ -10591,7 +10594,7 @@ packages: '@opentelemetry/api-logs': 0.49.1 '@types/shimmer': 1.0.2 import-in-the-middle: 1.7.1 - require-in-the-middle: 7.1.1(supports-color@10.0.0) + require-in-the-middle: 7.1.1 semver: 7.6.3 shimmer: 1.2.1 transitivePeerDependencies: @@ -10608,7 +10611,7 @@ packages: '@opentelemetry/api-logs': 0.49.1 '@types/shimmer': 1.0.2 import-in-the-middle: 1.7.1 - require-in-the-middle: 7.1.1(supports-color@10.0.0) + require-in-the-middle: 7.1.1 semver: 7.6.3 shimmer: 1.2.1 transitivePeerDependencies: @@ -10625,7 +10628,7 @@ packages: '@opentelemetry/api-logs': 0.49.1 '@types/shimmer': 1.0.2 import-in-the-middle: 1.7.1 - require-in-the-middle: 7.1.1(supports-color@10.0.0) + require-in-the-middle: 7.1.1 semver: 7.6.3 shimmer: 1.2.1 transitivePeerDependencies: @@ -10642,7 +10645,7 @@ packages: '@opentelemetry/api-logs': 0.51.1 '@types/shimmer': 1.0.2 import-in-the-middle: 1.7.4 - require-in-the-middle: 7.1.1(supports-color@10.0.0) + require-in-the-middle: 7.1.1 semver: 7.6.3 shimmer: 1.2.1 transitivePeerDependencies: @@ -10659,7 +10662,24 @@ packages: '@opentelemetry/api-logs': 0.52.1 '@types/shimmer': 1.0.2 import-in-the-middle: 1.11.0 - require-in-the-middle: 7.1.1(supports-color@10.0.0) + require-in-the-middle: 7.1.1 + semver: 7.6.3 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + dev: false + + /@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0): + resolution: {integrity: sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.52.1 + '@types/shimmer': 1.0.2 + import-in-the-middle: 1.11.0 + require-in-the-middle: 7.1.1 semver: 7.6.3 shimmer: 1.2.1 transitivePeerDependencies: @@ -10693,7 +10713,7 @@ packages: '@opentelemetry/api-logs': 0.57.2 '@types/shimmer': 1.2.0 import-in-the-middle: 1.11.0 - require-in-the-middle: 7.1.1(supports-color@10.0.0) + require-in-the-middle: 7.1.1 semver: 7.7.2 shimmer: 1.2.1 transitivePeerDependencies: @@ -11057,6 +11077,30 @@ packages: - supports-color dev: true + /@opentelemetry/sdk-node@0.52.1(@opentelemetry/api@1.9.0): + resolution: {integrity: sha512-uEG+gtEr6eKd8CVWeKMhH2olcCHM9dEK68pe0qE0be32BcCRsvYURhHaD1Srngh1SQcnQzZ4TP324euxqtBOJA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.52.1 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-grpc': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-zipkin': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + transitivePeerDependencies: + - supports-color + dev: false + /@opentelemetry/sdk-node@0.52.1(@opentelemetry/api@1.9.0)(supports-color@10.0.0): resolution: {integrity: sha512-uEG+gtEr6eKd8CVWeKMhH2olcCHM9dEK68pe0qE0be32BcCRsvYURhHaD1Srngh1SQcnQzZ4TP324euxqtBOJA==} engines: {node: '>=14'} @@ -11436,7 +11480,7 @@ packages: '@opentelemetry/api': ^1.8 dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0)(supports-color@10.0.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color dev: false @@ -20531,7 +20575,7 @@ packages: '@typescript-eslint/scope-manager': 5.59.6 '@typescript-eslint/types': 5.59.6 '@typescript-eslint/typescript-estree': 5.59.6(typescript@5.8.3) - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 eslint: 8.31.0 typescript: 5.8.3 transitivePeerDependencies: @@ -20558,7 +20602,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 5.59.6(typescript@5.8.3) '@typescript-eslint/utils': 5.59.6(eslint@8.31.0)(typescript@5.8.3) - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 eslint: 8.31.0 tsutils: 3.21.0(typescript@5.8.3) typescript: 5.8.3 @@ -20582,7 +20626,7 @@ packages: dependencies: '@typescript-eslint/types': 5.59.6 '@typescript-eslint/visitor-keys': 5.59.6 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.3 @@ -20810,7 +20854,7 @@ packages: dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -23560,6 +23604,15 @@ packages: shebang-command: 2.0.0 which: 2.0.2 + /cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: false + /crypto-js@4.1.1: resolution: {integrity: sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==} dev: false @@ -23865,7 +23918,7 @@ packages: ms: 2.1.3 supports-color: 10.0.0 - /debug@4.4.0(supports-color@10.0.0): + /debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} peerDependencies: @@ -23875,7 +23928,6 @@ packages: optional: true dependencies: ms: 2.1.3 - supports-color: 10.0.0 /debug@4.4.1(supports-color@10.0.0): resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} @@ -24151,7 +24203,7 @@ packages: resolution: {integrity: sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==} engines: {node: '>= 8.0'} dependencies: - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 readable-stream: 3.6.0 split-ca: 1.0.1 ssh2: 1.16.0 @@ -25203,7 +25255,7 @@ packages: eslint: '*' eslint-plugin-import: '*' dependencies: - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 enhanced-resolve: 5.15.0 eslint: 8.31.0 eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.31.0) @@ -26881,7 +26933,7 @@ packages: '@types/node': 20.14.14 '@types/semver': 7.5.1 chalk: 4.1.2 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 interpret: 3.1.1 semver: 7.6.3 tslib: 2.6.2 @@ -27240,7 +27292,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -31758,8 +31810,8 @@ packages: resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} engines: {node: '>= 6'} - /pkce-challenge@4.1.0: - resolution: {integrity: sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==} + /pkce-challenge@5.0.0: + resolution: {integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==} engines: {node: '>=16.20.0'} dev: false @@ -33737,7 +33789,7 @@ packages: remix-auth: ^3.6.0 dependencies: '@remix-run/server-runtime': 2.1.0(typescript@5.8.3) - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 remix-auth: 3.6.0(@remix-run/react@2.1.0)(@remix-run/server-runtime@2.1.0) transitivePeerDependencies: - supports-color @@ -33860,15 +33912,26 @@ packages: engines: {node: '>=0.10.0'} dev: true + /require-in-the-middle@7.1.1: + resolution: {integrity: sha512-OScOjQjrrjhAdFpQmnkE/qbIBGCRFhQB/YaJhcC3CPOlmhe7llnW46Ac1J5+EjcNXOTnDdpF96Erw/yedsGksQ==} + engines: {node: '>=8.6.0'} + dependencies: + debug: 4.4.0 + module-details-from-path: 1.0.3 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + /require-in-the-middle@7.1.1(supports-color@10.0.0): resolution: {integrity: sha512-OScOjQjrrjhAdFpQmnkE/qbIBGCRFhQB/YaJhcC3CPOlmhe7llnW46Ac1J5+EjcNXOTnDdpF96Erw/yedsGksQ==} engines: {node: '>=8.6.0'} dependencies: - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.1(supports-color@10.0.0) module-details-from-path: 1.0.3 resolve: 1.22.8 transitivePeerDependencies: - supports-color + dev: false /require-like@0.1.2: resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} @@ -35388,7 +35451,7 @@ packages: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 fast-safe-stringify: 2.1.1 form-data: 4.0.4 formidable: 3.5.1 @@ -35937,7 +36000,7 @@ packages: archiver: 7.0.1 async-lock: 1.4.1 byline: 5.0.0 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 docker-compose: 0.24.8 dockerode: 4.0.6 get-port: 7.1.0 @@ -36459,7 +36522,7 @@ packages: cac: 6.7.14 chokidar: 4.0.3 consola: 3.4.2 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 esbuild: 0.25.1 joycon: 3.1.1 picocolors: 1.1.1 @@ -37796,7 +37859,7 @@ packages: '@vitest/spy': 3.1.4 '@vitest/utils': 3.1.4 chai: 5.2.0 - debug: 4.4.0(supports-color@10.0.0) + debug: 4.4.0 expect-type: 1.2.1 magic-string: 0.30.17 pathe: 2.0.3 @@ -38513,6 +38576,7 @@ packages: zod: ^3.24.1 dependencies: zod: 3.23.8 + dev: true /zod-to-json-schema@3.24.5(zod@3.23.8): resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} From 44ba23976262b6e0ad5ff6c7515e4070ffabf913 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Wed, 30 Jul 2025 15:33:31 +0100 Subject: [PATCH 15/19] Upgrade zod to 3.25.76 --- apps/supervisor/package.json | 2 +- apps/webapp/package.json | 2 +- internal-packages/clickhouse/package.json | 2 +- internal-packages/emails/package.json | 2 +- internal-packages/run-engine/package.json | 2 +- .../schedule-engine/package.json | 2 +- internal-packages/zod-worker/package.json | 2 +- packages/cli-v3/package.json | 2 +- packages/cli-v3/src/cli/index.ts | 18 +- packages/cli-v3/src/commands/mcp.ts | 38 +- packages/core/package.json | 2 +- packages/redis-worker/package.json | 2 +- packages/trigger-sdk/package.json | 2 +- pnpm-lock.yaml | 441 +++++++++--------- references/d3-chat/package.json | 2 +- references/d3-openai-agents/package.json | 2 +- references/hello-world/package.json | 2 +- references/nextjs-realtime/package.json | 2 +- references/python-catalog/package.json | 2 +- references/test-tasks/package.json | 2 +- references/v3-catalog/package.json | 2 +- 21 files changed, 266 insertions(+), 267 deletions(-) diff --git a/apps/supervisor/package.json b/apps/supervisor/package.json index ae36549272..9cce9d5feb 100644 --- a/apps/supervisor/package.json +++ b/apps/supervisor/package.json @@ -19,7 +19,7 @@ "prom-client": "^15.1.0", "socket.io": "4.7.4", "std-env": "^3.8.0", - "zod": "3.23.8" + "zod": "3.25.76" }, "devDependencies": { "@types/dockerode": "^3.3.33" diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 80b7c9614b..69473b78b9 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -203,7 +203,7 @@ "ulidx": "^2.2.1", "uuid": "^9.0.0", "ws": "^8.11.0", - "zod": "3.23.8", + "zod": "3.25.76", "zod-error": "1.5.0", "zod-validation-error": "^1.5.0" }, diff --git a/internal-packages/clickhouse/package.json b/internal-packages/clickhouse/package.json index d85051b506..efa7cffd12 100644 --- a/internal-packages/clickhouse/package.json +++ b/internal-packages/clickhouse/package.json @@ -9,7 +9,7 @@ "@clickhouse/client": "^1.11.1", "@internal/tracing": "workspace:*", "@trigger.dev/core": "workspace:*", - "zod": "3.23.8", + "zod": "3.25.76", "zod-error": "1.5.0" }, "devDependencies": { diff --git a/internal-packages/emails/package.json b/internal-packages/emails/package.json index 85cb7abe01..e5d35cb2ee 100644 --- a/internal-packages/emails/package.json +++ b/internal-packages/emails/package.json @@ -17,7 +17,7 @@ "react-email": "^2.1.1", "resend": "^3.2.0", "tiny-invariant": "^1.2.0", - "zod": "3.23.8" + "zod": "3.25.76" }, "devDependencies": { "@types/nodemailer": "^6.4.17", diff --git a/internal-packages/run-engine/package.json b/internal-packages/run-engine/package.json index 62f0837897..a12abc73e2 100644 --- a/internal-packages/run-engine/package.json +++ b/internal-packages/run-engine/package.json @@ -30,7 +30,7 @@ "nanoid": "3.3.8", "redlock": "5.0.0-beta.2", "seedrandom": "^3.0.5", - "zod": "3.23.8" + "zod": "3.25.76" }, "devDependencies": { "@internal/testcontainers": "workspace:*", diff --git a/internal-packages/schedule-engine/package.json b/internal-packages/schedule-engine/package.json index ecdf151247..86929a3934 100644 --- a/internal-packages/schedule-engine/package.json +++ b/internal-packages/schedule-engine/package.json @@ -22,7 +22,7 @@ "cron-parser": "^4.9.0", "cronstrue": "^2.50.0", "nanoid": "3.3.8", - "zod": "3.23.8" + "zod": "3.25.76" }, "devDependencies": { "@internal/testcontainers": "workspace:*", diff --git a/internal-packages/zod-worker/package.json b/internal-packages/zod-worker/package.json index 1d9cbad93c..352fa94529 100644 --- a/internal-packages/zod-worker/package.json +++ b/internal-packages/zod-worker/package.json @@ -10,7 +10,7 @@ "@trigger.dev/database": "workspace:*", "graphile-worker": "0.16.6", "lodash.omit": "^4.5.0", - "zod": "3.23.8" + "zod": "3.25.76" }, "devDependencies": { "@types/lodash.omit": "^4.5.7", diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json index 8d9c4ecbc3..c539bc70c3 100644 --- a/packages/cli-v3/package.json +++ b/packages/cli-v3/package.json @@ -139,7 +139,7 @@ "tinyglobby": "^0.2.10", "ws": "^8.18.0", "xdg-app-paths": "^8.3.0", - "zod": "3.23.8", + "zod": "3.25.76", "zod-validation-error": "^1.5.0" }, "engines": { diff --git a/packages/cli-v3/src/cli/index.ts b/packages/cli-v3/src/cli/index.ts index 4a575831a5..d74686c983 100644 --- a/packages/cli-v3/src/cli/index.ts +++ b/packages/cli-v3/src/cli/index.ts @@ -1,21 +1,20 @@ import { Command } from "commander"; +import { configureAnalyzeCommand } from "../commands/analyze.js"; +import { configureDeployCommand } from "../commands/deploy.js"; import { configureDevCommand } from "../commands/dev.js"; import { configureInitCommand } from "../commands/init.js"; +import { configureListProfilesCommand } from "../commands/list-profiles.js"; import { configureLoginCommand } from "../commands/login.js"; import { configureLogoutCommand } from "../commands/logout.js"; +import { configurePreviewCommand } from "../commands/preview.js"; +import { configurePromoteCommand } from "../commands/promote.js"; +import { configureSwitchProfilesCommand } from "../commands/switch.js"; +import { configureUpdateCommand } from "../commands/update.js"; import { configureWhoamiCommand } from "../commands/whoami.js"; +import { configureMcpCommand } from "../commands/mcp.js"; import { COMMAND_NAME } from "../consts.js"; -import { configureListProfilesCommand } from "../commands/list-profiles.js"; -import { configureAnalyzeCommand } from "../commands/analyze.js"; -import { configureUpdateCommand } from "../commands/update.js"; import { VERSION } from "../version.js"; -import { configureDeployCommand } from "../commands/deploy.js"; import { installExitHandler } from "./common.js"; -import { configureWorkersCommand } from "../commands/workers/index.js"; -import { configureSwitchProfilesCommand } from "../commands/switch.js"; -import { configureTriggerTaskCommand } from "../commands/trigger.js"; -import { configurePromoteCommand } from "../commands/promote.js"; -import { configurePreviewCommand } from "../commands/preview.js"; export const program = new Command(); @@ -24,6 +23,7 @@ program .description("Create, run locally and deploy Trigger.dev background tasks.") .version(VERSION, "-v, --version", "Display the version number"); +configureMcpCommand(program); configureLoginCommand(program); configureInitCommand(program); configureDevCommand(program); diff --git a/packages/cli-v3/src/commands/mcp.ts b/packages/cli-v3/src/commands/mcp.ts index db34262383..1452574ed8 100644 --- a/packages/cli-v3/src/commands/mcp.ts +++ b/packages/cli-v3/src/commands/mcp.ts @@ -5,10 +5,9 @@ import { chalkError } from "../utilities/cliOutput.js"; import { logger } from "../utilities/logger.js"; import { login } from "./login.js"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; const McpCommandOptions = CommonCommandOptions.extend({ - config: z.string().optional(), projectRef: z.string().optional(), mcpPort: z.coerce.number().optional().default(3333), }); @@ -21,7 +20,7 @@ export function configureMcpCommand(program: Command) { .command("mcp") .description("Run the MCP server") .option("-p, --project-ref ", "The project ref to use") - .option("--mcp-port", "The port to run the MCP server on", "3333") + .option("-m, --mcp-port ", "The port to run the MCP server on", "3333") ).action(async (options) => { wrapCommandAction("mcp", McpCommandOptions, options, async (opts) => { await mcpCommand(opts); @@ -38,19 +37,6 @@ export async function mcpCommand(options: McpCommandOptions) { }); if (!authorization.ok) { - if (authorization.error === "fetch failed") { - logger.log( - `${chalkError( - "X Error:" - )} Connecting to the server failed. Please check your internet connection or contact eric@trigger.dev for help.` - ); - } else { - logger.log( - `${chalkError("X Error:")} You must login first. Use the \`login\` CLI command.\n\n${ - authorization.error - }` - ); - } process.exitCode = 1; return; } @@ -59,4 +45,24 @@ export async function mcpCommand(options: McpCommandOptions) { name: "trigger.dev", version: "1.0.0", }); + + server.registerTool( + "get_project_details", + { + title: "Get Project Details", + description: "Get the details of the project", + inputSchema: { + cwd: z.string().describe("The current working directory the user is in"), + }, + }, + async ({ cwd }) => { + return { + content: [{ type: "text", text: `Current working directory: ${cwd}` }], + }; + } + ); + + // Start receiving messages on stdin and sending messages on stdout + const transport = new StdioServerTransport(); + await server.connect(transport); } diff --git a/packages/core/package.json b/packages/core/package.json index 42c76333a4..4a7bbeb808 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -197,7 +197,7 @@ "superjson": "^2.2.1", "tinyexec": "^0.3.2", "uncrypto": "^0.1.3", - "zod": "3.23.8", + "zod": "3.25.76", "zod-error": "1.5.0", "zod-validation-error": "^1.5.0" }, diff --git a/packages/redis-worker/package.json b/packages/redis-worker/package.json index 3698cb76e3..ff238300b6 100644 --- a/packages/redis-worker/package.json +++ b/packages/redis-worker/package.json @@ -27,7 +27,7 @@ "lodash.omit": "^4.5.0", "nanoid": "^5.0.7", "p-limit": "^6.2.0", - "zod": "3.23.8", + "zod": "3.25.76", "cron-parser": "^4.9.0" }, "devDependencies": { diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index 4c64fc1131..b3227c48e3 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -75,7 +75,7 @@ "tshy": "^3.0.2", "tsx": "4.17.0", "typed-emitter": "^2.1.0", - "zod": "3.23.8" + "zod": "3.25.76" }, "peerDependencies": { "zod": "^3.0.0 || ^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f438d55569..2a640e040f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -182,8 +182,8 @@ importers: specifier: ^3.8.0 version: 3.8.1 zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@types/dockerode': specifier: ^3.3.33 @@ -193,7 +193,7 @@ importers: dependencies: '@ai-sdk/openai': specifier: ^1.3.23 - version: 1.3.23(zod@3.23.8) + version: 1.3.23(zod@3.25.76) '@ariakit/react': specifier: ^0.4.6 version: 0.4.6(react-dom@18.2.0)(react@18.2.0) @@ -244,7 +244,7 @@ importers: version: 0.9.2(react@18.2.0) '@conform-to/zod': specifier: 0.9.2 - version: 0.9.2(@conform-to/dom@0.9.2)(zod@3.23.8) + version: 0.9.2(@conform-to/dom@0.9.2)(zod@3.25.76) '@depot/cli': specifier: 0.0.1-cli.2.80.0 version: 0.0.1-cli.2.80.0 @@ -469,7 +469,7 @@ importers: version: 0.9.14 ai: specifier: ^4.3.19 - version: 4.3.19(react@18.2.0)(zod@3.23.8) + version: 4.3.19(react@18.2.0)(zod@3.25.76) assert-never: specifier: ^1.2.1 version: 1.2.1 @@ -652,7 +652,7 @@ importers: version: 0.3.1(@remix-run/react@2.1.0)(@remix-run/server-runtime@2.1.0)(react@18.2.0) remix-utils: specifier: ^7.7.0 - version: 7.7.0(@remix-run/node@2.1.0)(@remix-run/react@2.1.0)(@remix-run/router@1.15.3)(intl-parse-accept-language@1.0.0)(react@18.2.0)(zod@3.23.8) + version: 7.7.0(@remix-run/node@2.1.0)(@remix-run/react@2.1.0)(@remix-run/router@1.15.3)(intl-parse-accept-language@1.0.0)(react@18.2.0)(zod@3.25.76) seedrandom: specifier: ^3.0.5 version: 3.0.5 @@ -711,14 +711,14 @@ importers: specifier: ^8.11.0 version: 8.12.0 zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 zod-error: specifier: 1.5.0 version: 1.5.0 zod-validation-error: specifier: ^1.5.0 - version: 1.5.0(zod@3.23.8) + version: 1.5.0(zod@3.25.76) devDependencies: '@internal/clickhouse': specifier: workspace:* @@ -951,8 +951,8 @@ importers: specifier: workspace:* version: link:../../packages/core zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 zod-error: specifier: 1.5.0 version: 1.5.0 @@ -1004,8 +1004,8 @@ importers: specifier: ^1.2.0 version: 1.3.1 zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@types/nodemailer': specifier: ^6.4.17 @@ -1103,8 +1103,8 @@ importers: specifier: ^3.0.5 version: 3.0.5 zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@internal/testcontainers': specifier: workspace:* @@ -1143,8 +1143,8 @@ importers: specifier: 3.3.8 version: 3.3.8 zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@internal/testcontainers': specifier: workspace:* @@ -1220,8 +1220,8 @@ importers: specifier: ^4.5.0 version: 4.5.0 zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@types/lodash.omit': specifier: ^4.5.7 @@ -1447,11 +1447,11 @@ importers: specifier: ^8.3.0 version: 8.3.0 zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 zod-validation-error: specifier: ^1.5.0 - version: 1.5.0(zod@3.23.8) + version: 1.5.0(zod@3.25.76) devDependencies: '@epic-web/test-server': specifier: ^0.1.0 @@ -1607,18 +1607,18 @@ importers: specifier: ^0.1.3 version: 0.1.3 zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 zod-error: specifier: 1.5.0 version: 1.5.0 zod-validation-error: specifier: ^1.5.0 - version: 1.5.0(zod@3.23.8) + version: 1.5.0(zod@3.25.76) devDependencies: '@ai-sdk/provider-utils': specifier: ^1.0.22 - version: 1.0.22(zod@3.23.8) + version: 1.0.22(zod@3.25.76) '@arethetypeswrong/cli': specifier: ^0.15.4 version: 0.15.4 @@ -1639,7 +1639,7 @@ importers: version: 4.0.14 ai: specifier: ^3.4.33 - version: 3.4.33(react@18.3.1)(svelte@5.33.14)(vue@3.5.16)(zod@3.23.8) + version: 3.4.33(react@18.3.1)(svelte@5.33.14)(vue@3.5.16)(zod@3.25.76) defu: specifier: ^6.1.4 version: 6.1.4 @@ -1748,8 +1748,8 @@ importers: specifier: ^6.2.0 version: 6.2.0 zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@internal/redis': specifier: workspace:* @@ -1921,7 +1921,7 @@ importers: version: 8.5.4 ai: specifier: ^4.2.0 - version: 4.2.5(react@18.3.1)(zod@3.23.8) + version: 4.2.5(react@18.3.1)(zod@3.25.76) encoding: specifier: ^0.1.13 version: 0.1.13 @@ -1938,8 +1938,8 @@ importers: specifier: ^2.1.0 version: 2.1.0 zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 references/bun-catalog: dependencies: @@ -1958,10 +1958,10 @@ importers: dependencies: '@ai-sdk/anthropic': specifier: ^1.2.4 - version: 1.2.4(zod@3.23.8) + version: 1.2.4(zod@3.25.76) '@ai-sdk/openai': specifier: 1.3.3 - version: 1.3.3(zod@3.23.8) + version: 1.3.3(zod@3.25.76) '@e2b/code-interpreter': specifier: ^1.1.0 version: 1.1.0 @@ -1991,7 +1991,7 @@ importers: version: 0.10.0 ai: specifier: 4.2.5 - version: 4.2.5(react@19.0.0)(zod@3.23.8) + version: 4.2.5(react@19.0.0)(zod@3.25.76) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -2026,8 +2026,8 @@ importers: specifier: ^1.2.4 version: 1.2.4 zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@tailwindcss/postcss': specifier: ^4 @@ -2064,7 +2064,7 @@ importers: dependencies: '@ai-sdk/openai': specifier: 1.3.3 - version: 1.3.3(zod@3.23.8) + version: 1.3.3(zod@3.25.76) '@slack/web-api': specifier: 7.9.1 version: 7.9.1 @@ -2082,7 +2082,7 @@ importers: version: 0.10.0 ai: specifier: 4.2.5 - version: 4.2.5(react@19.0.0)(zod@3.23.8) + version: 4.2.5(react@19.0.0)(zod@3.25.76) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -2111,11 +2111,11 @@ importers: specifier: ^1.2.4 version: 1.2.4 zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 zod-to-json-schema: specifier: ^3.24.5 - version: 3.24.5(zod@3.23.8) + version: 3.24.5(zod@3.25.76) devDependencies: '@tailwindcss/postcss': specifier: ^4 @@ -2180,7 +2180,7 @@ importers: version: 2.1.20 openai: specifier: ^4.97.0 - version: 4.97.0(ws@8.12.0)(zod@3.23.8) + version: 4.97.0(ws@8.12.0)(zod@3.25.76) puppeteer-core: specifier: ^24.15.0 version: 24.15.0 @@ -2191,8 +2191,8 @@ importers: specifier: ^1.6.1 version: 1.6.1 zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 devDependencies: trigger.dev: specifier: workspace:* @@ -2254,7 +2254,7 @@ importers: dependencies: '@ai-sdk/openai': specifier: ^1.0.1 - version: 1.0.1(zod@3.23.8) + version: 1.0.1(zod@3.25.76) '@fal-ai/serverless-client': specifier: ^0.15.0 version: 0.15.0 @@ -2293,7 +2293,7 @@ importers: version: 7.0.3(next@14.2.21)(react@18.3.1)(uploadthing@7.1.0) ai: specifier: ^4.0.0 - version: 4.0.0(react@18.3.1)(zod@3.23.8) + version: 4.0.0(react@18.3.1)(zod@3.25.76) class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -2314,7 +2314,7 @@ importers: version: 14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.37.0)(react-dom@18.2.0)(react@18.3.1) openai: specifier: ^4.68.4 - version: 4.68.4(zod@3.23.8) + version: 4.68.4(zod@3.25.76) react: specifier: ^18 version: 18.3.1 @@ -2331,8 +2331,8 @@ importers: specifier: ^7.1.0 version: 7.1.0(next@14.2.21)(tailwindcss@3.4.1) zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@next/bundle-analyzer': specifier: ^15.0.2 @@ -2365,8 +2365,8 @@ importers: specifier: workspace:* version: link:../../packages/trigger-sdk zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@trigger.dev/build': specifier: workspace:* @@ -2384,8 +2384,8 @@ importers: specifier: workspace:* version: link:../../packages/trigger-sdk zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@trigger.dev/build': specifier: workspace:* @@ -2431,10 +2431,10 @@ importers: version: 2.2.1 '@t3-oss/env-core': specifier: ^0.11.0 - version: 0.11.0(typescript@5.8.3)(zod@3.23.8) + version: 0.11.0(typescript@5.8.3)(zod@3.25.76) '@t3-oss/env-nextjs': specifier: ^0.10.1 - version: 0.10.1(typescript@5.8.3)(zod@3.23.8) + version: 0.10.1(typescript@5.8.3)(zod@3.25.76) '@traceloop/instrumentation-openai': specifier: ^0.10.0 version: 0.10.0(@opentelemetry/api@1.4.1) @@ -2446,7 +2446,7 @@ importers: version: 0.14.0(@sinclair/typebox@0.33.17) ai: specifier: ^3.3.24 - version: 3.3.24(openai@4.56.0)(react@19.0.0-rc.0)(svelte@5.33.14)(vue@3.5.16)(zod@3.23.8) + version: 3.3.24(openai@4.56.0)(react@19.0.0-rc.0)(svelte@5.33.14)(vue@3.5.16)(zod@3.25.76) arktype: specifier: 2.0.0-rc.17 version: 2.0.0-rc.17 @@ -2482,7 +2482,7 @@ importers: version: 1.3.6 openai: specifier: ^4.47.0 - version: 4.56.0(zod@3.23.8) + version: 4.56.0(zod@3.25.76) pg: specifier: ^8.11.5 version: 8.11.5 @@ -2538,8 +2538,8 @@ importers: specifier: ^0.0.11 version: 0.0.11 zod: - specifier: 3.23.8 - version: 3.23.8 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@opentelemetry/core': specifier: ^1.22.0 @@ -2636,51 +2636,51 @@ packages: resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} dev: false - /@ai-sdk/anthropic@1.2.4(zod@3.23.8): + /@ai-sdk/anthropic@1.2.4(zod@3.25.76): resolution: {integrity: sha512-dAN6MXvLffeFVAr2gz3RGvOTgX1KL/Yn5q1l4/Dt0TUeDjQgCt4AbbYxZZB2qIAYzQvoyAFPhlw0sB3nNizG/g==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 dependencies: '@ai-sdk/provider': 1.1.0 - '@ai-sdk/provider-utils': 2.2.3(zod@3.23.8) - zod: 3.23.8 + '@ai-sdk/provider-utils': 2.2.3(zod@3.25.76) + zod: 3.25.76 dev: false - /@ai-sdk/openai@1.0.1(zod@3.23.8): + /@ai-sdk/openai@1.0.1(zod@3.25.76): resolution: {integrity: sha512-snZge8457afWlosVNUn+BG60MrxAPOOm3zmIMxJZih8tneNSiRbTVCbSzAtq/9vsnOHDe5RR83PRl85juOYEnA==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 dependencies: '@ai-sdk/provider': 1.0.0 - '@ai-sdk/provider-utils': 2.0.0(zod@3.23.8) - zod: 3.23.8 + '@ai-sdk/provider-utils': 2.0.0(zod@3.25.76) + zod: 3.25.76 dev: false - /@ai-sdk/openai@1.3.23(zod@3.23.8): + /@ai-sdk/openai@1.3.23(zod@3.25.76): resolution: {integrity: sha512-86U7rFp8yacUAOE/Jz8WbGcwMCqWvjK33wk5DXkfnAOEn3mx2r7tNSJdjukQFZbAK97VMXGPPHxF+aEARDXRXQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 dependencies: '@ai-sdk/provider': 1.1.3 - '@ai-sdk/provider-utils': 2.2.8(zod@3.23.8) - zod: 3.23.8 + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + zod: 3.25.76 dev: false - /@ai-sdk/openai@1.3.3(zod@3.23.8): + /@ai-sdk/openai@1.3.3(zod@3.25.76): resolution: {integrity: sha512-CH57tonLB4DwkwqwnMmTCoIOR7cNW3bP5ciyloI7rBGJS/Bolemsoo+vn5YnwkyT9O1diWJyvYeTh7A4UfiYOw==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 dependencies: '@ai-sdk/provider': 1.1.0 - '@ai-sdk/provider-utils': 2.2.1(zod@3.23.8) - zod: 3.23.8 + '@ai-sdk/provider-utils': 2.2.1(zod@3.25.76) + zod: 3.25.76 dev: false - /@ai-sdk/provider-utils@1.0.17(zod@3.23.8): + /@ai-sdk/provider-utils@1.0.17(zod@3.25.76): resolution: {integrity: sha512-2VyeTH5DQ6AxqvwdyytKIeiZyYTyJffpufWjE67zM2sXMIHgYl7fivo8m5wVl6Cbf1dFPSGKq//C9s+lz+NHrQ==} engines: {node: '>=18'} peerDependencies: @@ -2693,10 +2693,10 @@ packages: eventsource-parser: 1.1.2 nanoid: 3.3.6 secure-json-parse: 2.7.0 - zod: 3.23.8 + zod: 3.25.76 dev: false - /@ai-sdk/provider-utils@1.0.22(zod@3.23.8): + /@ai-sdk/provider-utils@1.0.22(zod@3.25.76): resolution: {integrity: sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==} engines: {node: '>=18'} peerDependencies: @@ -2709,10 +2709,10 @@ packages: eventsource-parser: 1.1.2 nanoid: 3.3.8 secure-json-parse: 2.7.0 - zod: 3.23.8 + zod: 3.25.76 dev: true - /@ai-sdk/provider-utils@2.0.0(zod@3.23.8): + /@ai-sdk/provider-utils@2.0.0(zod@3.25.76): resolution: {integrity: sha512-uITgVJByhtzuQU2ZW+2CidWRmQqTUTp6KADevy+4aRnmILZxY2LCt+UZ/ZtjJqq0MffwkuQPPY21ExmFAQ6kKA==} engines: {node: '>=18'} peerDependencies: @@ -2725,10 +2725,10 @@ packages: eventsource-parser: 3.0.0 nanoid: 5.1.5 secure-json-parse: 2.7.0 - zod: 3.23.8 + zod: 3.25.76 dev: false - /@ai-sdk/provider-utils@2.2.1(zod@3.23.8): + /@ai-sdk/provider-utils@2.2.1(zod@3.25.76): resolution: {integrity: sha512-BuExLp+NcpwsAVj1F4bgJuQkSqO/+roV9wM7RdIO+NVrcT8RBUTdXzf5arHt5T58VpK7bZyB2V9qigjaPHE+Dg==} engines: {node: '>=18'} peerDependencies: @@ -2737,9 +2737,9 @@ packages: '@ai-sdk/provider': 1.1.0 nanoid: 3.3.8 secure-json-parse: 2.7.0 - zod: 3.23.8 + zod: 3.25.76 - /@ai-sdk/provider-utils@2.2.3(zod@3.23.8): + /@ai-sdk/provider-utils@2.2.3(zod@3.25.76): resolution: {integrity: sha512-o3fWTzkxzI5Af7U7y794MZkYNEsxbjLam2nxyoUZSScqkacb7vZ3EYHLh21+xCcSSzEC161C7pZAGHtC0hTUMw==} engines: {node: '>=18'} peerDependencies: @@ -2748,10 +2748,10 @@ packages: '@ai-sdk/provider': 1.1.0 nanoid: 3.3.8 secure-json-parse: 2.7.0 - zod: 3.23.8 + zod: 3.25.76 dev: false - /@ai-sdk/provider-utils@2.2.8(zod@3.23.8): + /@ai-sdk/provider-utils@2.2.8(zod@3.25.76): resolution: {integrity: sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==} engines: {node: '>=18'} peerDependencies: @@ -2760,7 +2760,7 @@ packages: '@ai-sdk/provider': 1.1.3 nanoid: 3.3.8 secure-json-parse: 2.7.0 - zod: 3.23.8 + zod: 3.25.76 dev: false /@ai-sdk/provider@0.0.22: @@ -2797,7 +2797,7 @@ packages: json-schema: 0.4.0 dev: false - /@ai-sdk/react@0.0.53(react@19.0.0-rc.0)(zod@3.23.8): + /@ai-sdk/react@0.0.53(react@19.0.0-rc.0)(zod@3.25.76): resolution: {integrity: sha512-sIsmTFoR/QHvUUkltmHwP4bPjwy2vko6j/Nj8ayxLhEHs04Ug+dwXQyfA7MwgimEE3BcDQpWL8ikVj0m3ZILWQ==} engines: {node: '>=18'} peerDependencies: @@ -2809,14 +2809,14 @@ packages: zod: optional: true dependencies: - '@ai-sdk/provider-utils': 1.0.17(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.40(zod@3.23.8) + '@ai-sdk/provider-utils': 1.0.17(zod@3.25.76) + '@ai-sdk/ui-utils': 0.0.40(zod@3.25.76) react: 19.0.0-rc.0 swr: 2.2.5(react@19.0.0-rc.0) - zod: 3.23.8 + zod: 3.25.76 dev: false - /@ai-sdk/react@0.0.70(react@18.3.1)(zod@3.23.8): + /@ai-sdk/react@0.0.70(react@18.3.1)(zod@3.25.76): resolution: {integrity: sha512-GnwbtjW4/4z7MleLiW+TOZC2M29eCg1tOUpuEiYFMmFNZK8mkrqM0PFZMo6UsYeUYMWqEOOcPOU9OQVJMJh7IQ==} engines: {node: '>=18'} peerDependencies: @@ -2828,15 +2828,15 @@ packages: zod: optional: true dependencies: - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) + '@ai-sdk/provider-utils': 1.0.22(zod@3.25.76) + '@ai-sdk/ui-utils': 0.0.50(zod@3.25.76) react: 18.3.1 swr: 2.2.5(react@18.3.1) throttleit: 2.1.0 - zod: 3.23.8 + zod: 3.25.76 dev: true - /@ai-sdk/react@1.0.0(react@18.3.1)(zod@3.23.8): + /@ai-sdk/react@1.0.0(react@18.3.1)(zod@3.25.76): resolution: {integrity: sha512-BDrZqQA07Btg64JCuhFvBgYV+tt2B8cXINzEqWknGoxqcwgdE8wSLG2gkXoLzyC2Rnj7oj0HHpOhLUxDCmoKZg==} engines: {node: '>=18'} peerDependencies: @@ -2848,15 +2848,15 @@ packages: zod: optional: true dependencies: - '@ai-sdk/provider-utils': 2.0.0(zod@3.23.8) - '@ai-sdk/ui-utils': 1.0.0(zod@3.23.8) + '@ai-sdk/provider-utils': 2.0.0(zod@3.25.76) + '@ai-sdk/ui-utils': 1.0.0(zod@3.25.76) react: 18.3.1 swr: 2.2.5(react@18.3.1) throttleit: 2.1.0 - zod: 3.23.8 + zod: 3.25.76 dev: false - /@ai-sdk/react@1.2.12(react@18.2.0)(zod@3.23.8): + /@ai-sdk/react@1.2.12(react@18.2.0)(zod@3.25.76): resolution: {integrity: sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==} engines: {node: '>=18'} peerDependencies: @@ -2866,15 +2866,15 @@ packages: zod: optional: true dependencies: - '@ai-sdk/provider-utils': 2.2.8(zod@3.23.8) - '@ai-sdk/ui-utils': 1.2.11(zod@3.23.8) + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + '@ai-sdk/ui-utils': 1.2.11(zod@3.25.76) react: 18.2.0 swr: 2.2.5(react@18.2.0) throttleit: 2.1.0 - zod: 3.23.8 + zod: 3.25.76 dev: false - /@ai-sdk/react@1.2.2(react@18.3.1)(zod@3.23.8): + /@ai-sdk/react@1.2.2(react@18.3.1)(zod@3.25.76): resolution: {integrity: sha512-rxyNTFjUd3IilVOJFuUJV5ytZBYAIyRi50kFS2gNmSEiG4NHMBBm31ddrxI/i86VpY8gzZVp1/igtljnWBihUA==} engines: {node: '>=18'} peerDependencies: @@ -2884,15 +2884,15 @@ packages: zod: optional: true dependencies: - '@ai-sdk/provider-utils': 2.2.1(zod@3.23.8) - '@ai-sdk/ui-utils': 1.2.1(zod@3.23.8) + '@ai-sdk/provider-utils': 2.2.1(zod@3.25.76) + '@ai-sdk/ui-utils': 1.2.1(zod@3.25.76) react: 18.3.1 swr: 2.2.5(react@18.3.1) throttleit: 2.1.0 - zod: 3.23.8 + zod: 3.25.76 dev: true - /@ai-sdk/react@1.2.2(react@19.0.0)(zod@3.23.8): + /@ai-sdk/react@1.2.2(react@19.0.0)(zod@3.25.76): resolution: {integrity: sha512-rxyNTFjUd3IilVOJFuUJV5ytZBYAIyRi50kFS2gNmSEiG4NHMBBm31ddrxI/i86VpY8gzZVp1/igtljnWBihUA==} engines: {node: '>=18'} peerDependencies: @@ -2902,15 +2902,15 @@ packages: zod: optional: true dependencies: - '@ai-sdk/provider-utils': 2.2.1(zod@3.23.8) - '@ai-sdk/ui-utils': 1.2.1(zod@3.23.8) + '@ai-sdk/provider-utils': 2.2.1(zod@3.25.76) + '@ai-sdk/ui-utils': 1.2.1(zod@3.25.76) react: 19.0.0 swr: 2.2.5(react@19.0.0) throttleit: 2.1.0 - zod: 3.23.8 + zod: 3.25.76 dev: false - /@ai-sdk/solid@0.0.43(zod@3.23.8): + /@ai-sdk/solid@0.0.43(zod@3.25.76): resolution: {integrity: sha512-7PlPLaeMAu97oOY2gjywvKZMYHF+GDfUxYNcuJ4AZ3/MRBatzs/U2r4ClT1iH8uMOcMg02RX6UKzP5SgnUBjVw==} engines: {node: '>=18'} peerDependencies: @@ -2919,13 +2919,13 @@ packages: solid-js: optional: true dependencies: - '@ai-sdk/provider-utils': 1.0.17(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.40(zod@3.23.8) + '@ai-sdk/provider-utils': 1.0.17(zod@3.25.76) + '@ai-sdk/ui-utils': 0.0.40(zod@3.25.76) transitivePeerDependencies: - zod dev: false - /@ai-sdk/solid@0.0.54(zod@3.23.8): + /@ai-sdk/solid@0.0.54(zod@3.25.76): resolution: {integrity: sha512-96KWTVK+opdFeRubqrgaJXoNiDP89gNxFRWUp0PJOotZW816AbhUf4EnDjBjXTLjXL1n0h8tGSE9sZsRkj9wQQ==} engines: {node: '>=18'} peerDependencies: @@ -2934,13 +2934,13 @@ packages: solid-js: optional: true dependencies: - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) + '@ai-sdk/provider-utils': 1.0.22(zod@3.25.76) + '@ai-sdk/ui-utils': 0.0.50(zod@3.25.76) transitivePeerDependencies: - zod dev: true - /@ai-sdk/svelte@0.0.45(svelte@5.33.14)(zod@3.23.8): + /@ai-sdk/svelte@0.0.45(svelte@5.33.14)(zod@3.25.76): resolution: {integrity: sha512-w5Sdl0ArFIM3Fp8BbH4TUvlrS84WP/jN/wC1+fghMOXd7ceVO3Yhs9r71wTqndhgkLC7LAEX9Ll7ZEPfW9WBDA==} engines: {node: '>=18'} peerDependencies: @@ -2949,15 +2949,15 @@ packages: svelte: optional: true dependencies: - '@ai-sdk/provider-utils': 1.0.17(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.40(zod@3.23.8) + '@ai-sdk/provider-utils': 1.0.17(zod@3.25.76) + '@ai-sdk/ui-utils': 0.0.40(zod@3.25.76) sswr: 2.1.0(svelte@5.33.14) svelte: 5.33.14 transitivePeerDependencies: - zod dev: false - /@ai-sdk/svelte@0.0.57(svelte@5.33.14)(zod@3.23.8): + /@ai-sdk/svelte@0.0.57(svelte@5.33.14)(zod@3.25.76): resolution: {integrity: sha512-SyF9ItIR9ALP9yDNAD+2/5Vl1IT6kchgyDH8xkmhysfJI6WrvJbtO1wdQ0nylvPLcsPoYu+cAlz1krU4lFHcYw==} engines: {node: '>=18'} peerDependencies: @@ -2966,15 +2966,15 @@ packages: svelte: optional: true dependencies: - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) + '@ai-sdk/provider-utils': 1.0.22(zod@3.25.76) + '@ai-sdk/ui-utils': 0.0.50(zod@3.25.76) sswr: 2.1.0(svelte@5.33.14) svelte: 5.33.14 transitivePeerDependencies: - zod dev: true - /@ai-sdk/ui-utils@0.0.40(zod@3.23.8): + /@ai-sdk/ui-utils@0.0.40(zod@3.25.76): resolution: {integrity: sha512-f0eonPUBO13pIO8jA9IGux7IKMeqpvWK22GBr3tOoSRnO5Wg5GEpXZU1V0Po+unpeZHyEPahrWbj5JfXcyWCqw==} engines: {node: '>=18'} peerDependencies: @@ -2984,14 +2984,14 @@ packages: optional: true dependencies: '@ai-sdk/provider': 0.0.22 - '@ai-sdk/provider-utils': 1.0.17(zod@3.23.8) + '@ai-sdk/provider-utils': 1.0.17(zod@3.25.76) json-schema: 0.4.0 secure-json-parse: 2.7.0 - zod: 3.23.8 - zod-to-json-schema: 3.23.2(zod@3.23.8) + zod: 3.25.76 + zod-to-json-schema: 3.23.2(zod@3.25.76) dev: false - /@ai-sdk/ui-utils@0.0.50(zod@3.23.8): + /@ai-sdk/ui-utils@0.0.50(zod@3.25.76): resolution: {integrity: sha512-Z5QYJVW+5XpSaJ4jYCCAVG7zIAuKOOdikhgpksneNmKvx61ACFaf98pmOd+xnjahl0pIlc/QIe6O4yVaJ1sEaw==} engines: {node: '>=18'} peerDependencies: @@ -3001,14 +3001,14 @@ packages: optional: true dependencies: '@ai-sdk/provider': 0.0.26 - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) + '@ai-sdk/provider-utils': 1.0.22(zod@3.25.76) json-schema: 0.4.0 secure-json-parse: 2.7.0 - zod: 3.23.8 - zod-to-json-schema: 3.24.5(zod@3.23.8) + zod: 3.25.76 + zod-to-json-schema: 3.24.5(zod@3.25.76) dev: true - /@ai-sdk/ui-utils@1.0.0(zod@3.23.8): + /@ai-sdk/ui-utils@1.0.0(zod@3.25.76): resolution: {integrity: sha512-oXBDIM/0niWeTWyw77RVl505dNxBUDLLple7bTsqo2d3i1UKwGlzBUX8XqZsh7GbY7I6V05nlG0Y8iGlWxv1Aw==} engines: {node: '>=18'} peerDependencies: @@ -3018,35 +3018,35 @@ packages: optional: true dependencies: '@ai-sdk/provider': 1.0.0 - '@ai-sdk/provider-utils': 2.0.0(zod@3.23.8) - zod: 3.23.8 - zod-to-json-schema: 3.24.5(zod@3.23.8) + '@ai-sdk/provider-utils': 2.0.0(zod@3.25.76) + zod: 3.25.76 + zod-to-json-schema: 3.24.5(zod@3.25.76) dev: false - /@ai-sdk/ui-utils@1.2.1(zod@3.23.8): + /@ai-sdk/ui-utils@1.2.1(zod@3.25.76): resolution: {integrity: sha512-BzvMbYm7LHBlbWuLlcG1jQh4eu14MGpz7L+wrGO1+F4oQ+O0fAjgUSNwPWGlZpKmg4NrcVq/QLmxiVJrx2R4Ew==} engines: {node: '>=18'} peerDependencies: zod: ^3.23.8 dependencies: '@ai-sdk/provider': 1.1.0 - '@ai-sdk/provider-utils': 2.2.1(zod@3.23.8) - zod: 3.23.8 - zod-to-json-schema: 3.24.5(zod@3.23.8) + '@ai-sdk/provider-utils': 2.2.1(zod@3.25.76) + zod: 3.25.76 + zod-to-json-schema: 3.24.5(zod@3.25.76) - /@ai-sdk/ui-utils@1.2.11(zod@3.23.8): + /@ai-sdk/ui-utils@1.2.11(zod@3.25.76): resolution: {integrity: sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==} engines: {node: '>=18'} peerDependencies: zod: ^3.23.8 dependencies: '@ai-sdk/provider': 1.1.3 - '@ai-sdk/provider-utils': 2.2.8(zod@3.23.8) - zod: 3.23.8 - zod-to-json-schema: 3.24.5(zod@3.23.8) + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + zod: 3.25.76 + zod-to-json-schema: 3.24.5(zod@3.25.76) dev: false - /@ai-sdk/vue@0.0.45(vue@3.5.16)(zod@3.23.8): + /@ai-sdk/vue@0.0.45(vue@3.5.16)(zod@3.25.76): resolution: {integrity: sha512-bqeoWZqk88TQmfoPgnFUKkrvhOIcOcSH5LMPgzZ8XwDqz5tHHrMHzpPfHCj7XyYn4ROTFK/2kKdC/ta6Ko0fMw==} engines: {node: '>=18'} peerDependencies: @@ -3055,15 +3055,15 @@ packages: vue: optional: true dependencies: - '@ai-sdk/provider-utils': 1.0.17(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.40(zod@3.23.8) + '@ai-sdk/provider-utils': 1.0.17(zod@3.25.76) + '@ai-sdk/ui-utils': 0.0.40(zod@3.25.76) swrv: 1.0.4(vue@3.5.16) vue: 3.5.16(typescript@5.8.3) transitivePeerDependencies: - zod dev: false - /@ai-sdk/vue@0.0.59(vue@3.5.16)(zod@3.23.8): + /@ai-sdk/vue@0.0.59(vue@3.5.16)(zod@3.25.76): resolution: {integrity: sha512-+ofYlnqdc8c4F6tM0IKF0+7NagZRAiqBJpGDJ+6EYhDW8FHLUP/JFBgu32SjxSxC6IKFZxEnl68ZoP/Z38EMlw==} engines: {node: '>=18'} peerDependencies: @@ -3072,8 +3072,8 @@ packages: vue: optional: true dependencies: - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) + '@ai-sdk/provider-utils': 1.0.22(zod@3.25.76) + '@ai-sdk/ui-utils': 0.0.50(zod@3.25.76) swrv: 1.0.4(vue@3.5.16) vue: 3.5.16(typescript@5.5.4) transitivePeerDependencies: @@ -6119,14 +6119,14 @@ packages: react: 18.2.0 dev: false - /@conform-to/zod@0.9.2(@conform-to/dom@0.9.2)(zod@3.23.8): + /@conform-to/zod@0.9.2(@conform-to/dom@0.9.2)(zod@3.25.76): resolution: {integrity: sha512-treG9ZcuNuRERQ1uYvJSWT0zZuqHnYTzRwucg20+/WdjgKNSb60Br+Cy6BAHvVQ8dN6wJsGkHenkX2mSVw3xOA==} peerDependencies: '@conform-to/dom': 0.9.2 zod: ^3.21.0 dependencies: '@conform-to/dom': 0.9.2 - zod: 3.23.8 + zod: 3.25.76 dev: false /@connectrpc/connect-node@1.4.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0): @@ -19365,7 +19365,7 @@ packages: defer-to-connect: 1.1.3 dev: true - /@t3-oss/env-core@0.10.1(typescript@5.8.3)(zod@3.23.8): + /@t3-oss/env-core@0.10.1(typescript@5.8.3)(zod@3.25.76): resolution: {integrity: sha512-GcKZiCfWks5CTxhezn9k5zWX3sMDIYf6Kaxy2Gx9YEQftFcz8hDRN56hcbylyAO3t4jQnQ5ifLawINsNgCDpOg==} peerDependencies: typescript: '>=5.0.0' @@ -19375,10 +19375,10 @@ packages: optional: true dependencies: typescript: 5.8.3 - zod: 3.23.8 + zod: 3.25.76 dev: false - /@t3-oss/env-core@0.11.0(typescript@5.8.3)(zod@3.23.8): + /@t3-oss/env-core@0.11.0(typescript@5.8.3)(zod@3.25.76): resolution: {integrity: sha512-PSalC5bG0a7XbyoLydiQdAnx3gICX6IQNctvh+TyLrdFxsxgocdj9Ui7sd061UlBzi+z4aIGjnem1kZx9QtUgQ==} peerDependencies: typescript: '>=5.0.0' @@ -19388,10 +19388,10 @@ packages: optional: true dependencies: typescript: 5.8.3 - zod: 3.23.8 + zod: 3.25.76 dev: false - /@t3-oss/env-nextjs@0.10.1(typescript@5.8.3)(zod@3.23.8): + /@t3-oss/env-nextjs@0.10.1(typescript@5.8.3)(zod@3.25.76): resolution: {integrity: sha512-iy2qqJLnFh1RjEWno2ZeyTu0ufomkXruUsOZludzDIroUabVvHsrSjtkHqwHp1/pgPUzN3yBRHMILW162X7x2Q==} peerDependencies: typescript: '>=5.0.0' @@ -19400,9 +19400,9 @@ packages: typescript: optional: true dependencies: - '@t3-oss/env-core': 0.10.1(typescript@5.8.3)(zod@3.23.8) + '@t3-oss/env-core': 0.10.1(typescript@5.8.3)(zod@3.25.76) typescript: 5.8.3 - zod: 3.23.8 + zod: 3.25.76 dev: false /@tabler/icons-react@2.47.0(react@18.2.0): @@ -19628,7 +19628,7 @@ packages: dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.6.0) graphql: 16.6.0 - zod: 3.23.8 + zod: 3.25.76 dev: false /@testcontainers/postgresql@10.28.0: @@ -20726,7 +20726,7 @@ packages: /@unkey/error@0.2.0: resolution: {integrity: sha512-DFGb4A7SrusZPP0FYuRIF0CO+Gi4etLUAEJ6EKc+TKYmscL0nEJ2Pr38FyX9MvjI4Wx5l35Wc9KsBjMm9Ybh7w==} dependencies: - zod: 3.23.8 + zod: 3.25.76 dev: false /@uploadthing/mime-types@0.3.0: @@ -21514,7 +21514,7 @@ packages: resolution: {integrity: sha512-hCOfMzbFx5IDutmWLAt6MZwOUjIfSM9G9FyVxytmE4Rs/5YDPWQrD/+IR1w+FweD9H2oOZEnv36TmkjhNURBVA==} dev: true - /ai@3.3.24(openai@4.56.0)(react@19.0.0-rc.0)(svelte@5.33.14)(vue@3.5.16)(zod@3.23.8): + /ai@3.3.24(openai@4.56.0)(react@19.0.0-rc.0)(svelte@5.33.14)(vue@3.5.16)(zod@3.25.76): resolution: {integrity: sha512-hhyczvEdCQeeEMWBWP4Af8k1YIzsheC+dHv6lAsti8NBiOnySFhnjS1sTiIrLyuCgciHXoFYLhlA2+/3AtBLAQ==} engines: {node: '>=18'} peerDependencies: @@ -21536,29 +21536,29 @@ packages: optional: true dependencies: '@ai-sdk/provider': 0.0.22 - '@ai-sdk/provider-utils': 1.0.17(zod@3.23.8) - '@ai-sdk/react': 0.0.53(react@19.0.0-rc.0)(zod@3.23.8) - '@ai-sdk/solid': 0.0.43(zod@3.23.8) - '@ai-sdk/svelte': 0.0.45(svelte@5.33.14)(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.40(zod@3.23.8) - '@ai-sdk/vue': 0.0.45(vue@3.5.16)(zod@3.23.8) + '@ai-sdk/provider-utils': 1.0.17(zod@3.25.76) + '@ai-sdk/react': 0.0.53(react@19.0.0-rc.0)(zod@3.25.76) + '@ai-sdk/solid': 0.0.43(zod@3.25.76) + '@ai-sdk/svelte': 0.0.45(svelte@5.33.14)(zod@3.25.76) + '@ai-sdk/ui-utils': 0.0.40(zod@3.25.76) + '@ai-sdk/vue': 0.0.45(vue@3.5.16)(zod@3.25.76) '@opentelemetry/api': 1.9.0 eventsource-parser: 1.1.2 json-schema: 0.4.0 jsondiffpatch: 0.6.0 nanoid: 3.3.6 - openai: 4.56.0(zod@3.23.8) + openai: 4.56.0(zod@3.25.76) react: 19.0.0-rc.0 secure-json-parse: 2.7.0 svelte: 5.33.14 - zod: 3.23.8 - zod-to-json-schema: 3.23.2(zod@3.23.8) + zod: 3.25.76 + zod-to-json-schema: 3.23.2(zod@3.25.76) transitivePeerDependencies: - solid-js - vue dev: false - /ai@3.4.33(react@18.3.1)(svelte@5.33.14)(vue@3.5.16)(zod@3.23.8): + /ai@3.4.33(react@18.3.1)(svelte@5.33.14)(vue@3.5.16)(zod@3.25.76): resolution: {integrity: sha512-plBlrVZKwPoRTmM8+D1sJac9Bq8eaa2jiZlHLZIWekKWI1yMWYZvCCEezY9ASPwRhULYDJB2VhKOBUUeg3S5JQ==} engines: {node: '>=18'} peerDependencies: @@ -21580,12 +21580,12 @@ packages: optional: true dependencies: '@ai-sdk/provider': 0.0.26 - '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) - '@ai-sdk/react': 0.0.70(react@18.3.1)(zod@3.23.8) - '@ai-sdk/solid': 0.0.54(zod@3.23.8) - '@ai-sdk/svelte': 0.0.57(svelte@5.33.14)(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) - '@ai-sdk/vue': 0.0.59(vue@3.5.16)(zod@3.23.8) + '@ai-sdk/provider-utils': 1.0.22(zod@3.25.76) + '@ai-sdk/react': 0.0.70(react@18.3.1)(zod@3.25.76) + '@ai-sdk/solid': 0.0.54(zod@3.25.76) + '@ai-sdk/svelte': 0.0.57(svelte@5.33.14)(zod@3.25.76) + '@ai-sdk/ui-utils': 0.0.50(zod@3.25.76) + '@ai-sdk/vue': 0.0.59(vue@3.5.16)(zod@3.25.76) '@opentelemetry/api': 1.9.0 eventsource-parser: 1.1.2 json-schema: 0.4.0 @@ -21593,14 +21593,14 @@ packages: react: 18.3.1 secure-json-parse: 2.7.0 svelte: 5.33.14 - zod: 3.23.8 - zod-to-json-schema: 3.24.3(zod@3.23.8) + zod: 3.25.76 + zod-to-json-schema: 3.24.3(zod@3.25.76) transitivePeerDependencies: - solid-js - vue dev: true - /ai@4.0.0(react@18.3.1)(zod@3.23.8): + /ai@4.0.0(react@18.3.1)(zod@3.25.76): resolution: {integrity: sha512-cqf2GCaXnOPhUU+Ccq6i+5I0jDjnFkzfq7t6mc0SUSibSa1wDPn5J4p8+Joh2fDGDYZOJ44rpTW9hSs40rXNAw==} engines: {node: '>=18'} peerDependencies: @@ -21613,17 +21613,17 @@ packages: optional: true dependencies: '@ai-sdk/provider': 1.0.0 - '@ai-sdk/provider-utils': 2.0.0(zod@3.23.8) - '@ai-sdk/react': 1.0.0(react@18.3.1)(zod@3.23.8) - '@ai-sdk/ui-utils': 1.0.0(zod@3.23.8) + '@ai-sdk/provider-utils': 2.0.0(zod@3.25.76) + '@ai-sdk/react': 1.0.0(react@18.3.1)(zod@3.25.76) + '@ai-sdk/ui-utils': 1.0.0(zod@3.25.76) '@opentelemetry/api': 1.9.0 jsondiffpatch: 0.6.0 react: 18.3.1 - zod: 3.23.8 - zod-to-json-schema: 3.24.5(zod@3.23.8) + zod: 3.25.76 + zod-to-json-schema: 3.24.5(zod@3.25.76) dev: false - /ai@4.2.5(react@18.3.1)(zod@3.23.8): + /ai@4.2.5(react@18.3.1)(zod@3.25.76): resolution: {integrity: sha512-URJEslI3cgF/atdTJHtz+Sj0W1JTmiGmD3znw9KensL3qV605odktDim+GTazNJFPR4QaIu1lUio5b8RymvOjA==} engines: {node: '>=18'} peerDependencies: @@ -21634,16 +21634,16 @@ packages: optional: true dependencies: '@ai-sdk/provider': 1.1.0 - '@ai-sdk/provider-utils': 2.2.1(zod@3.23.8) - '@ai-sdk/react': 1.2.2(react@18.3.1)(zod@3.23.8) - '@ai-sdk/ui-utils': 1.2.1(zod@3.23.8) + '@ai-sdk/provider-utils': 2.2.1(zod@3.25.76) + '@ai-sdk/react': 1.2.2(react@18.3.1)(zod@3.25.76) + '@ai-sdk/ui-utils': 1.2.1(zod@3.25.76) '@opentelemetry/api': 1.9.0 jsondiffpatch: 0.6.0 react: 18.3.1 - zod: 3.23.8 + zod: 3.25.76 dev: true - /ai@4.2.5(react@19.0.0)(zod@3.23.8): + /ai@4.2.5(react@19.0.0)(zod@3.25.76): resolution: {integrity: sha512-URJEslI3cgF/atdTJHtz+Sj0W1JTmiGmD3znw9KensL3qV605odktDim+GTazNJFPR4QaIu1lUio5b8RymvOjA==} engines: {node: '>=18'} peerDependencies: @@ -21654,16 +21654,16 @@ packages: optional: true dependencies: '@ai-sdk/provider': 1.1.0 - '@ai-sdk/provider-utils': 2.2.1(zod@3.23.8) - '@ai-sdk/react': 1.2.2(react@19.0.0)(zod@3.23.8) - '@ai-sdk/ui-utils': 1.2.1(zod@3.23.8) + '@ai-sdk/provider-utils': 2.2.1(zod@3.25.76) + '@ai-sdk/react': 1.2.2(react@19.0.0)(zod@3.25.76) + '@ai-sdk/ui-utils': 1.2.1(zod@3.25.76) '@opentelemetry/api': 1.9.0 jsondiffpatch: 0.6.0 react: 19.0.0 - zod: 3.23.8 + zod: 3.25.76 dev: false - /ai@4.3.19(react@18.2.0)(zod@3.23.8): + /ai@4.3.19(react@18.2.0)(zod@3.25.76): resolution: {integrity: sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==} engines: {node: '>=18'} peerDependencies: @@ -21674,13 +21674,13 @@ packages: optional: true dependencies: '@ai-sdk/provider': 1.1.3 - '@ai-sdk/provider-utils': 2.2.8(zod@3.23.8) - '@ai-sdk/react': 1.2.12(react@18.2.0)(zod@3.23.8) - '@ai-sdk/ui-utils': 1.2.11(zod@3.23.8) + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + '@ai-sdk/react': 1.2.12(react@18.2.0)(zod@3.25.76) + '@ai-sdk/ui-utils': 1.2.11(zod@3.25.76) '@opentelemetry/api': 1.9.0 jsondiffpatch: 0.6.0 react: 18.2.0 - zod: 3.23.8 + zod: 3.25.76 dev: false /ajv-formats@2.1.1(ajv@8.17.1): @@ -22141,9 +22141,9 @@ packages: js-yaml: 4.1.0 linear-sum-assignment: 1.0.7 mustache: 4.2.0 - openai: 4.97.0(ws@8.12.0)(zod@3.23.8) - zod: 3.23.8 - zod-to-json-schema: 3.24.5(zod@3.23.8) + openai: 4.97.0(ws@8.12.0)(zod@3.25.76) + zod: 3.25.76 + zod-to-json-schema: 3.24.5(zod@3.25.76) transitivePeerDependencies: - encoding - ws @@ -28339,7 +28339,7 @@ packages: dependencies: '@types/uuid': 10.0.0 commander: 10.0.1 - openai: 4.68.4(zod@3.23.8) + openai: 4.68.4(zod@3.25.76) p-queue: 6.6.2 p-retry: 4.6.2 semver: 7.6.3 @@ -31065,7 +31065,7 @@ packages: - encoding dev: false - /openai@4.56.0(zod@3.23.8): + /openai@4.56.0(zod@3.25.76): resolution: {integrity: sha512-zcag97+3bG890MNNa0DQD9dGmmTWL8unJdNkulZzWRXrl+QeD+YkBI4H58rJcwErxqGK6a0jVPZ4ReJjhDGcmw==} hasBin: true peerDependencies: @@ -31081,12 +31081,12 @@ packages: form-data-encoder: 1.7.2 formdata-node: 4.4.1 node-fetch: 2.6.12 - zod: 3.23.8 + zod: 3.25.76 transitivePeerDependencies: - encoding dev: false - /openai@4.68.4(zod@3.23.8): + /openai@4.68.4(zod@3.25.76): resolution: {integrity: sha512-LRinV8iU9VQplkr25oZlyrsYGPGasIwYN8KFMAAFTHHLHjHhejtJ5BALuLFrkGzY4wfbKhOhuT+7lcHZ+F3iEA==} hasBin: true peerDependencies: @@ -31102,12 +31102,12 @@ packages: form-data-encoder: 1.7.2 formdata-node: 4.4.1 node-fetch: 2.6.12 - zod: 3.23.8 + zod: 3.25.76 transitivePeerDependencies: - encoding dev: false - /openai@4.97.0(ws@8.12.0)(zod@3.23.8): + /openai@4.97.0(ws@8.12.0)(zod@3.25.76): resolution: {integrity: sha512-LRoiy0zvEf819ZUEJhgfV8PfsE8G5WpQi4AwA1uCV8SKvvtXQkoWUFkepD6plqyJQRghy2+AEPQ07FrJFKHZ9Q==} hasBin: true peerDependencies: @@ -31127,7 +31127,7 @@ packages: formdata-node: 4.4.1 node-fetch: 2.6.12 ws: 8.12.0 - zod: 3.23.8 + zod: 3.25.76 transitivePeerDependencies: - encoding @@ -33818,7 +33818,7 @@ packages: react: 18.2.0 dev: false - /remix-utils@7.7.0(@remix-run/node@2.1.0)(@remix-run/react@2.1.0)(@remix-run/router@1.15.3)(intl-parse-accept-language@1.0.0)(react@18.2.0)(zod@3.23.8): + /remix-utils@7.7.0(@remix-run/node@2.1.0)(@remix-run/react@2.1.0)(@remix-run/router@1.15.3)(intl-parse-accept-language@1.0.0)(react@18.2.0)(zod@3.25.76): resolution: {integrity: sha512-J8NhP044nrNIam/xOT1L9a4RQ9FSaA2wyrUwmN8ZT+c/+CdAAf70yfaLnvMyKcV5U+8BcURQ/aVbth77sT6jGA==} engines: {node: '>=18.0.0'} peerDependencies: @@ -33857,7 +33857,7 @@ packages: intl-parse-accept-language: 1.0.0 react: 18.2.0 type-fest: 4.33.0 - zod: 3.23.8 + zod: 3.25.76 dev: false /remove-accents@0.5.0: @@ -38559,47 +38559,39 @@ packages: /zod-error@1.5.0: resolution: {integrity: sha512-zzopKZ/skI9iXpqCEPj+iLCKl9b88E43ehcU+sbRoHuwGd9F1IDVGQ70TyO6kmfiRL1g4IXkjsXK+g1gLYl4WQ==} dependencies: - zod: 3.23.8 + zod: 3.25.76 dev: false - /zod-to-json-schema@3.23.2(zod@3.23.8): + /zod-to-json-schema@3.23.2(zod@3.25.76): resolution: {integrity: sha512-uSt90Gzc/tUfyNqxnjlfBs8W6WSGpNBv0rVsNxP/BVSMHMKGdthPYff4xtCHYloJGM0CFxFsb3NbC0eqPhfImw==} peerDependencies: zod: ^3.23.3 dependencies: - zod: 3.23.8 + zod: 3.25.76 dev: false - /zod-to-json-schema@3.24.3(zod@3.23.8): + /zod-to-json-schema@3.24.3(zod@3.25.76): resolution: {integrity: sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A==} peerDependencies: zod: ^3.24.1 dependencies: - zod: 3.23.8 + zod: 3.25.76 dev: true - /zod-to-json-schema@3.24.5(zod@3.23.8): - resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} - peerDependencies: - zod: ^3.24.1 - dependencies: - zod: 3.23.8 - /zod-to-json-schema@3.24.5(zod@3.25.76): resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} peerDependencies: zod: ^3.24.1 dependencies: zod: 3.25.76 - dev: false - /zod-validation-error@1.5.0(zod@3.23.8): + /zod-validation-error@1.5.0(zod@3.25.76): resolution: {integrity: sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw==} engines: {node: '>=16.0.0'} peerDependencies: zod: ^3.18.0 dependencies: - zod: 3.23.8 + zod: 3.25.76 dev: false /zod@3.22.3: @@ -38608,6 +38600,7 @@ packages: /zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + dev: false /zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} diff --git a/references/d3-chat/package.json b/references/d3-chat/package.json index 36dc313f2b..55e657203b 100644 --- a/references/d3-chat/package.json +++ b/references/d3-chat/package.json @@ -42,7 +42,7 @@ "react-markdown": "^10.1.0", "tailwind-merge": "^3.1.0", "tw-animate-css": "^1.2.4", - "zod": "3.23.8" + "zod": "3.25.76" }, "devDependencies": { "@tailwindcss/postcss": "^4", diff --git a/references/d3-openai-agents/package.json b/references/d3-openai-agents/package.json index e94dc26a21..91309ea5a3 100644 --- a/references/d3-openai-agents/package.json +++ b/references/d3-openai-agents/package.json @@ -33,7 +33,7 @@ "react-dom": "^19.0.0", "tailwind-merge": "^3.0.2", "tw-animate-css": "^1.2.4", - "zod": "3.23.8", + "zod": "3.25.76", "zod-to-json-schema": "^3.24.5" }, "devDependencies": { diff --git a/references/hello-world/package.json b/references/hello-world/package.json index 3529940529..dea86c6b7a 100644 --- a/references/hello-world/package.json +++ b/references/hello-world/package.json @@ -13,7 +13,7 @@ "puppeteer-core": "^24.15.0", "replicate": "^1.0.1", "yup": "^1.6.1", - "zod": "3.23.8", + "zod": "3.25.76", "@sinclair/typebox": "^0.34.3" }, "scripts": { diff --git a/references/nextjs-realtime/package.json b/references/nextjs-realtime/package.json index 9e9edd34a1..4c2736a020 100644 --- a/references/nextjs-realtime/package.json +++ b/references/nextjs-realtime/package.json @@ -37,7 +37,7 @@ "tailwind-merge": "^2.5.3", "tailwindcss-animate": "^1.0.7", "uploadthing": "^7.1.0", - "zod": "3.23.8" + "zod": "3.25.76" }, "devDependencies": { "@next/bundle-analyzer": "^15.0.2", diff --git a/references/python-catalog/package.json b/references/python-catalog/package.json index 171a1e3ea4..1c4f585926 100644 --- a/references/python-catalog/package.json +++ b/references/python-catalog/package.json @@ -10,7 +10,7 @@ "dependencies": { "@trigger.dev/sdk": "workspace:*", "@trigger.dev/python": "workspace:*", - "zod": "3.23.8" + "zod": "3.25.76" }, "devDependencies": { "@trigger.dev/build": "workspace:*", diff --git a/references/test-tasks/package.json b/references/test-tasks/package.json index 6739b44b30..9ed2635da1 100644 --- a/references/test-tasks/package.json +++ b/references/test-tasks/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "@trigger.dev/sdk": "workspace:*", - "zod": "3.23.8" + "zod": "3.25.76" }, "devDependencies": { "@trigger.dev/build": "workspace:*", diff --git a/references/v3-catalog/package.json b/references/v3-catalog/package.json index 5b2315fe67..b637520197 100644 --- a/references/v3-catalog/package.json +++ b/references/v3-catalog/package.json @@ -62,7 +62,7 @@ "yt-dlp-wrap": "^2.3.12", "yup": "^1.4.0", "zip-node-addon": "^0.0.11", - "zod": "3.23.8" + "zod": "3.25.76" }, "devDependencies": { "@opentelemetry/api": "^1.8.0", From 32af62e124d1f270dea0a95de690386b0ef1790b Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Thu, 31 Jul 2025 12:58:03 +0100 Subject: [PATCH 16/19] More MCP --- packages/cli-v3/.mcp.log | 19 ++++ packages/cli-v3/install-mcp.sh | 106 ++++++++++++++++++++++ packages/cli-v3/package.json | 4 +- packages/cli-v3/src/commands/mcp.ts | 48 +++++++--- packages/cli-v3/src/mcp/logger.ts | 47 ++++++++++ packages/cli-v3/src/mcp/mintlifyClient.ts | 73 +++++++++++++++ pnpm-lock.yaml | 5 +- 7 files changed, 287 insertions(+), 15 deletions(-) create mode 100644 packages/cli-v3/.mcp.log create mode 100755 packages/cli-v3/install-mcp.sh create mode 100644 packages/cli-v3/src/mcp/logger.ts create mode 100644 packages/cli-v3/src/mcp/mintlifyClient.ts diff --git a/packages/cli-v3/.mcp.log b/packages/cli-v3/.mcp.log new file mode 100644 index 0000000000..21049213ef --- /dev/null +++ b/packages/cli-v3/.mcp.log @@ -0,0 +1,19 @@ +[2025-07-31T11:21:07.328Z] get_project_details - [ { roots: { roots: [] }, projectRef: 'asdasdjaskdjasd' } ] +[2025-07-31T11:26:08.494Z][version=0.16.2 capabilities=sampling,elicitation,roots] get_project_details - [ { roots: { roots: [] }, projectRef: 'asdaasd' } ] +[2025-07-31T11:27:06.469Z][client=mcp-inspector version=0.16.2 capabilities=sampling,elicitation,roots] get_project_details - [ { roots: { roots: [] }, projectRef: 'asdadas' } ] +[2025-07-31T11:28:40.476Z][client=mcp-inspector version=0.16.2 capabilities=sampling,elicitation,roots] get_project_details - [ + { + roots: { roots: [] }, + projectRef: 'asdadasd', + extra: { + signal: AbortSignal { aborted: false }, + sessionId: undefined, + _meta: { progressToken: 1 }, + sendNotification: [Function: sendNotification], + sendRequest: [Function: sendRequest], + authInfo: undefined, + requestId: 1, + requestInfo: undefined + } + } +] diff --git a/packages/cli-v3/install-mcp.sh b/packages/cli-v3/install-mcp.sh new file mode 100755 index 0000000000..f3424c85cc --- /dev/null +++ b/packages/cli-v3/install-mcp.sh @@ -0,0 +1,106 @@ +#!/bin/bash + +set -e # Exit on error + +echo "🚀 Installing Trigger.dev MCP Server..." + +# Get the absolute path to the node binary +NODE_PATH=$(which node) +if [ -z "$NODE_PATH" ]; then + echo "❌ Error: Node.js not found in PATH" + echo "Please ensure Node.js is installed and available in your PATH" + exit 1 +fi + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Construct the path to the CLI index.js file +CLI_PATH="$SCRIPT_DIR/dist/esm/index.js" + +# Construct the path to the MCP log file +MCP_LOG_FILE="$SCRIPT_DIR/.mcp.log" + +# Make sure the MCP log file exists +touch "$MCP_LOG_FILE" + +# Check if the CLI file exists +if [ ! -f "$CLI_PATH" ]; then + echo "❌ Error: CLI file not found at $CLI_PATH" + echo "Make sure to build the CLI first with: pnpm run build" + exit 1 +fi + +# Ensure the CLI is executable +chmod +x "$CLI_PATH" + +echo "✅ Found Node.js at: $NODE_PATH" +echo "✅ Found CLI at: $CLI_PATH" + +# Claude Code configuration +CLAUDE_CONFIG="$HOME/.claude.json" + +echo "📁 Claude configuration file: $CLAUDE_CONFIG" + +# Check if Claude config exists, create if it doesn't +if [ ! -f "$CLAUDE_CONFIG" ]; then + echo "📝 Creating new Claude configuration file..." + echo '{"mcpServers": {}}' > "$CLAUDE_CONFIG" +fi + +# Use Node.js to manipulate the JSON +echo "🔧 Updating Claude configuration..." + +node -e " +const fs = require('fs'); +const path = require('path'); + +const configPath = '$CLAUDE_CONFIG'; +const nodePath = '$NODE_PATH'; +const cliPath = '$CLI_PATH'; +const logFile = '$MCP_LOG_FILE'; + +try { + // Read existing config + let config; + try { + const configContent = fs.readFileSync(configPath, 'utf8'); + config = JSON.parse(configContent); + } catch (error) { + console.log('📝 Creating new configuration structure...'); + config = {}; + } + + // Ensure mcpServers object exists + if (!config.mcpServers) { + config.mcpServers = {}; + } + + // Add/update trigger.dev entry + config.mcpServers['trigger'] = { + command: nodePath, + args: [cliPath, 'mcp', '--log-file', logFile] + }; + + // Write back to file with proper formatting + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + + console.log('✅ Successfully installed Trigger.dev MCP server to Claude Code'); + console.log(''); + console.log('📋 Configuration Details:'); + console.log(' • Config file:', configPath); + console.log(' • Node.js path:', nodePath); + console.log(' • CLI path:', cliPath); + console.log(''); + console.log('🎉 Installation complete! You can now use Trigger.dev MCP commands in Claude Code.'); + console.log('💡 Try typing @ in Claude Code and select \"triggerdev\" to get started.'); + +} catch (error) { + console.error('❌ Error updating Claude configuration:', error.message); + process.exit(1); +} +" + +echo "" +echo "🔍 You can test the MCP server with:" +echo " pnpm run inspector" diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json index c539bc70c3..a172abfc05 100644 --- a/packages/cli-v3/package.json +++ b/packages/cli-v3/package.json @@ -75,7 +75,9 @@ "dev": "tshy --watch", "test": "vitest", "test:e2e": "vitest --run -c ./e2e/vitest.config.ts", - "update-version": "tsx ../../scripts/updateVersion.ts" + "update-version": "tsx ../../scripts/updateVersion.ts", + "install-mcp": "./install-mcp.sh", + "inspector": "npx @modelcontextprotocol/inspector dist/esm/index.js mcp --log-file .mcp.log" }, "dependencies": { "@clack/prompts": "^0.10.0", diff --git a/packages/cli-v3/src/commands/mcp.ts b/packages/cli-v3/src/commands/mcp.ts index 1452574ed8..6602b4a6c7 100644 --- a/packages/cli-v3/src/commands/mcp.ts +++ b/packages/cli-v3/src/commands/mcp.ts @@ -1,15 +1,16 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { Command } from "commander"; import { z } from "zod"; import { CommonCommandOptions, commonOptions, wrapCommandAction } from "../cli/common.js"; -import { chalkError } from "../utilities/cliOutput.js"; -import { logger } from "../utilities/logger.js"; import { login } from "./login.js"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { performSearch } from "../mcp/mintlifyClient.js"; +import { logger } from "../utilities/logger.js"; +import { FileLogger } from "../mcp/logger.js"; const McpCommandOptions = CommonCommandOptions.extend({ projectRef: z.string().optional(), - mcpPort: z.coerce.number().optional().default(3333), + logFile: z.string().optional(), }); export type McpCommandOptions = z.infer; @@ -20,7 +21,7 @@ export function configureMcpCommand(program: Command) { .command("mcp") .description("Run the MCP server") .option("-p, --project-ref ", "The project ref to use") - .option("-m, --mcp-port ", "The port to run the MCP server on", "3333") + .option("--log-file ", "The file to log to") ).action(async (options) => { wrapCommandAction("mcp", McpCommandOptions, options, async (opts) => { await mcpCommand(opts); @@ -29,6 +30,8 @@ export function configureMcpCommand(program: Command) { } export async function mcpCommand(options: McpCommandOptions) { + logger.loggerLevel = "none"; + const authorization = await login({ embedded: true, silent: true, @@ -42,22 +45,45 @@ export async function mcpCommand(options: McpCommandOptions) { } const server = new McpServer({ - name: "trigger.dev", + name: "triggerdev", version: "1.0.0", + description: "Trigger.dev MCP server. Search the Trigger.dev docs.", }); + const fileLogger: FileLogger | undefined = options.logFile + ? new FileLogger(options.logFile, server) + : undefined; + + server.registerTool( + "search_docs", + { + description: + "Search across the Trigger.dev documentation to find relevant information, code examples, API references, and guides. Use this tool when you need to answer questions about Trigger.dev, find specific documentation, understand how features work, or locate implementation details. The search returns contextual content with titles and direct links to the documentation pages", + inputSchema: { + query: z.string(), + }, + }, + async ({ query }) => { + const results = await performSearch(query); + return results; + } + ); + server.registerTool( "get_project_details", { - title: "Get Project Details", description: "Get the details of the project", inputSchema: { - cwd: z.string().describe("The current working directory the user is in"), + projectRef: z.string().optional(), }, }, - async ({ cwd }) => { + async ({ projectRef }, extra) => { + const roots = await server.server.listRoots(); + + fileLogger?.log("get_project_details", { roots, projectRef, extra }); + return { - content: [{ type: "text", text: `Current working directory: ${cwd}` }], + content: [{ type: "text", text: "Not implemented" }], }; } ); diff --git a/packages/cli-v3/src/mcp/logger.ts b/packages/cli-v3/src/mcp/logger.ts new file mode 100644 index 0000000000..b30576a331 --- /dev/null +++ b/packages/cli-v3/src/mcp/logger.ts @@ -0,0 +1,47 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { appendFileSync } from "node:fs"; +import util from "node:util"; + +export class FileLogger { + private filePath: string; + private server: McpServer; + + constructor(filePath: string, server: McpServer) { + this.filePath = filePath; + this.server = server; + } + + log(message: string, ...args: unknown[]) { + const logMessage = `[${new Date().toISOString()}][${this.formatServerInfo()}] ${message} - ${util.inspect( + args, + { + depth: null, + colors: false, + } + )}\n`; + appendFileSync(this.filePath, logMessage); + } + + private formatServerInfo() { + return `${this.formatClientName()} ${this.formatClientVersion()} ${this.formatClientCapabilities()}`; + } + + private formatClientName() { + const clientName = this.server.server.getClientVersion()?.name; + return `client=${clientName ?? "unknown"}`; + } + + private formatClientVersion() { + const clientVersion = this.server.server.getClientVersion(); + + return `version=${clientVersion?.version ?? "unknown"}`; + } + + private formatClientCapabilities() { + const clientCapabilities = this.server.server.getClientCapabilities(); + + const keys = Object.keys(clientCapabilities ?? {}); + + return `capabilities=${keys.join(",")}`; + } +} diff --git a/packages/cli-v3/src/mcp/mintlifyClient.ts b/packages/cli-v3/src/mcp/mintlifyClient.ts new file mode 100644 index 0000000000..1b05f07301 --- /dev/null +++ b/packages/cli-v3/src/mcp/mintlifyClient.ts @@ -0,0 +1,73 @@ +export async function performSearch(query: string) { + const body = callToolBody("search", { query }); + + const response = await fetch("https://trigger.dev/docs/mcp", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json, text/event-stream", + "MCP-Protocol-Version": "2025-06-18", + }, + body: JSON.stringify(body), + }); + + const data = await parseResponse(response); + return data; +} + +async function parseResponse(response: Response) { + if (response.headers.get("content-type")?.includes("text/event-stream")) { + return parseSSEResponse(response); + } else { + return parseJSONResponse(response); + } +} + +async function parseJSONResponse(response: Response) { + const data = await response.json(); + return data; +} + +// Get the first data: event and return the parsed JSON of the event +async function parseSSEResponse(response: Response) { + const reader = response.body?.getReader(); + const decoder = new TextDecoder(); + + if (!reader) { + throw new Error("No reader found"); + } + + let buffer = ""; + + while (true) { + const { value, done } = await reader.read(); + if (done) throw new Error("SSE stream closed before data arrived"); + + buffer += decoder.decode(value, { stream: true }); + const events = buffer.split("\n\n"); // SSE delimiter + buffer = events.pop()!; // keep incomplete + + for (const evt of events) { + for (const line of evt.split("\n")) { + if (line.startsWith("data:")) { + const json = line.slice(5).trim(); + return JSON.parse(json); // ✅ got it + } + } + } + } + + throw new Error("No data: event found"); +} + +function callToolBody(tool: string, args: Record) { + return { + jsonrpc: "2.0", + id: 1, + method: "tools/call", + params: { + name: tool, + arguments: args, + }, + }; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a640e040f..bbb9d7acf7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11304,7 +11304,7 @@ packages: resolution: {integrity: sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 is-glob: 4.0.3 open: 8.4.0 picocolors: 1.1.1 @@ -23611,7 +23611,6 @@ packages: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: false /crypto-js@4.1.1: resolution: {integrity: sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==} @@ -26321,7 +26320,7 @@ packages: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 signal-exit: 4.1.0 /forever-agent@0.6.1: From df907ff0aa6074dc298fb5545095a374e534be7a Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Thu, 31 Jul 2025 15:56:33 +0100 Subject: [PATCH 17/19] more mcp stuff --- packages/cli-v3/.mcp.log | 19 ------------------- packages/cli-v3/src/commands/mcp.ts | 27 +++++++++------------------ packages/cli-v3/src/mcp/context.ts | 23 +++++++++++++++++++++++ packages/cli-v3/src/mcp/tools.ts | 23 +++++++++++++++++++++++ 4 files changed, 55 insertions(+), 37 deletions(-) delete mode 100644 packages/cli-v3/.mcp.log create mode 100644 packages/cli-v3/src/mcp/context.ts create mode 100644 packages/cli-v3/src/mcp/tools.ts diff --git a/packages/cli-v3/.mcp.log b/packages/cli-v3/.mcp.log deleted file mode 100644 index 21049213ef..0000000000 --- a/packages/cli-v3/.mcp.log +++ /dev/null @@ -1,19 +0,0 @@ -[2025-07-31T11:21:07.328Z] get_project_details - [ { roots: { roots: [] }, projectRef: 'asdasdjaskdjasd' } ] -[2025-07-31T11:26:08.494Z][version=0.16.2 capabilities=sampling,elicitation,roots] get_project_details - [ { roots: { roots: [] }, projectRef: 'asdaasd' } ] -[2025-07-31T11:27:06.469Z][client=mcp-inspector version=0.16.2 capabilities=sampling,elicitation,roots] get_project_details - [ { roots: { roots: [] }, projectRef: 'asdadas' } ] -[2025-07-31T11:28:40.476Z][client=mcp-inspector version=0.16.2 capabilities=sampling,elicitation,roots] get_project_details - [ - { - roots: { roots: [] }, - projectRef: 'asdadasd', - extra: { - signal: AbortSignal { aborted: false }, - sessionId: undefined, - _meta: { progressToken: 1 }, - sendNotification: [Function: sendNotification], - sendRequest: [Function: sendRequest], - authInfo: undefined, - requestId: 1, - requestInfo: undefined - } - } -] diff --git a/packages/cli-v3/src/commands/mcp.ts b/packages/cli-v3/src/commands/mcp.ts index 6602b4a6c7..d7f9b5af89 100644 --- a/packages/cli-v3/src/commands/mcp.ts +++ b/packages/cli-v3/src/commands/mcp.ts @@ -7,6 +7,8 @@ import { login } from "./login.js"; import { performSearch } from "../mcp/mintlifyClient.js"; import { logger } from "../utilities/logger.js"; import { FileLogger } from "../mcp/logger.js"; +import { McpContext } from "../mcp/context.js"; +import { registerGetProjectDetailsTool } from "../mcp/tools.js"; const McpCommandOptions = CommonCommandOptions.extend({ projectRef: z.string().optional(), @@ -54,6 +56,12 @@ export async function mcpCommand(options: McpCommandOptions) { ? new FileLogger(options.logFile, server) : undefined; + const context = new McpContext(server, { + login: authorization, + projectRef: options.projectRef, + fileLogger, + }); + server.registerTool( "search_docs", { @@ -69,24 +77,7 @@ export async function mcpCommand(options: McpCommandOptions) { } ); - server.registerTool( - "get_project_details", - { - description: "Get the details of the project", - inputSchema: { - projectRef: z.string().optional(), - }, - }, - async ({ projectRef }, extra) => { - const roots = await server.server.listRoots(); - - fileLogger?.log("get_project_details", { roots, projectRef, extra }); - - return { - content: [{ type: "text", text: "Not implemented" }], - }; - } - ); + registerGetProjectDetailsTool(context); // Start receiving messages on stdin and sending messages on stdout const transport = new StdioServerTransport(); diff --git a/packages/cli-v3/src/mcp/context.ts b/packages/cli-v3/src/mcp/context.ts new file mode 100644 index 0000000000..ace12e6f22 --- /dev/null +++ b/packages/cli-v3/src/mcp/context.ts @@ -0,0 +1,23 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { FileLogger } from "./logger.js"; +import { LoginResult } from "../utilities/session.js"; + +export type McpContextOptions = { + login: LoginResult; + projectRef?: string; + fileLogger?: FileLogger; +}; + +export class McpContext { + public readonly server: McpServer; + public readonly options: McpContextOptions; + + constructor(server: McpServer, options: McpContextOptions) { + this.server = server; + this.options = options; + } + + get logger() { + return this.options.fileLogger; + } +} diff --git a/packages/cli-v3/src/mcp/tools.ts b/packages/cli-v3/src/mcp/tools.ts new file mode 100644 index 0000000000..e0e0f18541 --- /dev/null +++ b/packages/cli-v3/src/mcp/tools.ts @@ -0,0 +1,23 @@ +import z from "zod"; +import { McpContext } from "./context.js"; + +export function registerGetProjectDetailsTool(context: McpContext) { + context.server.registerTool( + "get_project_details", + { + description: "Get the details of the project", + inputSchema: { + projectRef: z.string().optional(), + }, + }, + async ({ projectRef }, extra) => { + const roots = await context.server.server.listRoots(); + + context.logger?.log("get_project_details", { roots, projectRef, extra }); + + return { + content: [{ type: "text", text: "Not implemented" }], + }; + } + ); +} From 0ca3607a965872aa94947b1d44f29f5ca48a24db Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Thu, 31 Jul 2025 15:56:54 +0100 Subject: [PATCH 18/19] ignore .mcp.log --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9bee46fc27..6f435d0400 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,5 @@ apps/**/public/build /packages/core/src/package.json /packages/trigger-sdk/src/package.json /packages/python/src/package.json -.claude \ No newline at end of file +.claude +.mcp.log \ No newline at end of file From 45047781ffafffe59b88737b067a03c9fe61c3ba Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Fri, 1 Aug 2025 15:34:15 +0100 Subject: [PATCH 19/19] WIP Assignments --- pnpm-lock.yaml | 43 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bbb9d7acf7..ecf9102e50 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1529,38 +1529,35 @@ importers: specifier: 1.9.0 version: 1.9.0 '@opentelemetry/api-logs': - specifier: 0.52.1 - version: 0.52.1 + specifier: 0.203.0 + version: 0.203.0 '@opentelemetry/core': - specifier: ^1.30.1 - version: 1.30.1(@opentelemetry/api@1.9.0) + specifier: 2.0.1 + version: 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/exporter-logs-otlp-http': - specifier: 0.52.1 - version: 0.52.1(@opentelemetry/api@1.9.0) + specifier: 0.203.0 + version: 0.203.0(@opentelemetry/api@1.9.0) '@opentelemetry/exporter-trace-otlp-http': - specifier: 0.52.1 - version: 0.52.1(@opentelemetry/api@1.9.0) + specifier: 0.203.0 + version: 0.203.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': - specifier: 0.52.1 - version: 0.52.1(@opentelemetry/api@1.9.0) + specifier: 0.203.0 + version: 0.203.0(@opentelemetry/api@1.9.0)(supports-color@10.0.0) '@opentelemetry/resources': - specifier: 1.25.1 - version: 1.25.1(@opentelemetry/api@1.9.0) + specifier: 2.0.1 + version: 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-logs': - specifier: 0.52.1 - version: 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-node': - specifier: 0.52.1 - version: 0.52.1(@opentelemetry/api@1.9.0) + specifier: 0.203.0 + version: 0.203.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': - specifier: 1.25.1 - version: 1.25.1(@opentelemetry/api@1.9.0) + specifier: 2.0.1 + version: 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-node': - specifier: 1.25.1 - version: 1.25.1(@opentelemetry/api@1.9.0) + specifier: 2.0.1 + version: 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': - specifier: 1.25.1 - version: 1.25.1 + specifier: 1.36.0 + version: 1.36.0 dequal: specifier: ^2.0.3 version: 2.0.3