Executor
Executors are event-driven handlers that automatically trigger in response to data changes, schedules, or external events.
Overview
Executors provide:
- Automatic triggers on record changes (create, update, delete)
- Scheduled execution via cron expressions
- Incoming webhook handlers
- Post-resolver execution hooks
- Multiple operation types (functions, webhooks, GraphQL, workflows)
For the official Tailor Platform documentation, see Executor Guide.
Creating an Executor
Define executors in files matching glob patterns specified in tailor.config.ts.
Definition Rules:
- One executor per file: Each file must contain exactly one executor definition
- Export method: Must use
export default - Uniqueness: Executor names must be unique globally across your entire application
import { createExecutor, recordCreatedTrigger, t } from "@tailor-platform/sdk";
import { user } from "../tailordb/user";
export default createExecutor({
name: "user-welcome",
description: "Send welcome email to new users",
trigger: recordCreatedTrigger({
type: user,
condition: ({ newRecord }) => !!newRecord.email && newRecord.isActive,
}),
operation: {
kind: "function",
body: async ({ newRecord }) => {
// Send welcome email logic here
},
},
});Trigger Types
Record Triggers
Fire when records are created, updated, or deleted:
recordCreatedTrigger(): Fires when a new record is createdrecordUpdatedTrigger(): Fires when a record is updatedrecordDeletedTrigger(): Fires when a record is deleted
Each trigger can include an optional filter function:
recordUpdatedTrigger({
type: order,
condition: ({ newRecord, oldRecord }) =>
newRecord.status === "completed" && oldRecord.status !== "completed",
});Schedule Trigger
Fires on a cron schedule:
scheduleTrigger({ cron: "*/5 * * * *" }); // Every 5 minutes
scheduleTrigger({ cron: "0 9 * * 1" }); // Every Monday at 9am
scheduleTrigger({ cron: "0 0 1 * *" }); // First day of every month
scheduleTrigger({ cron: "0 * * * *", timezone: "Asia/Tokyo" });Incoming Webhook Trigger
Fires when an external webhook is received:
type WebhookRequest = {
body: WebhookPayload;
headers: Record<string, string>;
};
incomingWebhookTrigger<WebhookRequest>();Resolver Executed Trigger
Fires when a resolver is executed:
resolverExecutedTrigger({
resolver: createOrderResolver,
condition: ({ result, error }) => !error && result?.order?.id,
});IdP User Triggers
Fire when IdP users are created, updated, or deleted:
idpUserCreatedTrigger(): Fires when a new IdP user is createdidpUserUpdatedTrigger(): Fires when an IdP user is updatedidpUserDeletedTrigger(): Fires when an IdP user is deleted
idpUserCreatedTrigger();Auth Access Token Triggers
Fire on auth access token lifecycle events:
authAccessTokenIssuedTrigger(): Fires when a new access token is issuedauthAccessTokenRefreshedTrigger(): Fires when an access token is refreshedauthAccessTokenRevokedTrigger(): Fires when an access token is revoked
authAccessTokenIssuedTrigger();Operation Types
Function Operation
Execute JavaScript/TypeScript functions:
createExecutor({
operation: {
kind: "function",
body: async ({ newRecord }) => {
console.log("New record created:", newRecord);
},
},
});Job Function Operation
For long-running operations, use jobFunction which runs asynchronously and supports extended execution times. See Job Function Operation for details.
import { createExecutor, scheduleTrigger } from "@tailor-platform/sdk";
import { getDB } from "../generated/tailordb";
export default createExecutor({
name: "daily-report-generator",
description: "Generate daily reports",
trigger: scheduleTrigger({ cron: "0 0 * * *" }),
operation: {
kind: "jobFunction",
body: async () => {
const db = getDB("tailordb");
// Long-running report generation logic
const records = await db.selectFrom("Order").selectAll().execute();
// Process records...
},
},
});Webhook Operation
Call external webhooks with dynamic data:
createExecutor({
operation: {
kind: "webhook",
url: ({ typeName }) => `https://api.example.com/webhooks/${typeName}`,
headers: {
"Content-Type": "application/json",
"X-API-Key": { vault: "api-keys", key: "external-api" },
},
requestBody: ({ newRecord }) => ({
id: newRecord.id,
timestamp: new Date(),
data: newRecord,
}),
},
});GraphQL Operation
Execute GraphQL queries and mutations:
createExecutor({
operation: {
kind: "graphql",
appName: "my-app",
query: `
mutation UpdateUserStatus($id: ID!, $status: String!) {
updateUser(id: $id, input: { status: $status }) {
id
status
updatedAt
}
}
`,
variables: ({ newRecord }) => ({
id: newRecord.userId,
status: "active",
}),
},
});Workflow Operation
Trigger workflows from executors. See Workflow documentation for how to define workflows.
import { createExecutor, recordCreatedTrigger } from "@tailor-platform/sdk";
import { order } from "../tailordb/order";
import processOrderWorkflow from "../workflows/process-order";
export default createExecutor({
name: "order-processor",
description: "Process new orders via workflow",
trigger: recordCreatedTrigger({ type: order }),
operation: {
kind: "workflow",
workflow: processOrderWorkflow,
args: ({ newRecord }) => ({
orderId: newRecord.id,
customerId: newRecord.customerId,
}),
},
});You can also pass static arguments:
createExecutor({
operation: {
kind: "workflow",
workflow: dailyReportWorkflow,
args: { reportType: "summary" },
},
});Authentication for Operations
GraphQL and Workflow operations can specify an authInvoker to execute with machine user credentials:
import { defineAuth, createExecutor, scheduleTrigger } from "@tailor-platform/sdk";
const auth = defineAuth("my-auth", {
// ... auth configuration
machineUsers: {
"batch-processor": {
attributes: { role: "ADMIN" },
},
},
});
export default createExecutor({
name: "scheduled-cleanup",
trigger: scheduleTrigger({ cron: "0 0 * * *" }),
operation: {
kind: "graphql",
query: `mutation { cleanupOldRecords { count } }`,
authInvoker: auth.invoker("batch-processor"),
},
});Event Payloads
Each trigger type provides specific context data in the callback functions.
Record Event Payloads
Record triggers receive context based on the operation type:
Created Event
interface RecordCreatedContext<T> {
workspaceId: string; // Workspace identifier
appNamespace: string; // Application/namespace name
typeName: string; // TailorDB type name
newRecord: T; // The newly created record
}Updated Event
interface RecordUpdatedContext<T> {
workspaceId: string;
appNamespace: string;
typeName: string;
oldRecord: T; // Previous record state
newRecord: T; // Current record state
}Deleted Event
interface RecordDeletedContext<T> {
workspaceId: string;
appNamespace: string;
typeName: string;
oldRecord: T; // The deleted record
}Usage Example:
import { createExecutor, recordUpdatedTrigger, t } from "@tailor-platform/sdk";
import { order } from "../tailordb/order";
export default createExecutor({
name: "order-status-changed",
trigger: recordUpdatedTrigger({
type: order,
condition: ({ oldRecord, newRecord }) => oldRecord.status !== newRecord.status,
}),
operation: {
kind: "function",
body: async ({ oldRecord, newRecord, typeName }) => {
console.log(`${typeName} status changed:`);
console.log(` From: ${oldRecord.status}`);
console.log(` To: ${newRecord.status}`);
},
},
});Schedule Event Payload
Schedule triggers receive minimal context:
interface ScheduleContext {
scheduledTime: string; // ISO 8601 timestamp
}Incoming Webhook Payload
Webhook triggers receive HTTP request data:
interface WebhookContext<T = unknown> {
body: T; // Parsed request body
headers: Record<string, string>; // Request headers
method: "POST" | "GET" | "PUT" | "DELETE"; // HTTP method
rawBody: string; // Raw request body as string
}Usage Example:
import { createExecutor, incomingWebhookTrigger } from "@tailor-platform/sdk";
interface StripeWebhook {
type: string;
data: { object: { id: string; amount: number } };
}
export default createExecutor({
name: "stripe-webhook",
trigger: incomingWebhookTrigger<{
body: StripeWebhook;
headers: { "stripe-signature": string };
}>(),
operation: {
kind: "function",
body: async ({ body, headers }) => {
const signature = headers["stripe-signature"];
console.log(`Received ${body.type} event`);
// Process webhook...
},
},
});Resolver Executed Payload
Resolver triggers receive the resolver's result or error:
interface ResolverExecutedContext<TResult> {
workspaceId: string; // Workspace identifier
appNamespace: string; // Application/namespace name
resolverName: string; // Name of the executed resolver
result?: TResult; // Return value (on success)
error?: string; // Error message (on failure)
}Usage Example:
import { createExecutor, resolverExecutedTrigger } from "@tailor-platform/sdk";
import { createOrderResolver } from "../resolvers/create-order";
export default createExecutor({
name: "order-created-notification",
trigger: resolverExecutedTrigger({
resolver: createOrderResolver,
condition: ({ result, error }) => !error && !!result?.order,
}),
operation: {
kind: "function",
body: async ({ result, resolverName }) => {
console.log(`${resolverName} completed successfully`);
console.log(`Order ID: ${result.order.id}`);
},
},
});IdP User Event Payload
IdP user triggers receive user context:
interface IdpUserContext {
namespaceName: string; // IdP namespace name
userId: string; // The affected user ID
}Auth Access Token Event Payload
Auth access token triggers receive token context:
interface AuthAccessTokenContext {
namespaceName: string; // Auth namespace name
userId: string; // The user associated with the token
}