End-to-end Type Safety
Full TypeScript inference from contract to client, workflows, and activities. No manual type annotations needed.
End-to-end type safety and automatic validation for workflows and activities
Working with Temporal.io workflows is powerful, but comes with challenges:
// โ No type safety
const result = await client.workflow.execute('processOrder', {
workflowId: 'order-123',
taskQueue: 'orders',
args: [{ orderId: 'ORD-123' }], // What fields? What types?
});
console.log(result.status); // unknown type, no autocomplete
// โ Manual validation everywhere
// โ Runtime errors from wrong data
// โ Scattered activity definitionstemporal-contract transforms your Temporal workflows with a contract-first approach:
// โ
Define once
const contract = defineContract({
taskQueue: 'orders',
workflows: {
processOrder: {
input: z.object({ orderId: z.string(), customerId: z.string() }),
output: z.object({ status: z.enum(['success', 'failed']), transactionId: z.string() }),
activities: { /* ... */ }
}
}
});
// โ
Type-safe client
const result = await client.executeWorkflow('processOrder', {
workflowId: 'order-123',
args: { orderId: 'ORD-123', customerId: 'CUST-456' }, // TypeScript knows!
});
console.log(result.status); // 'success' | 'failed' โ full autocomplete!See how easy it is to get started with a complete workflow:
import { defineContract } from '@temporal-contract/contract';
import { z } from 'zod';
// โ
Define your contract once
export const orderContract = defineContract({
taskQueue: 'orders',
workflows: {
processOrder: {
input: z.object({
orderId: z.string(),
customerId: z.string(),
amount: z.number()
}),
output: z.object({
status: z.enum(['success', 'failed']),
transactionId: z.string().optional()
}),
activities: {
processPayment: {
input: z.object({
customerId: z.string(),
amount: z.number()
}),
output: z.object({ transactionId: z.string() }),
},
sendNotification: {
input: z.object({
customerId: z.string(),
message: z.string()
}),
output: z.void(),
},
},
},
},
});import { Future, Result } from '@temporal-contract/boxed';
import { declareActivitiesHandler, ActivityError } from '@temporal-contract/worker/activity';
import { orderContract } from './contract';
// โ
Implement activities with full type safety
// Note: Replace paymentService and notificationService with your actual service implementations
export const activities = declareActivitiesHandler({
contract: orderContract,
activities: {
processOrder: {
processPayment: ({ customerId, amount }) => {
// Replace paymentService with your actual payment service
return Future.fromPromise(
paymentService.charge(customerId, amount)
).mapOk((transaction) => ({ transactionId: transaction.id }))
.mapError((error) =>
new ActivityError(
'PAYMENT_FAILED',
error instanceof Error ? error.message : 'Payment processing failed',
error
)
);
},
sendNotification: ({ customerId, message }) => {
// Replace notificationService with your actual notification service
return Future.fromPromise(
notificationService.send(customerId, message)
).mapError((error) =>
new ActivityError(
'NOTIFICATION_FAILED',
error instanceof Error ? error.message : 'Failed to send notification',
error
)
);
},
},
},
});import { declareWorkflow } from '@temporal-contract/worker/workflow';
import { orderContract } from './contract';
// โ
Type-safe workflow orchestration
export const processOrder = declareWorkflow({
workflowName: 'processOrder',
contract: orderContract,
implementation: async (context, { orderId, customerId, amount }) => {
// Activities are fully typed!
const { transactionId } = await context.activities.processPayment({
customerId,
amount,
});
await context.activities.sendNotification({
customerId,
message: `Order ${orderId} confirmed!`,
});
return { status: 'success', transactionId };
},
});import { NativeConnection } from '@temporalio/worker';
import { createWorker } from '@temporal-contract/worker/worker';
import { orderContract } from './contract';
import { activities } from './activities';
// โ
Start the worker
const connection = await NativeConnection.connect({
address: 'localhost:7233',
});
const worker = await createWorker({
contract: orderContract,
connection,
namespace: 'default',
workflowsPath: './workflows',
activities,
});
await worker.run(); // Worker is now listening!import { TypedClient } from '@temporal-contract/client';
import { orderContract } from './contract';
// โ
Type-safe client calls
const client = TypedClient.create(orderContract, { connection });
const result = await client.executeWorkflow('processOrder', {
workflowId: 'order-123',
args: {
orderId: 'ORD-123',
customerId: 'CUST-456',
amount: 99.99,
},
});
// Full autocomplete on result!
console.log(result.status); // 'success' | 'failed'
console.log(result.transactionId); // string | undefinedWith temporal-contract, you get a complete, type-safe workflow system:
All parts work together seamlessly with end-to-end type safety!