Documentation Index
Fetch the complete documentation index at: https://mintlify.com/tailor-platform/sdk/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The createResolver function creates custom GraphQL endpoints with business logic that execute on the Tailor Platform. Resolvers provide type-safe input/output schemas, access to TailorDB via Kysely, and user context for authentication.
Function Signature
function createResolver<
Input extends Record<string, TailorAnyField> | undefined,
Output extends TailorAnyField | Record<string, TailorAnyField>
>(config: {
name: string;
description?: string;
operation: "query" | "mutation";
input?: Input;
output: Output;
body: (context: Context<Input>) => OutputType<Output> | Promise<OutputType<Output>>;
}): ResolverConfig
Parameters
Unique resolver name per namespace. Used as the GraphQL field name.
Optional description of the resolver’s purpose.
operation
'query' | 'mutation'
required
GraphQL operation type:
"query" - For read operations (fetching data)
"mutation" - For write operations (creating, updating, deleting data)
input
Record<string, TailorAnyField>
Input schema defined using t object methods. Fields can include validation rules.input: {
email: t.string().validate(({ value }) => value.includes("@")),
age: t.int()
}
output
TailorAnyField | Record<string, TailorAnyField>
required
Output schema defined using t object methods. Can be a single field or object of fields.// Single field
output: t.string()
// Object of fields
output: t.object({
id: t.uuid(),
name: t.string()
})
// Shorthand object (automatically wrapped)
output: {
id: t.uuid(),
name: t.string()
}
Function containing the resolver’s business logic. Receives a context object and returns data matching the output schema.Context Object Structure:Typed input data from the GraphQL request, inferred from the input schema.
User performing the operation.interface TailorUser {
id: string; // User ID
type: string; // User type (e.g., "endUser", "machineUser")
workspaceId: string; // Workspace identifier
attributes?: Record<string, unknown>; // Custom user attributes
attributeList?: string[]; // List of attribute keys
}
Environment variables defined in tailor.config.ts.
Add validation rules to input fields using the validate method:
createResolver({
name: "createUser",
operation: "mutation",
input: {
email: t.string().validate(
({ value }) => value.includes("@"),
[({ value }) => value.length <= 255, "Email must be 255 characters or less"]
),
age: t.int().validate(({ value }) => value >= 0 && value <= 150)
},
body: (context) => {
// Input is validated before body executes
return { email: context.input.email };
},
output: t.object({ email: t.string() })
});
Validation Function Parameters:
value - The field value being validated
data - The entire input object
user - The user performing the operation
Validation Formats:
- Function returning
boolean (uses default error message)
- Tuple of
[function, errorMessage] for custom error messages
- Multiple validators (pass multiple arguments to
validate)
Examples
Basic Query Resolver
import { createResolver, t } from "@tailor-platform/sdk";
const validators: [(a: { value: number }) => boolean, string][] = [
[({ value }) => value >= 0, "Value must be non-negative"],
[({ value }) => value < 10, "Value must be less than 10"],
];
export default createResolver({
name: "add",
description: "Addition operation",
operation: "query",
input: {
a: t.int().description("First number to add").validate(...validators),
b: t.int().description("Second number to add").validate(...validators),
},
body: ({ input }) => input.a + input.b,
output: t.int().description("Sum of the two input numbers"),
});
Query with User Context
import { createResolver, t } from "@tailor-platform/sdk";
export default createResolver({
name: "showUserInfo",
description: "Show current user information",
operation: "query",
body: (context) => {
return {
id: context.user.id,
type: context.user.type,
workspaceId: context.user.workspaceId,
role: context.user.attributes?.role ?? "MANAGER",
};
},
output: t.object({
id: t.string().description("User ID"),
type: t.string().description("User type"),
workspaceId: t.string().description("Workspace ID"),
role: t.enum(["MANAGER", "STAFF"]).description("User role"),
}).description("User information"),
});
Mutation with Database Access
import { createResolver, t } from "@tailor-platform/sdk";
import { format } from "date-fns";
import { getDB } from "../generated/tailordb";
export default createResolver({
name: "stepChain",
description: "Step chain operation with nested validation",
operation: "query",
input: {
user: t.object({
name: t.object({
first: t.string()
.description("User's first name")
.validate([
({ value }) => value.length >= 2,
"First name must be at least 2 characters",
]),
last: t.string()
.description("User's last name")
.validate([
({ value }) => value.length >= 2,
"Last name must be at least 2 characters",
]),
}).description("User's full name"),
activatedAt: t.datetime({ optional: true }).description("User activation timestamp"),
})
.typeName("StepChainUser")
.description("User information"),
},
output: t.object({
result: t.object({
summary: t.string({ array: true }).description("Summary of processing steps"),
}).description("Processing result"),
}).description("Result of step chain operation"),
body: async (context) => {
const step1 = `step1: Hello ${context.input.user.name.first} ${context.input.user.name.last} on step1!`;
const step2 = `step2: recorded ${format(new Date(), "yyyy-MM-dd HH:mm:ss")} on step2!`;
const db = getDB("tailordb");
const kyselyResult = await db.selectFrom("Supplier").select(["state"]).execute();
const kyselyStep = kyselyResult.map((r) => r.state).join(", ");
return {
result: {
summary: [step1, step2, kyselyStep],
},
};
},
});
Triggering Workflows from Resolvers
resolvers/triggerWorkflow.ts
import { createResolver, t } from "@tailor-platform/sdk";
import { auth } from "../tailor.config";
import orderProcessingWorkflow from "../workflows/order-processing";
export default createResolver({
name: "triggerOrderProcessing",
description: "Trigger the order processing workflow",
operation: "mutation",
input: {
orderId: t.string().description("Order ID to process"),
customerId: t.string().description("Customer ID for the order"),
},
body: async ({ input }) => {
// Trigger the workflow with authInvoker
const workflowRunId = await orderProcessingWorkflow.trigger(
{
orderId: input.orderId,
customerId: input.customerId,
},
{ authInvoker: auth.invoker("manager-machine-user") },
);
return {
workflowRunId,
message: `Workflow triggered for order ${input.orderId}`,
};
},
output: t.object({
workflowRunId: t.string(),
message: t.string(),
}),
});
Definition Rules
- One resolver per file: Each file must contain exactly one resolver definition
- Export method: Must use
export default
- Uniqueness: Resolver names must be unique per namespace
Query vs Mutation
Use operation: "query" for read operations and operation: "mutation" for write operations:
// Query - for reading data
createResolver({
name: "getUsers",
operation: "query",
// ...
});
// Mutation - for creating, updating, or deleting data
createResolver({
name: "createUser",
operation: "mutation",
// ...
});
Comparison with Pipeline Resolver
The SDK’s Resolver is a simplified version of Tailor Platform’s Pipeline Resolver:
| Pipeline Resolver | SDK Resolver |
|---|
| Multiple steps with different operations | Single body function |
| Declarative step configuration | Imperative TypeScript code |
| Built-in TailorDB/GraphQL steps | Direct database access via Kysely |
| CEL expressions for data transformation | Native TypeScript transformations |
SDK Resolvers provide more flexibility with native TypeScript code, while Pipeline Resolvers offer declarative configuration for common patterns.