Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changeset/nine-snails-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@trigger.dev/core": major
---
Why
Allow usage of validation libraries that implement standard schema for schemaTask

What
support standard schema for schemaTask

How
Pass schema definition via schemaTask.schema
Usage:
schemaTask({
schema : `any compatable schema`
})


3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@
"uncrypto": "^0.1.3",
"zod": "3.25.76",
"zod-error": "1.5.0",
"zod-validation-error": "^1.5.0"
"zod-validation-error": "^1.5.0",
"@standard-schema/spec": "^1.1.0"
},
"devDependencies": {
"@ai-sdk/provider-utils": "^1.0.22",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/v3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export {
type SchemaParseFn,
isSchemaZodEsque,
isSchemaValibotEsque,
isSchemaStandardSchemaV1,
isSchemaArkTypeEsque,
} from "./types/schemas.js";

Expand Down
42 changes: 39 additions & 3 deletions packages/core/src/v3/types/schemas.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { StandardSchemaV1 } from "@standard-schema/spec";

export type SchemaZodEsque<TInput, TParsedInput> = {
_input: TInput;
_output: TParsedInput;
Expand Down Expand Up @@ -41,6 +43,12 @@ export function isSchemaArkTypeEsque<TInput, TParsedInput>(
return typeof schema === "object" && "_inferIn" in schema && "_infer" in schema;
}

export function isSchemaStandardSchemaV1<TInput, TParsedInput>(
schema: Schema
): schema is StandardSchemaV1<TInput, TParsedInput> {
return typeof schema === "object" && "~standard" in schema && schema["~standard"].version === 1;
}

export type SchemaMyZodEsque<TInput> = {
parse: (input: any) => TInput;
};
Expand All @@ -64,12 +72,14 @@ export type SchemaWithoutInput<TInput> =
| SchemaMyZodEsque<TInput>
| SchemaScaleEsque<TInput>
| SchemaSuperstructEsque<TInput>
| SchemaYupEsque<TInput>;
| SchemaYupEsque<TInput>
| StandardSchemaV1<TInput>;

export type SchemaWithInputOutput<TInput, TParsedInput> =
| SchemaZodEsque<TInput, TParsedInput>
| SchemaValibotEsque<TInput, TParsedInput>
| SchemaArkTypeEsque<TInput, TParsedInput>;
| SchemaArkTypeEsque<TInput, TParsedInput>
| StandardSchemaV1<TInput, TParsedInput>;
Comment on lines 72 to +82

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 StandardSchemaV1 added to both SchemaWithInputOutput and SchemaWithoutInput may cause ambiguous type inference

StandardSchemaV1<TInput, TParsedInput> is added to both SchemaWithInputOutput at line 82 and SchemaWithoutInput at line 76. The inferSchema type at line 86-98 first checks SchemaWithInputOutput then SchemaWithoutInput. Since StandardSchemaV1<TInput> (single type param) defaults Output = Input, it matches both unions.

This means TypeScript's conditional type resolution will always match SchemaWithInputOutput first for a StandardSchemaV1 schema, giving { in: TInput, out: TOutput }. The SchemaWithoutInput branch would only be reached if inference from SchemaWithInputOutput fails. In practice, StandardSchemaV1 carries its types via ~standard.types.input and ~standard.types.output, so the SchemaWithInputOutput match should infer correctly. However, the dual membership is redundant and could confuse contributors β€” the existing patterns (Zod, Valibot, ArkType) only appear in one union, not both.

(Refers to lines 70-82)

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.


export type Schema = SchemaWithInputOutput<any, any> | SchemaWithoutInput<any>;

Expand Down Expand Up @@ -98,11 +108,26 @@ export type inferSchemaOut<
TDefault = unknown,
> = TSchema extends Schema ? inferSchema<TSchema>["out"] : TDefault;

function issueToString(issue: StandardSchemaV1.Issue) : string {
return `{ message : ${issue.message} , path : ${issue.path}}`
}

class StandardSchemaV1ValidateError extends Error {
constructor(result : StandardSchemaV1.FailureResult){
let message = `StandardSchemaV1 Validate Error [`
result.issues.forEach((issue) => {
message += ` ${issueToString(issue)}`
})
message += ' ]'
super(message)
}
}

export type SchemaParseFn<TType> = (value: unknown) => Promise<TType> | TType;
export type AnySchemaParseFn = SchemaParseFn<any>;

export function getSchemaParseFn<TType>(procedureParser: Schema): SchemaParseFn<TType> {
const parser = procedureParser as any;
const parser = procedureParser as any;

if (typeof parser === "function" && typeof parser.assert === "function") {
// ParserArkTypeEsque - arktype schemas shouldn't be called as a function because they return a union type instead of throwing
Expand Down Expand Up @@ -144,5 +169,16 @@ export function getSchemaParseFn<TType>(procedureParser: Schema): SchemaParseFn<
};
}

if (parser["~standard"] && typeof parser["~standard"].validate === "function") {
return (value) => {
let response = parser["~standard"].validate(value);
if ("value" in response) {
return response["value"] as TType;
}
//FailureResult type
throw new StandardSchemaV1ValidateError(response);
};
}

throw new Error("Could not find a validator fn");
}