Files
gh-jezweb-claude-skills-ski…/templates/workflow-with-retries.ts
2025-11-30 08:24:41 +08:00

236 lines
6.2 KiB
TypeScript

/**
* Workflow with Advanced Retry Configuration
*
* Demonstrates:
* - Custom retry limits
* - Exponential, linear, and constant backoff
* - Step timeouts
* - NonRetryableError for terminal failures
* - Error handling with try-catch
*/
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
import { NonRetryableError } from 'cloudflare:workflows';
type Env = {
MY_WORKFLOW: Workflow;
};
type PaymentParams = {
orderId: string;
amount: number;
customerId: string;
};
/**
* Payment Processing Workflow with Retries
*
* Handles payment processing with:
* - Validation with NonRetryableError
* - Retry logic for payment gateway
* - Fallback to backup gateway
* - Graceful error handling
*/
export class PaymentWorkflow extends WorkflowEntrypoint<Env, PaymentParams> {
async run(event: WorkflowEvent<PaymentParams>, step: WorkflowStep) {
const { orderId, amount, customerId } = event.payload;
// Step 1: Validate input (no retries - fail fast)
await step.do(
'validate payment request',
{
retries: {
limit: 0 // No retries for validation
}
},
async () => {
if (!orderId || !customerId) {
throw new NonRetryableError('Missing required fields: orderId or customerId');
}
if (amount <= 0) {
throw new NonRetryableError(`Invalid amount: ${amount}`);
}
if (amount > 100000) {
throw new NonRetryableError(`Amount exceeds limit: ${amount}`);
}
return { valid: true };
}
);
// Step 2: Call primary payment gateway (exponential backoff)
let paymentResult;
try {
paymentResult = await step.do(
'charge primary payment gateway',
{
retries: {
limit: 5, // Max 5 retry attempts
delay: '10 seconds', // Start at 10 seconds
backoff: 'exponential' // 10s, 20s, 40s, 80s, 160s
},
timeout: '2 minutes' // Each attempt times out after 2 minutes
},
async () => {
const response = await fetch('https://primary-payment-gateway.example.com/charge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
orderId,
amount,
customerId
})
});
if (!response.ok) {
// Check if error is retryable
if (response.status === 401 || response.status === 403) {
throw new NonRetryableError('Authentication failed with payment gateway');
}
throw new Error(`Payment gateway error: ${response.status}`);
}
const data = await response.json();
return {
transactionId: data.transactionId,
status: data.status,
gateway: 'primary'
};
}
);
} catch (error) {
console.error('Primary gateway failed:', error);
// Step 3: Fallback to backup gateway (linear backoff)
paymentResult = await step.do(
'charge backup payment gateway',
{
retries: {
limit: 3,
delay: '30 seconds',
backoff: 'linear' // 30s, 60s, 90s
},
timeout: '3 minutes'
},
async () => {
const response = await fetch('https://backup-payment-gateway.example.com/charge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
orderId,
amount,
customerId
})
});
if (!response.ok) {
throw new Error(`Backup gateway error: ${response.status}`);
}
const data = await response.json();
return {
transactionId: data.transactionId,
status: data.status,
gateway: 'backup'
};
}
);
}
// Step 4: Update order status (constant backoff)
await step.do(
'update order status',
{
retries: {
limit: 10,
delay: '5 seconds',
backoff: 'constant' // Always 5 seconds between retries
},
timeout: '30 seconds'
},
async () => {
const response = await fetch(`https://api.example.com/orders/${orderId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
status: 'paid',
transactionId: paymentResult.transactionId,
gateway: paymentResult.gateway
})
});
if (!response.ok) {
throw new Error('Failed to update order status');
}
return { updated: true };
}
);
// Step 5: Send confirmation (optional - don't fail workflow if this fails)
try {
await step.do(
'send payment confirmation',
{
retries: {
limit: 3,
delay: '10 seconds',
backoff: 'exponential'
}
},
async () => {
await fetch('https://api.example.com/notifications/payment-confirmed', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
orderId,
customerId,
amount
})
});
return { sent: true };
}
);
} catch (error) {
// Log but don't fail workflow
console.error('Failed to send confirmation:', error);
}
return {
orderId,
transactionId: paymentResult.transactionId,
gateway: paymentResult.gateway,
status: 'complete'
};
}
}
export default {
async fetch(req: Request, env: Env): Promise<Response> {
const url = new URL(req.url);
if (url.pathname.startsWith('/favicon')) {
return Response.json({}, { status: 404 });
}
// Create payment workflow
const instance = await env.MY_WORKFLOW.create({
params: {
orderId: 'ORD-' + Date.now(),
amount: 99.99,
customerId: 'CUST-123'
}
});
return Response.json({
id: instance.id,
status: await instance.status()
});
}
};