Skip to main content

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 ResolverSDK Resolver
Multiple steps with different operationsSingle body function
Declarative step configurationImperative TypeScript code
Built-in TailorDB/GraphQL stepsDirect database access via Kysely
CEL expressions for data transformationNative TypeScript transformations

Example Comparison

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 }}"

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
example/resolvers/add.ts
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"),
});

Input/Output Schemas

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,
  },
});

Input Validation

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:
value
any
The field value being validated
data
object
The entire input object
user
object
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
object
Input data from GraphQL request
user
object
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(),
  }),
});

Build docs developers (and LLMs) love