inngest-durable-functions

安装量: 227
排名: #3844

安装

npx skills add https://github.com/inngest/inngest-skills --skill inngest-durable-functions

Inngest Durable Functions Master Inngest's durable execution model for building fault-tolerant, long-running workflows. This skill covers the complete lifecycle from triggers to error handling. These skills are focused on TypeScript. For Python or Go, refer to the Inngest documentation for language-specific guidance. Core concepts apply across all languages. Core Concepts You Need to Know Durable Execution Model Each step should encapsulate side-effects and non-deterministic code Memoization prevents re-execution of completed steps State persistence survives infrastructure failures Automatic retries with configurable retry count Step Execution Flow // ❌ BAD: Non-deterministic logic outside steps async ( { event , step } ) => { const timestamp = Date . now ( ) ; // This runs multiple times! const result = await step . run ( "process-data" , ( ) => { return processData ( event . data ) ; } ) ; } ; // ✅ GOOD: All non-deterministic logic in steps async ( { event , step } ) => { const result = await step . run ( "process-with-timestamp" , ( ) => { const timestamp = Date . now ( ) ; // Only runs once return processData ( event . data , timestamp ) ; } ) ; } ; Function Limits Every Inngest function has these hard limits: Maximum 1,000 steps per function run Maximum 4MB returned data for each step Maximum 32MB combined function run state including, event data, step output, and function output Each step = separate HTTP request (~50-100ms overhead) If you're hitting these limits, break your function into smaller functions connected via step.invoke() or step.sendEvent() . When to Use Steps Always wrap in step.run() : API calls and network requests Database reads and writes File I/O operations Any non-deterministic operation Anything you want retried independently on failure Never wrap in step.run() : Pure calculations and data transformations Simple validation logic Deterministic operations with no side effects Logging (use outside steps) Function Creation Basic Function Structure const processOrder = inngest . createFunction ( { id : "process-order" , // Unique, never change this retries : 4 , // Default: 4 retries per step concurrency : 10 // Max concurrent executions } , { event : "order/created" } , // Trigger async ( { event , step } ) => { // Your durable workflow } ) ; Step IDs and Memoization // Step IDs can be reused - Inngest handles counters automatically const data = await step . run ( "fetch-data" , ( ) => fetchUserData ( ) ) ; const more = await step . run ( "fetch-data" , ( ) => fetchOrderData ( ) ) ; // Different execution // Use descriptive IDs for clarity await step . run ( "validate-payment" , ( ) => validatePayment ( event . data . paymentId ) ) ; await step . run ( "charge-customer" , ( ) => chargeCustomer ( event . data ) ) ; await step . run ( "send-confirmation" , ( ) => sendEmail ( event . data . email ) ) ; Triggers and Events Event Triggers // Single event trigger { event : "user/signup" } // Event with conditional filter { event : "user/action" , if : 'event.data.action == "purchase" && event.data.amount > 100' } // Multiple triggers (up to 10) [ { event : "user/signup" } , { event : "user/login" , if : 'event.data.firstLogin == true' } , { cron : "0 9 * * " } // Daily at 9 AM ] Cron Triggers // Basic cron { cron : "0 /6 * * " ; } // Every 6 hours // With timezone { cron : "TZ=Europe/Paris 0 12 * * 5" ; } // Fridays at noon Paris time // Combine with events [ { event : "manual/report.requested" } , { cron : "0 0 * * 0" } // Weekly on Sunday ] ; Function Invocation // Invoke another function as a step const result = await step . invoke ( "generate-report" , { function : generateReportFunction , data : { userId : event . data . userId } } ) ; // Use returned data await step . run ( "process-report" , ( ) => { return processReport ( result ) ; } ) ; Idempotency Strategies Event-Level Idempotency (Producer Side) // Prevent duplicate events with custom ID await inngest . send ( { id : checkout-completed- ${ cartId } , // 24-hour deduplication name : "cart/checkout.completed" , data : { cartId , email : "user@example.com" } } ) ; Function-Level Idempotency (Consumer Side) const sendEmail = inngest . createFunction ( { id : "send-checkout-email" , // Only run once per cartId per 24 hours idempotency : "event.data.cartId" } , { event : "cart/checkout.completed" } , async ( { event , step } ) => { // This function won't run twice for same cartId } ) ; // Complex idempotency keys const processUserAction = inngest . createFunction ( { id : "process-user-action" , // Unique per user + organization combination idempotency : 'event.data.userId + "-" + event.data.organizationId' } , { event : "user/action.performed" } , async ( { event , step } ) => { / ... / } ) ; Cancellation Patterns Event-Based Cancellation In expressions, event = the original triggering event, async = the new event being matched. See Expression Syntax Reference for full details. const processOrder = inngest . createFunction ( { id : "process-order" , cancelOn : [ { event : "order/cancelled" , if : "event.data.orderId == async.data.orderId" } ] } , { event : "order/created" } , async ( { event , step } ) => { await step . sleepUntil ( "wait-for-payment" , event . data . paymentDue ) ; // Will be cancelled if order/cancelled event received await step . run ( "charge-payment" , ( ) => processPayment ( event . data ) ) ; } ) ; Timeout Cancellation const processWithTimeout = inngest . createFunction ( { id : "process-with-timeout" , timeouts : { start : "5m" , // Cancel if not started within 5 minutes finish : "30m" // Cancel if not finished within 30 minutes } } , { event : "long/process.requested" } , async ( { event , step } ) => { / ... */ } ) ; Handling Cancellation Cleanup // Listen for cancellation events const cleanupCancelled = inngest . createFunction ( { id : "cleanup-cancelled-process" } , { event : "inngest/function.cancelled" } , async ( { event , step } ) => { if ( event . data . function_id === "process-order" ) { await step . run ( "cleanup-resources" , ( ) => { return cleanupOrderResources ( event . data . run_id ) ; } ) ; } } ) ; Error Handling and Retries Default Retry Behavior 5 total attempts (1 initial + 4 retries) per step Exponential backoff with jitter Independent retry counters per step Custom Retry Configuration const reliableFunction = inngest . createFunction ( { id : "reliable-function" , retries : 10 // Up to 10 retries per step } , { event : "critical/task" } , async ( { event , step , attempt } ) => { // attempt is the function-level attempt counter (0-indexed) // It tracks retries for the currently executing step, not the overall function if ( attempt

5 ) { // Different logic for later attempts of the current step } } ) ; Non-Retriable Errors Prevent retries for code that won't succeed upon retry. import { NonRetriableError } from "inngest" ; const processUser = inngest . createFunction ( { id : "process-user" } , { event : "user/process.requested" } , async ( { event , step } ) => { const user = await step . run ( "fetch-user" , async ( ) => { const user = await db . users . findOne ( event . data . userId ) ; if ( ! user ) { // Don't retry - user doesn't exist throw new NonRetriableError ( "User not found, stopping execution" ) ; } return user ; } ) ; // Continue processing... } ) ; Custom Retry Timing import { RetryAfterError } from "inngest" ; const respectRateLimit = inngest . createFunction ( { id : "api-call" } , { event : "api/call.requested" } , async ( { event , step } ) => { await step . run ( "call-api" , async ( ) => { const response = await externalAPI . call ( event . data ) ; if ( response . status === 429 ) { // Retry after specific time from API const retryAfter = response . headers [ "retry-after" ] ; throw new RetryAfterError ( "Rate limited" , ${ retryAfter } s ) ; } return response . data ; } ) ; } ) ; Logging Best Practices Proper Logging Setup import winston from "winston" ; // Configure logger const logger = winston . createLogger ( { level : "info" , format : winston . format . json ( ) , transports : [ new winston . transports . Console ( ) ] } ) ; const inngest = new Inngest ( { id : "my-app" , logger // Pass logger to client } ) ; Function Logging Patterns const processData = inngest . createFunction ( { id : "process-data" } , { event : "data/process.requested" } , async ( { event , step , logger } ) => { // ✅ GOOD: Log inside steps to avoid duplicates const result = await step . run ( "fetch-data" , async ( ) => { logger . info ( "Fetching data for user" , { userId : event . data . userId } ) ; return await fetchUserData ( event . data . userId ) ; } ) ; // ❌ AVOID: Logging outside steps can duplicate // logger.info("Processing complete"); // This could run multiple times! await step . run ( "log-completion" , async ( ) => { logger . info ( "Processing complete" , { resultCount : result . length } ) ; } ) ; } ) ; Performance Optimization Checkpointing // Enable checkpointing for lower latency const realTimeFunction = inngest . createFunction ( { id : "real-time-function" , checkpointing : { maxRuntime : "5m" , // Max continuous execution time bufferedSteps : 2 , // Buffer 2 steps before checkpointing maxInterval : "10s" // Max wait before checkpoint } } , { event : "realtime/process" } , async ( { event , step } ) => { // Steps execute immediately with periodic checkpointing const result1 = await step . run ( "step-1" , ( ) => process1 ( event . data ) ) ; const result2 = await step . run ( "step-2" , ( ) => process2 ( result1 ) ) ; return { result2 } ; } ) ; Advanced Patterns Conditional Step Execution const conditionalProcess = inngest . createFunction ( { id : "conditional-process" } , { event : "process/conditional" } , async ( { event , step } ) => { const userData = await step . run ( "fetch-user" , ( ) => { return getUserData ( event . data . userId ) ; } ) ; // Conditional step execution if ( userData . isPremium ) { await step . run ( "premium-processing" , ( ) => { return processPremiumFeatures ( userData ) ; } ) ; } // Always runs await step . run ( "standard-processing" , ( ) => { return processStandardFeatures ( userData ) ; } ) ; } ) ; Error Recovery Patterns const robustProcess = inngest . createFunction ( { id : "robust-process" } , { event : "process/robust" } , async ( { event , step } ) => { let primaryResult ; try { primaryResult = await step . run ( "primary-service" , ( ) => { return callPrimaryService ( event . data ) ; } ) ; } catch ( error ) { // Fallback to secondary service primaryResult = await step . run ( "fallback-service" , ( ) => { return callSecondaryService ( event . data ) ; } ) ; } return { result : primaryResult } ; } ) ; Common Mistakes to Avoid ❌ Non-deterministic code outside steps ❌ Database calls outside steps ❌ Logging outside steps (causes duplicates) ❌ Changing step IDs after deployment ❌ Not handling NonRetriableError cases ❌ Ignoring idempotency for critical functions Next Steps See inngest-steps for detailed step method reference See references/step-execution.md for detailed step patterns See references/error-handling.md for comprehensive error strategies See references/observability.md for monitoring and tracing setup See references/checkpointing.md for performance optimization details This skill covers Inngest's durable function patterns. For event sending and webhook handling, see the inngest-events skill.

返回排行榜