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.
Resolvers are custom GraphQL endpoints that allow you to implement business logic with type-safe input/output schemas and direct database access.
Overview
Resolvers provide:
- Custom GraphQL queries and mutations
- Type-safe input/output schemas
- Access to TailorDB via Kysely query builder
- User context for authentication/authorization
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 |
Example Comparison
Pipeline Resolver
SDK Resolver
steps:
- name: getUser
operation: tailordb.query
params:
type: User
filter:
email: { eq: "{{ input.email }}" }
- name: updateAge
operation: tailordb.mutation
params:
type: User
id: "{{ steps.getUser.id }}"
input:
age: "{{ steps.getUser.age + 1 }}"
createResolver({
name: "incrementUserAge",
operation: "mutation",
input: { email: t.string() },
body: async (context) => {
const db = getDB("tailordb");
const user = await db
.selectFrom("User")
.selectAll()
.where("email", "=", context.input.email)
.executeTakeFirstOrThrow();
await db
.updateTable("User")
.set({ age: user.age + 1 })
.where("id", "=", user.id)
.execute();
return { oldAge: user.age, newAge: user.age + 1 };
},
output: t.object({ oldAge: t.int(), newAge: t.int() }),
});
Creating a Resolver
Define resolvers in files matching glob patterns specified in tailor.config.ts.
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
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"),
});
Define input/output schemas using methods of t object. Basic usage and supported field types are the same as TailorDB. TailorDB-specific options (e.g., index, relation) are not supported.
You can reuse fields defined with db object, but note that unsupported options will be ignored:
const user = db.type("User", {
name: db.string().unique(),
age: db.int(),
});
createResolver({
input: {
name: user.fields.name,
},
});
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 functions receive:
The field value being validated
The user performing the operation
You can specify validation as:
- A function returning
boolean (uses default error message)
- A tuple of
[function, errorMessage] for custom error messages
- Multiple validators (pass multiple arguments to
validate)
Body Function
Define actual resolver logic in the body function. Function arguments include:
Input data from GraphQL request
User performing the operation
Using Kysely for Database Access
If you’re generating Kysely types with a generator, you can use getDB to execute typed queries:
import { getDB } from "../generated/tailordb";
createResolver({
name: "getUser",
operation: "query",
input: {
name: t.string(),
},
body: async (context) => {
const db = getDB("tailordb");
const result = await db
.selectFrom("User")
.select("id")
.where("name", "=", context.input.name)
.limit(1)
.executeTakeFirstOrThrow();
return {
result: result.id,
};
},
output: t.object({
result: t.uuid(),
}),
});
Accessing User Context
example/resolvers/userInfo.ts
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"),
});
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",
// ...
});
Triggering Workflows
Resolvers can trigger workflows using workflow.trigger():
example/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(),
}),
});