### Install Gostage Source: https://github.com/davidroman0o/gostage/blob/main/guide/01-quickstart.md Install the gostage library using go get. ```bash go get github.com/davidroman0O/gostage ``` -------------------------------- ### Minimum Viable Workflow Example Source: https://github.com/davidroman0o/gostage/blob/main/guide/01-quickstart.md A complete example demonstrating the five essential steps: registering a task, building a workflow, creating an engine, running the workflow, and reading the result. ```go package main import ( "context" "fmt" "log" gs "github.com/davidroman0O/gostage" ) func main() { // 1. Register a task gs.Task("greet", func(ctx *gs.Ctx) error { name := gs.GetOr[string](ctx, "name", "World") return gs.Set(ctx, "greeting", fmt.Sprintf("Hello, %s!", name)) }) // 2. Build a workflow wf, err := gs.NewWorkflow("hello").Step("greet").Commit() if err != nil { log.Fatal(err) } // 3. Create an engine engine, err := gs.New() if err != nil { log.Fatal(err) } defer engine.Close() // 4. Run the workflow result, err := engine.RunSync(context.Background(), wf, gs.Params{"name": "Alice"}) if err != nil { log.Fatal(err) } // 5. Read the result greeting, _ := gs.ResultGet[string](result, "greeting") fmt.Println(greeting) // Hello, Alice! } ``` -------------------------------- ### Import and Use Spawn Package Source: https://github.com/davidroman0o/gostage/blob/main/guide/07-spawn.md Import the 'spawn' package and initialize the gostage engine with the 'WithSpawn' option. This is necessary if a ForEach step uses spawning without a SpawnRunner installed. ```go import "github.com/davidroman0O/gostage/spawn" engine, _ := gostage.New(spawn.WithSpawn()) ``` -------------------------------- ### LoggingPlugin Engine Middleware Example Source: https://github.com/davidroman0o/gostage/blob/main/guide/08-middleware.md This is an example of the engine-level middleware provided by the LoggingPlugin. It logs workflow start, completion, or failure with execution duration. ```go func (p *loggingPlugin) EngineMiddleware() EngineMiddleware { return func(ctx context.Context, wf *Workflow, runID RunID, next func() error) error { p.logger.Info("workflow %s run %s started", wf.ID, runID) start := time.Now() err := next() dur := time.Since(start) if err != nil { p.logger.Error("workflow %s run %s failed after %s: %v", wf.ID, runID, dur, err) } else { p.logger.Info("workflow %s run %s completed in %s", wf.ID, runID, dur) } return err } } ``` -------------------------------- ### Implement Engine Middleware for Run Logging Source: https://github.com/davidroman0o/gostage/blob/main/guide/08-middleware.md An example of engine middleware that logs the start and potential failure of a workflow run. It wraps the call to `next()` to capture errors. ```go gostage.WithEngineMiddleware(func(ctx context.Context, wf *gostage.Workflow, runID gostage.RunID, next func() error) error { log.Printf("run %s starting", runID) err := next() if err != nil { log.Printf("run %s failed: %v", runID, err) } return err }) ``` -------------------------------- ### Multi-Cycle Suspend and Resume Example Source: https://github.com/davidroman0o/gostage/blob/main/guide/05-suspend-resume.md Illustrates a workflow that suspends multiple times, requiring repeated `Resume` calls with different parameters. Each cycle persists state, ensuring durability across resumes. ```go result, _ := engine.RunSync(ctx, wf, nil) // -> suspended result, _ = engine.Resume(ctx, result.RunID, gostage.Params{"approved": true}) // -> suspended again result, _ = engine.Resume(ctx, result.RunID, gostage.Params{"approved": true}) // -> completed ``` -------------------------------- ### Implement Step Middleware with Timing Source: https://github.com/davidroman0o/gostage/blob/main/guide/08-middleware.md Example of a Step middleware that logs the duration of a step's execution. It calls `next()` to continue the workflow and logs the time taken after `next()` returns. ```go func timingMW(ctx context.Context, info gostage.StepInfo, runID gostage.RunID, next func() error) error { start := time.Now() err := next() log.Printf("step %s took %s", info.ID, time.Since(start)) return err } ``` -------------------------------- ### Spawn Child Processes for Tasks in Go Source: https://context7.com/davidroman0o/gostage/llms.txt Use `spawn.IsChild` and `spawn.HandleChild` at the start of main to detect and handle child process invocations. This pattern is useful for isolating CPU-heavy work. ```go import ( "github.com/davidroman0O/gostage" "github.com/davidroman0O/gostage/spawn" ) func registerTasks() { gostage.Task("process.image", func(ctx *gostage.Ctx) error { url := gostage.Item[string](ctx) idx := gostage.ItemIndex(ctx) // CPU-heavy work in isolated process return gostage.Set(ctx, fmt.Sprintf("result_%d", idx), "processed:"+url) }) } func main() { // MUST be first: detect child invocation before any other setup if spawn.IsChild() { registerTasks() spawn.HandleChild() // never returns; calls os.Exit return } registerTasks() engine, _ := gostage.New( gostage.WithSQLite("app.db"), spawn.WithSpawn(), // install the spawn runner ) defer engine.Close() wf, _ := gostage.NewWorkflow("image-pipeline"). ForEach("image_urls", gostage.Step("process.image"), gostage.WithConcurrency(4), // up to 4 child processes at once gostage.WithSpawn(), // use child processes instead of goroutines ). Commit() result, _ := engine.RunSync(context.Background(), wf, gostage.Params{ "image_urls": []string{ "https://example.com/img1.jpg", "https://example.com/img2.jpg", }, }) fmt.Println(result.Status) // completed r0, _ := gostage.ResultGet[string](result, "result_0") fmt.Println(r0) // processed:https://example.com/img1.jpg } ``` -------------------------------- ### Registering and Using Named Functions for Resumability Source: https://github.com/davidroman0o/gostage/blob/main/guide/05-suspend-resume.md This example shows how to register functions (MapFn, Condition) by name and then use these named references in the workflow definition to ensure it is serializable and resumable. ```go gostage.MapFn("transform-data", func(ctx *gostage.Ctx) error { // ... return nil }) gostage.Condition("is-ready", func(ctx *gostage.Ctx) bool { return gostage.Get[string](ctx, "status") == "ready" }) wf, _ := gostage.NewWorkflow("resumable"). MapNamed("transform-data"). Step("wait-for-approval"). DoUntilNamed(gostage.Step("poll"), "is-ready"). Branch( gostage.WhenNamed("is-high-priority").Step("urgent"), gostage.Default().Step("normal"), ). Commit() ``` -------------------------------- ### Register a Task with State Management Source: https://github.com/davidroman0o/gostage/blob/main/guide/01-quickstart.md Register a task that reads and writes data using the context's key-value store. Use Get, Set, GetOr, and GetOk for state management. ```go gs.Task("validate-order", func(ctx *gs.Ctx) error { orderID := gs.Get[string](ctx, "order_id") if orderID == "" { return gs.Bail(ctx, "missing order_id") } ctx.Log.Info("Order %s is valid", orderID) return nil }) ``` -------------------------------- ### Production Engine Configuration Source: https://github.com/davidroman0o/gostage/blob/main/guide/12-production.md This example demonstrates a typical production configuration for the gostage engine, combining various options for durable storage, recovery, garbage collection, state limits, worker pool sizing, timeouts, caching, and logging. ```APIDOC ## Production Engine Configuration ### Description Initializes the gostage engine with production-ready configurations, including storage, recovery, garbage collection, state limits, worker pool size, timeouts, cache size, and logging. ### Method ```go engine, err := gostage.New( gostage.WithSQLite("/var/lib/myapp/workflows.db"), gostage.WithAutoRecover(), gostage.WithRunGC(7*24*time.Hour, 10*time.Minute), gostage.WithStateLimit(1000), gostage.WithWorkerPoolSize(32), gostage.WithTimeout(10*time.Minute), gostage.WithShutdownTimeout(30*time.Second), gostage.WithCacheSize(200), gostage.WithLogger(myLogger), ) if err != nil { log.Fatalf("engine init: %v", err) } defer engine.Close() ``` ### Options - **gostage.WithSQLite(path string)**: Configures durable storage using SQLite. - **gostage.WithAutoRecover()**: Enables automatic recovery of interrupted runs on startup. - **gostage.WithRunGC(cutoff time.Duration, interval time.Duration)**: Sets up garbage collection for old terminal runs. - **gostage.WithStateLimit(limit int)**: Prevents unbounded state growth by setting a state limit. - **gostage.WithWorkerPoolSize(size int)**: Sizes the worker pool for expected concurrency. - **gostage.WithTimeout(timeout time.Duration)**: Sets a global safety net timeout. - **gostage.WithShutdownTimeout(timeout time.Duration)**: Specifies a timeout for graceful shutdown. - **gostage.WithCacheSize(size int)**: Right-sizes the workflow cache. - **gostage.WithLogger(logger Logger)**: Integrates a custom logger for operational visibility. ``` -------------------------------- ### Implement Asynchronous Event Handling Source: https://github.com/davidroman0o/gostage/blob/main/guide/09-events-observability.md Dispatch event handling logic to a channel to avoid blocking the event goroutine. This example drops events if the channel is full to maintain responsiveness. ```go func (n *asyncNotifier) OnEvent(event gostage.EngineEvent) { select { case n.ch <- event: default: // drop if full } } ``` -------------------------------- ### Attach Workflow-Scoped Step Middleware Source: https://github.com/davidroman0o/gostage/blob/main/guide/08-middleware.md Example of attaching step middleware specifically to a workflow using `WithWorkflowMiddleware`. This middleware will run for every step within the 'monitored' workflow. ```go wf, _ := gostage.NewWorkflow("monitored", gostage.WithWorkflowMiddleware(timingMW), ).Step("fetch").Step("transform").Build() ``` -------------------------------- ### Start Automatic Garbage Collection Source: https://github.com/davidroman0o/gostage/blob/main/guide/12-production.md Enable background garbage collection for terminal runs. Configure the time-to-live (TTL) for runs to be deleted and the interval at which the GC process runs. ```go engine, _ := gostage.New( gostage.WithSQLite("app.db"), gostage.WithRunGC(24*time.Hour, 5*time.Minute), ) ``` -------------------------------- ### Register Gostage Tasks and Workflow Source: https://context7.com/davidroman0o/gostage/llms.txt Register named tasks, conditions, and map functions with optional retry, timeout, and tags. This example demonstrates building and running a simple workflow using these registered components. ```go package main import ( "context" "fmt" "log" "time" gs "github.com/davidroman0O/gostage" ) func main() { // Register with retry and per-task timeout gs.Task("call-api", func(ctx *gs.Ctx) error { userID := gs.Get[string](ctx, "user_id") // simulate API call result := fmt.Sprintf("data-for-%s", userID) return gs.Set(ctx, "api_result", result) }, gs.WithRetry(3), gs.WithRetryStrategy(gs.ExponentialBackoffWithJitter(100*time.Millisecond, 10*time.Second)), gs.WithTaskTimeout(30*time.Second), gs.WithTags("external", "billing"), ) // Register a condition for use in Branch / DoUntil / DoWhile gs.Condition("is-premium", func(ctx *gs.Ctx) bool { return gs.Get[string](ctx, "tier") == "premium" }) // Register a named map function for serializable workflows gs.MapFn("normalize", func(ctx *gs.Ctx) error { raw := gs.Get[string](ctx, "api_result") return gs.Set(ctx, "normalized", fmt.Sprintf("[%s]", raw)) }) wf, err := gs.NewWorkflow("demo"). Step("call-api"). MapNamed("normalize"). Branch( gs.WhenNamed("is-premium").Step("call-api"), gs.Default().Step("call-api"), ). Commit() if err != nil { log.Fatal(err) } engine, _ := gs.New() defer engine.Close() result, _ := engine.RunSync(context.Background(), wf, gs.Params{"user_id": "u42", "tier": "premium"}) fmt.Println(result.Status) // completed v, _ := gs.ResultGet[string](result, "normalized") fmt.Println(v) // [data-for-u42] } ``` -------------------------------- ### Basic Workflow with Single Steps Source: https://github.com/davidroman0o/gostage/blob/main/guide/02-step-kinds.md Demonstrates the creation of a basic workflow using sequential 'Single' steps. Tasks do not need to be registered during build time. ```go wf, _ := gs.NewWorkflow("pipeline"). Step("validate"). Step("process"). Step("notify"). Commit() ``` -------------------------------- ### Read State with Get Source: https://github.com/davidroman0o/gostage/blob/main/guide/03-state.md Retrieve values from the state store. If a key is missing, `Get` returns the zero value for the specified type. ```go name := gs.Get[string](ctx, "name") // "" if missing count := gs.Get[int](ctx, "count") // 0 if missing items := gs.Get[[]string](ctx, "items") // nil if missing ``` -------------------------------- ### Define and Run a Simple Workflow Source: https://github.com/davidroman0o/gostage/blob/main/README.md Defines a basic 'greet' task and runs a workflow synchronously. Use this for straightforward, linear workflows. ```go gostage.Task("greet", func(ctx *gostage.Ctx) error { name := gostage.GetOr[string](ctx, "name", "World") return gostage.Set(ctx, "greeting", "Hello, "+name+"!") }) wf, _ := gostage.NewWorkflow("hello").Step("greet").Commit() engine, _ := gostage.New() defer engine.Close() result, _ := engine.RunSync(ctx, wf, gostage.Params{"name": "Alice"}) fmt.Println(result.Store["greeting"]) ``` -------------------------------- ### Read Workflow Result State Source: https://github.com/davidroman0o/gostage/blob/main/guide/03-state.md Access state values from a completed workflow's result. `ResultGet` performs type coercion similar to `Get`. ```go result, _ := engine.RunSync(ctx, wf, params) total, ok := gs.ResultGet[int](result, "order_total") ``` -------------------------------- ### Integrating Custom Persistence Backend Source: https://github.com/davidroman0o/gostage/blob/main/guide/04-persistence.md Shows how to initialize the gostage engine with a custom persistence backend by passing an implementation of the Persistence interface using WithPersistence. ```go engine, err := gostage.New(gostage.WithPersistence(myPostgresBackend)) ``` -------------------------------- ### Get current run state Source: https://github.com/davidroman0o/gostage/blob/main/guide/12-production.md Use GetRun to retrieve the current persisted state of a run without blocking. Useful for status polling and health checks. ```go run, err := engine.GetRun(ctx, runID) if err != nil { // ErrRunNotFound or ErrEngineClosed } fmt.Println(run.Status, run.WorkflowID, run.UpdatedAt) ``` -------------------------------- ### Build and Run Workflow Synchronously Source: https://context7.com/davidroman0o/gostage/llms.txt Demonstrates building a workflow with tasks and executing it synchronously using `engine.RunSync`. Handles results, errors, and bail conditions. ```go gs.Task("validate", func(ctx *gs.Ctx) error { if gs.Get[string](ctx, "order_id") == "" { return gs.Bail(ctx, "missing order_id") } return nil }) gs.Task("charge", func(ctx *gs.Ctx) error { return gs.Set(ctx, "charged", true) }) gs.Task("notify", func(ctx *gs.Ctx) error { return gs.Set(ctx, "notified", true) }) wf, _ := gs.NewWorkflow("order", gs.WithDefaultRetry(2, time.Second), // workflow-level retry default ). Step("validate"). Step("charge"). Step("notify"). Commit() engine, _ := gs.New() defer engine.Close() result, err := engine.RunSync(context.Background(), wf, gs.Params{"order_id": "ORD-99"}) if err != nil { log.Fatal(err) // engine-level error only } switch result.Status { case gs.Completed: charged, _ := gs.ResultGet[bool](result, "charged") fmt.Println("charged:", charged) // charged: true case gs.Failed: fmt.Println("error:", result.Error) case gs.Bailed: fmt.Println("bail:", result.BailReason) case gs.Suspended: fmt.Println("suspended, run ID:", result.RunID) } ``` -------------------------------- ### Register a Task with Options Source: https://github.com/davidroman0o/gostage/blob/main/guide/01-quickstart.md Configure tasks with options such as retries, retry delays, and timeouts at registration time. ```go gs.Task("call-api", handler, gs.WithRetry(3), gs.WithRetryDelay(2*time.Second), gs.WithTaskTimeout(30*time.Second), ) ``` -------------------------------- ### Building Workflows Before Task Registration Source: https://github.com/davidroman0o/gostage/blob/main/guide/10-registry-isolation.md Demonstrates building a workflow structure before its tasks are registered. Task names are resolved lazily at execution time, not build time, supporting dynamic loading patterns. ```go // Build the workflow first wf, _ := gostage.NewWorkflow("order"). Step("validate"). Step("charge"). Commit() // Register tasks later (perhaps loaded from a plugin) gostage.Task("validate", validateHandler) gostage.Task("charge", chargeHandler) // Run -- tasks are resolved now result, _ := engine.RunSync(ctx, wf, nil) ``` -------------------------------- ### Test Isolation using Default Registry Reset Source: https://github.com/davidroman0o/gostage/blob/main/guide/10-registry-isolation.md Use `ResetTaskRegistry()` in test setup to clear the default registry and its cache, preventing state leakage between test cases. ```go func TestOrderWorkflow(t *testing.T) { gostage.ResetTaskRegistry() gostage.Task("charge", mockChargeHandler) gostage.Condition("is-paid", func(ctx *gostage.Ctx) bool { return true }) // ... build, run, assert } ``` -------------------------------- ### Register Engine Middleware Source: https://github.com/davidroman0o/gostage/blob/main/guide/08-middleware.md Demonstrates how to register multiple engine middleware functions using `gostage.WithEngineMiddleware`. The order of registration determines the execution order, with the first registered middleware being the outermost. ```go engine, _ := gostage.New( gostage.WithEngineMiddleware(loggingMW), // outermost gostage.WithEngineMiddleware(timingMW), // inner gostage.WithEngineMiddleware(retryMW), // innermost ) ``` -------------------------------- ### Create a Durable Engine with SQLite Source: https://github.com/davidroman0o/gostage/blob/main/guide/01-quickstart.md Initialize a gostage engine with SQLite persistence for durable workflows that survive process restarts. ```go engine, err := gs.New(gs.WithSQLite("app.db")) ``` -------------------------------- ### Collect workflow event metrics Source: https://github.com/davidroman0o/gostage/blob/main/guide/09-events-observability.md Implement the `OnEvent` method to collect metrics for various engine events. This example increments counters for total events, completed, and failed workflows. ```go type metricsCollector struct{} func (m *metricsCollector) OnEvent(event gostage.EngineEvent) { metrics.IncrCounter("workflow_events_total", "type", eventName(event.Type)) switch event.Type { case gostage.EventRunCompleted: metrics.IncrCounter("workflow_completed_total", "workflow", event.WorkflowID) case gostage.EventRunFailed: metrics.IncrCounter("workflow_failed_total", "workflow", event.WorkflowID) } } ``` -------------------------------- ### Multi-Tenant Isolation with Separate Registries Source: https://github.com/davidroman0o/gostage/blob/main/guide/10-registry-isolation.md Illustrates how to provide each tenant with its own registry, allowing for distinct implementations of tasks with the same name across different engines. ```go tenantA := gostage.NewRegistry() tenantA.RegisterTask("process", tenantAHandler) tenantB := gostage.NewRegistry() tenantB.RegisterTask("process", tenantBHandler) engineA, _ := gostage.New(gostage.WithRegistry(tenantA)) engineB, _ := gostage.New(gostage.WithRegistry(tenantB)) ``` -------------------------------- ### Asynchronous Workflow Execution Control Source: https://github.com/davidroman0o/gostage/blob/main/README.md Manage workflow execution asynchronously. Start workflows immediately, wait for their completion, cancel them, check their status, list running workflows, or delete them. ```go runID, _ := engine.Run(ctx, wf, params) // returns immediately result, _ := engine.Wait(ctx, runID) // blocks until done engine.Cancel(ctx, runID) // abort a running workflow run, _ := engine.GetRun(ctx, runID) // non-blocking status check runs, _ := engine.ListRuns(ctx, gostage.RunFilter{Status: gostage.Running}) engine.DeleteRun(ctx, runID) // remove from persistence ``` -------------------------------- ### Initialize Engine with SQLite Backend Source: https://github.com/davidroman0o/gostage/blob/main/guide/04-persistence.md Initializes the GoStage engine with a persistent SQLite backend. The database file is created if it doesn't exist, and schema migrations are handled automatically. WAL mode is enabled for concurrent read/write access, and the connection is limited to one to prevent SQLite busy errors. ```go engine, err := gostage.New(gostage.WithSQLite("app.db")) if err != nil { log.Fatal(err) } deffer engine.Close() ``` -------------------------------- ### Async Workflow Execution, Waiting, and Cancellation Source: https://context7.com/davidroman0o/gostage/llms.txt Shows how to submit a workflow for asynchronous execution using `engine.Run`, poll its status with `engine.GetRun`, wait for completion with `engine.Wait`, and cancel with `engine.Cancel`. ```go gs.Task("slow-task", func(ctx *gs.Ctx) error { time.Sleep(2 * time.Second) return gs.Set(ctx, "done", true) }) wf, _ := gs.NewWorkflow("async-demo").Step("slow-task").Commit() engine, _ := gs.New() defer engine.Close() ctx := context.Background() // Submit without blocking runID, err := engine.Run(ctx, wf, gs.Params{"key": "val"}) if err != nil { log.Fatal(err) } // Poll status without blocking run, _ := engine.GetRun(ctx, runID) fmt.Println("status after submit:", run.Status) // running // List all running workflows runs, _ := engine.ListRuns(ctx, gs.RunFilter{Status: gs.Running}) fmt.Println("active runs:", len(runs)) // Wait for completion (blocks) result, _ := engine.Wait(ctx, runID) fmt.Println("final status:", result.Status) // completed // Cancel example runID2, _ := engine.Run(ctx, wf, nil) engine.Cancel(ctx, runID2) // Delete a run from persistence engine.DeleteRun(ctx, runID) ``` -------------------------------- ### Access Control Task Middleware Source: https://github.com/davidroman0o/gostage/blob/main/guide/08-middleware.md Enforce access control at the task level by checking user roles or permissions. This example gates tasks prefixed with 'admin.' to only allow users with the 'admin' role. ```go gostage.WithTaskMiddleware(func(tctx *gostage.Ctx, taskName string, next func() error) error { role := gostage.Get[string](tctx, "user.role") if strings.HasPrefix(taskName, "admin.") && role != "admin" { return fmt.Errorf("unauthorized: %s requires admin role", taskName) } return next() }) ``` -------------------------------- ### Run Workflow and Access State Source: https://github.com/davidroman0o/gostage/blob/main/guide/03-state.md Initiate a workflow with initial parameters and access the final state. Ensure correct type assertions when retrieving results. ```go result, _ := engine.RunSync(ctx, wf, gs.Params{ "order_id": "ORD-123", "amount": 99, }) gs.Task("process", func(ctx *gs.Ctx) error { orderID := gs.Get[string](ctx, "order_id") // "ORD-123" amount := gs.Get[int](ctx, "amount") // 99 return gs.Set(ctx, "total", amount * 100) // write new key }) total, ok := gs.ResultGet[int](result, "total") // 9900 ``` -------------------------------- ### Set Per-Task Timeout with Retries Source: https://github.com/davidroman0o/gostage/blob/main/guide/12-production.md Configure a deadline for individual task invocations, with support for retries. Each retry attempt gets a new timeout. Task timeouts interact with retries, and the global timeout can cancel the entire run. ```go gostage.Task("external-api", apiHandler, gostage.WithTaskTimeout(30*time.Second), gostage.WithRetry(3), ) ``` -------------------------------- ### Conditional Routing with Branch Step Source: https://github.com/davidroman0o/gostage/blob/main/guide/02-step-kinds.md Shows how to implement conditional logic using the 'Branch' step. Cases are evaluated sequentially, and the first matching condition executes its associated step. ```go wf, _ := gs.NewWorkflow("route"). Branch( gs.When(func(ctx *gs.Ctx) bool { return gs.Get[string](ctx, "priority") == "high" }).Step("urgent-process"), gs.When(func(ctx *gs.Ctx) bool { return gs.Get[string](ctx, "priority") == "medium" }).Step("normal-process"), gs.Default().Step("low-priority-process"), ). Commit() ``` -------------------------------- ### Implement DoWhile Loop Source: https://github.com/davidroman0o/gostage/blob/main/README.md Define a loop that checks a condition first and then executes a step. The loop continues as long as the condition is true. ```go wf.DoWhileNamed(gostage.Step("fetch-page"), "has-more") ``` -------------------------------- ### Compose Complex Workflows with Various Step Kinds Source: https://github.com/davidroman0o/gostage/blob/main/README.md Demonstrates the construction of a complex workflow using multiple step kinds like Step, Stage, Parallel, Branch, ForEach, MapNamed, DoUntilNamed, Sub, and Sleep. This is useful for building intricate business logic. ```go wf, _ := gostage.NewWorkflow("order"). Step("validate"). // single task Stage("checks", gostage.Step("credit"), gostage.Step("fraud")). // sequential group Parallel(gostage.Step("reserve"), gostage.Step("charge")). // concurrent fan-out Branch( // conditional gostage.WhenNamed("is-priority").Step("expedite"), gostage.Default().Step("standard"), ). ForEach("items", gostage.Step("process"), gostage.WithConcurrency(4)). // collection iteration MapNamed("summarize"). // inline transform DoUntilNamed(gostage.Step("poll"), "is-ready"). // repeat until condition Sub(otherWorkflow). // nested workflow Sleep(time.Hour). // persistent timed delay Step("notify"). Commit() ``` -------------------------------- ### Create an In-Memory Engine Source: https://github.com/davidroman0o/gostage/blob/main/guide/01-quickstart.md Instantiate an in-memory gostage engine suitable for development and testing. ```go engine, err := gs.New() ``` -------------------------------- ### Adding Tags to a Single Step Source: https://github.com/davidroman0o/gostage/blob/main/guide/02-step-kinds.md Shows how to attach tags to an individual step for runtime queries and mutations. Tags help in identifying and managing steps during execution. ```go wf.Step("charge", gs.WithStepTags("billing", "critical")) ``` -------------------------------- ### Implement Step Timing Middleware Source: https://context7.com/davidroman0o/gostage/llms.txt Create middleware to measure the execution time of individual steps. This can be registered globally or per workflow. ```go // Step timing middleware timingMW := func(ctx context.Context, info gs.StepInfo, runID gs.RunID, next func() error) error { start := time.Now() err := next() fmt.Printf("step %s (%s) took %s\n", info.Name, info.Kind, time.Since(start)) return err } // Workflow-scoped step middleware (applies only to this workflow) wf, _ := gs.NewWorkflow("monitored", gs.WithWorkflowMiddleware(timingMW), ).Step("some-task").Commit() ``` -------------------------------- ### Implement DoUntil Loop Source: https://github.com/davidroman0o/gostage/blob/main/README.md Define a loop that executes a step and then checks a condition. The loop continues until the condition is met. ```go wf.DoUntilNamed(gostage.Step("poll"), "is-ready") ``` -------------------------------- ### Gostage Workflow Composition with Step Kinds Source: https://context7.com/davidroman0o/gostage/llms.txt Demonstrates the composition of a workflow using various step kinds: Parallel for concurrent execution, ForEach for iteration with concurrency limits, Branch for conditional routing, Stage for grouping, and Sub for embedding another workflow. Includes task definitions and workflow execution. ```go gs.Task("fetch-users", func(ctx *gs.Ctx) error { return gs.Set(ctx, "user_count", 150) }) gs.Task("fetch-orders", func(ctx *gs.Ctx) error { return gs.Set(ctx, "order_count", 340) }) gs.Task("process-item", func(ctx *gs.Ctx) error { item := gs.Item[string](ctx) idx := gs.ItemIndex(ctx) return gs.Set(ctx, fmt.Sprintf("result_%d", idx), "processed:"+item) }) gs.Task("merge", func(ctx *gs.Ctx) error { return gs.Set(ctx, "merged", true) }) gs.Task("finalize", func(ctx *gs.Ctx) error { return nil }) gs.Task("fast-path", func(ctx *gs.Ctx) error { return gs.Set(ctx, "path", "fast") }) gs.Task("slow-path", func(ctx *gs.Ctx) error { return gs.Set(ctx, "path", "slow") }) gs.Condition("is-fast", func(ctx *gs.Ctx) bool { return gs.Get[bool](ctx, "use_fast") }) paymentWf, _ := gs.NewWorkflow("payment").Step("finalize").Commit() wf, _ := gs.NewWorkflow("pipeline"). // Parallel: concurrent fan-out, all must succeed Parallel( gs.Step("fetch-users"), gs.Step("fetch-orders"), ). // ForEach: iterate slice stored at "items" key, up to 4 concurrent ForEach("items", gs.Step("process-item"), gs.WithConcurrency(4)). // Branch: first-match conditional routing Branch( gs.WhenNamed("is-fast").Step("fast-path"), gs.Default().Step("slow-path"), ). // Stage: named sequential group for readability Stage("cleanup", gs.Step("merge"), gs.Step("finalize"), ). // Sub: embed a committed sub-workflow Sub(paymentWf). Commit() engine, _ := gs.New() deffer engine.Close() result, _ := engine.RunSync(context.Background(), wf, gs.Params{ "items": []string{"a", "b", "c"}, "use_fast": true, }) fmt.Println(result.Status) // completed path, _ := gs.ResultGet[string](result, "path") fmt.Println(path) // fast ``` -------------------------------- ### Complex Workflow Composition Source: https://github.com/davidroman0o/gostage/blob/main/guide/02-step-kinds.md Demonstrates nesting various step kinds including Stages, Sub-workflows, ForEach loops with concurrency, and Branching with conditional sub-workflows. ```go wf, _ := gs.NewWorkflow("complex"). Stage("init", gs.Step("setup"), gs.Sub(configWorkflow), ). ForEach("batches", gs.Sub(batchWorkflow), gs.WithConcurrency(4)). Branch( gs.When(hasErrors).Sub(errorHandlingWorkflow), gs.Default().Step("finalize"), ). Commit() ``` -------------------------------- ### Dynamically Load Tasks into Registry Source: https://github.com/davidroman0o/gostage/blob/main/guide/10-registry-isolation.md Populate a registry with tasks discovered at runtime, such as from a plugin directory. This allows the same workflow definition to use different task implementations across environments. ```go reg := gostage.NewRegistry() for _, plugin := range discoverPlugins("/etc/app/plugins") { reg.RegisterTask(plugin.Name, plugin.Handler) } engine, _ := gostage.New(gostage.WithRegistry(reg)) ``` -------------------------------- ### Test Isolation with a Fresh Registry per Test Source: https://github.com/davidroman0o/gostage/blob/main/guide/10-registry-isolation.md Create a fresh registry for each test and pass it via `WithRegistry` to avoid touching global state entirely, ensuring complete isolation. ```go func TestChargeTask(t *testing.T) { reg := gostage.NewRegistry() reg.RegisterTask("charge", mockChargeHandler) engine, _ := gostage.New(gostage.WithRegistry(reg)) defer engine.Close() // ... run workflow against this isolated engine } ``` -------------------------------- ### ForEach: Run iterations in isolated processes Source: https://github.com/davidroman0o/gostage/blob/main/guide/02-step-kinds.md Use `WithSpawn()` to run each iteration in an isolated child process for process-level isolation, at the cost of IPC overhead. ```go wf.ForEach("items", gs.Step("process"), gs.WithSpawn()) ``` -------------------------------- ### Run a Gostage Workflow Source: https://github.com/davidroman0o/gostage/blob/main/guide/01-quickstart.md Execute a workflow using `engine.RunSync()` or `Run()`. This clones the workflow, injects parameters, and runs the tasks. ```go engine.RunSync(ctx, wf, params) ``` ```go or Run() ``` -------------------------------- ### Configure Fixed Delay Retry Strategy Source: https://context7.com/davidroman0o/gostage/llms.txt Use `gs.WithRetry` and `gs.WithRetryDelay` to set a fixed number of retries and a constant delay between them. ```go // Fixed delay gs.Task("fixed-retry", handler, gs.WithRetry(3), gs.WithRetryDelay(2*time.Second)) ``` -------------------------------- ### Configure Child Process with Engine Options Source: https://github.com/davidroman0o/gostage/blob/main/guide/07-spawn.md Pass engine options to `HandleChild` to configure the child process's logger or task middleware. These options are applied during the child's execution lifecycle. ```go spawn.HandleChild(gostage.WithLogger(myLogger), gostage.WithTaskMiddleware(myMW)) ``` -------------------------------- ### Build a Workflow with Sequential Steps Source: https://github.com/davidroman0o/gostage/blob/main/guide/01-quickstart.md Define a workflow by chaining steps using a fluent API. Use Commit() to finalize the workflow. ```go wf, err := gs.NewWorkflow("process-order"). Step("validate-order"). Step("charge-payment"). Step("send-confirmation"). Commit() ``` -------------------------------- ### Creating and Registering with a Custom Registry Source: https://github.com/davidroman0o/gostage/blob/main/guide/10-registry-isolation.md Create a dedicated registry instance for isolated workflow domains or testing. Register tasks, conditions, and map functions into this specific registry. ```go reg := gostage.NewRegistry() reg.RegisterTask("ingest", ingestHandler) reg.RegisterCondition("has-data", hasDataFn) reg.RegisterMapFn("transform", transformFn) ``` -------------------------------- ### Create Production Gostage Engine Source: https://context7.com/davidroman0o/gostage/llms.txt Configure a production-ready Gostage engine with durable SQLite persistence, automatic recovery, garbage collection, and various performance tuning options. ```go import ( "log" "time" gs "github.com/davidroman0O/gostage" ) func newProductionEngine() *gs.Engine { engine, err := gs.New( gs.WithSQLite("/var/lib/app/workflows.db"), // durable SQLite backend gs.WithAutoRecover(), // resume sleeping runs, mark crashed runs failed gs.WithRunGC(7*24*time.Hour, 10*time.Minute), // delete terminal runs older than 7 days gs.WithStateLimit(1000), // cap per-run key-value entries gs.WithWorkerPoolSize(32), // async worker goroutines gs.WithTimeout(10*time.Minute), // per-run deadline for RunSync gs.WithShutdownTimeout(30*time.Second), // Close() drain deadline gs.WithCacheSize(200), // LRU workflow template cache gs.WithLogger(gs.NewDefaultLogger()), ) if err != nil { log.Fatalf("engine init: %v", err) } return engine } // In-memory engine for unit tests func newTestEngine() *gs.Engine { engine, _ := gs.New(gs.WithSQLite(":memory:")) return engine } ``` -------------------------------- ### Nesting Sub-workflows in Parallel Source: https://github.com/davidroman0o/gostage/blob/main/guide/02-step-kinds.md Demonstrates nesting a 'Sub' workflow reference within a 'Parallel' step to execute sub-workflows concurrently. Ensure unique state keys to avoid data races. ```go wf.Parallel(gs.Step("fast-task"), gs.Sub(heavyWorkflow)) ``` -------------------------------- ### Run End-to-End Go Tests Source: https://github.com/davidroman0o/gostage/blob/main/README.md Execute end-to-end tests for workflow patterns. Use the -v flag for verbose output. ```bash go test -run TestE2E_ -v ``` -------------------------------- ### Concurrent Execution with Parallel Steps Source: https://github.com/davidroman0o/gostage/blob/main/guide/02-step-kinds.md Illustrates running multiple independent tasks concurrently using the 'Parallel' step kind. The workflow waits for all parallel tasks to complete. ```go wf, _ := gs.NewWorkflow("extract"). Parallel( gs.Step("extract-users"), gs.Step("extract-orders"), gs.Step("extract-products"), ). Step("merge-results"). Commit() ``` -------------------------------- ### Configure Exponential Backoff Retry Strategy Source: https://context7.com/davidroman0o/gostage/llms.txt Employ `gs.WithRetryStrategy` with `gs.ExponentialBackoff` to implement retries with increasing delays. ```go // Exponential backoff gs.Task("backoff-task", handler, gs.WithRetry(5), gs.WithRetryStrategy(gs.ExponentialBackoff(100*time.Millisecond, 30*time.Second)), ) ``` -------------------------------- ### Gostage Loops: DoUntil and DoWhile Source: https://context7.com/davidroman0o/gostage/llms.txt Illustrates the usage of DoUntil (post-condition, at least one execution) and DoWhile (pre-condition, zero or more executions) loops in Gostage workflows. Includes task definitions for polling and fetching, conditions for loop termination, and workflow execution with result printing. ```go gs.Task("poll-status", func(ctx *gs.Ctx) error { // simulate polling: flip status after 3 calls count := gs.GetOr[int](ctx, "poll_count", 0) count++ gs.Set(ctx, "poll_count", count) if count >= 3 { return gs.Set(ctx, "status", "ready") } return gs.Set(ctx, "status", "pending") }) gs.Task("fetch-page", func(ctx *gs.Ctx) error { page := gs.GetOr[int](ctx, "page", 0) page++ gs.Set(ctx, "page", page) return gs.Set(ctx, "has_more", page < 3) }) gs.Condition("is-ready", func(ctx *gs.Ctx) bool { return gs.Get[string](ctx, "status") == "ready" }) gs.Condition("has-more", func(ctx *gs.Ctx) bool { return gs.Get[bool](ctx, "has_more") }) wf, _ := gs.NewWorkflow("loops"). // DoUntil: run body, then check; always runs at least once DoUntilNamed(gs.Step("poll-status"), "is-ready"). // DoWhile: check first, then run body DoWhileNamed(gs.Step("fetch-page"), "has-more"). Commit() engine, _ := gs.New() deffer engine.Close() result, _ := engine.RunSync(context.Background(), wf, nil) fmt.Println(result.Status) // completed count, _ := gs.ResultGet[int](result, "poll_count") fmt.Println("polls:", count) // polls: 3 pages, _ := gs.ResultGet[int](result, "page") fmt.Println("pages fetched:", pages) // pages fetched: 3 ``` -------------------------------- ### State Management in Tasks Source: https://context7.com/davidroman0o/gostage/llms.txt Illustrates using `gs.Get`, `gs.GetOr`, `gs.GetOk`, `gs.Set`, and `gs.Delete` to manage data within a task's execution context. `Set` buffers writes and handles JSON serialization. ```go gs.Task("state-demo", func(ctx *gs.Ctx) error { // Get: zero value on miss name := gs.Get[string](ctx, "name") // "" if missing // GetOr: caller-provided default pageSize := gs.GetOr[int](ctx, "page_size", 25) // 25 if missing // GetOk: distinguish missing from zero flag, ok := gs.GetOk[bool](ctx, "feature_enabled") if !ok { fmt.Println("key not set") } // Set: returns error if value is not JSON-serializable if err := gs.Set(ctx, "greeting", "Hello, "+name); err != nil { return fmt.Errorf("set failed: %w", err) } // Structs round-trip through persistence correctly type Record struct { ID string `json:"id"` Score int `json:"score"` } gs.Set(ctx, "record", Record{ID: "r1", Score: 42}) // Delete removes a key durably (tombstone in persistence) gs.Delete(ctx, "temp_data") _ = pageSize _ = flag return nil }) // After run, read result store engine, _ := gs.New() defer engine.Close() Wf, _ := gs.NewWorkflow("s").Step("state-demo").Commit() result, _ := engine.RunSync(context.Background(), wf, gs.Params{"name": "Alice"}) greeting, _ := gs.ResultGet[string](result, "greeting") fmt.Println(greeting) // Hello, Alice ``` -------------------------------- ### Detect and Handle Child Process Execution Source: https://github.com/davidroman0o/gostage/blob/main/guide/07-spawn.md The `main` function must first check if it's running as a child process. If so, it registers tasks and calls `HandleChild`, which manages the child's lifecycle and never returns. This must be the very first operation in `main`. ```go func main() { if spawn.IsChild() { registerTasks() // same tasks the parent uses spawn.HandleChild() // never returns -- calls os.Exit return } // normal parent setup continues here engine, _ := gostage.New(spawn.WithSpawn()) } ``` -------------------------------- ### Configure Automatic Run Garbage Collection Source: https://github.com/davidroman0o/gostage/blob/main/README.md Set up automatic purging of old runs. Specify the maximum age of runs to keep and the interval for the garbage collection process. ```go engine, _ := gostage.New( gostage.WithRunGC(24*time.Hour, 5*time.Minute), // purge runs older than 24h every 5m ) ``` -------------------------------- ### Run Stress Tests for Go Source: https://github.com/davidroman0o/gostage/blob/main/README.md Execute adversarial stress tests for Go. Use the -v flag for verbose output. ```bash go test -run TestStress_ -v ``` -------------------------------- ### Set State Limit Source: https://github.com/davidroman0o/gostage/blob/main/guide/03-state.md Configure a maximum number of state entries per run to prevent unbounded memory consumption. Updates to existing keys always succeed, even if the limit is reached. ```go engine, _ := gs.New(gs.WithStateLimit(1000)) ``` -------------------------------- ### Implement Child Middleware for Spawning Logging Source: https://github.com/davidroman0o/gostage/blob/main/guide/08-middleware.md This child middleware logs messages before and after spawning a child process. It measures the time taken for the child's execution. ```go gostage.WithChildMiddleware(func(ctx context.Context, job *gostage.SpawnJob, next func() error) error { log.Printf("spawning child for job %s", job.ID) start := time.Now() err := next() log.Printf("child %s finished in %s", job.ID, time.Since(start)) return err }) ``` -------------------------------- ### Control Steps by Tags at Runtime Source: https://context7.com/davidroman0o/gostage/llms.txt Utilize `gs.WithTags` to mark tasks and then use `gs.DisableByTag` or `gs.FindStepsByTag` to manage groups of tasks dynamically during execution. ```go // Tags-based runtime control gs.Task("charge", handler, gs.WithTags("billing")) gs.Task("refund", handler, gs.WithTags("billing")) gs.Task("kill-billing", func(ctx *gs.Ctx) error { gs.DisableByTag(ctx, "billing") // disable all billing-tagged steps ids := gs.FindStepsByTag(ctx, "billing") fmt.Println("disabled:", ids) return nil }) wf, _ := gs.NewWorkflow("dynamic"). Step("router"). Step("auto-approve"). Step("finalize"). Commit() engine, _ := gs.New() defertools engine.Close() result, _ := engine.RunSync(context.Background(), wf, gs.Params{"needs_review": true}) reviewed, _ := gs.ResultGet[bool](result, "reviewed") fmt.Println("reviewed:", reviewed) // reviewed: true ``` -------------------------------- ### Register Tasks with Gostage Source: https://github.com/davidroman0o/gostage/blob/main/guide/01-quickstart.md Use `gostage.Task()` to add your functions to the global registry. This is the first step in defining your workflow. ```go gostage.Task("name", fn) ``` -------------------------------- ### Tag Tasks and Steps Source: https://github.com/davidroman0o/gostage/blob/main/README.md Assign tags to tasks and steps for organization and conditional execution. Tags can be used for querying and managing steps. ```go gostage.Task("charge", handler, gostage.WithTags("billing", "critical")) wf.Step("charge", gostage.WithStepTags("billing")) // Inside a task: find and disable all billing steps ids := gostage.FindStepsByTag(ctx, "billing") gostage.DisableByTag(ctx, "billing") ``` -------------------------------- ### Implement Task Access Control Middleware Source: https://context7.com/davidroman0o/gostage/llms.txt Create middleware to enforce access control, such as gating admin tasks based on user roles retrieved from the context. This is registered via engine options. ```go // Access-control middleware (gates admin tasks by role) authMW := func(tctx *gs.Ctx, taskName string, next func() error) error { role := gs.Get[string](tctx, "user.role") if strings.HasPrefix(taskName, "admin.") && role != "admin" { return fmt.Errorf("unauthorized: %s requires admin", taskName) } return next() } engine, _ := gs.New( gs.WithStepMiddleware(timingMW), gs.WithTaskMiddleware(wrapMW), gs.WithTaskMiddleware(authMW), // Or use Plugin to register at all levels at once: gs.WithPlugin(gs.LoggingPlugin(gs.NewDefaultLogger())), ) defertools engine.Close() ``` -------------------------------- ### DoUntil: Post-condition loop Source: https://github.com/davidroman0o/gostage/blob/main/guide/02-step-kinds.md Implement a post-condition loop with DoUntil, guaranteeing at least one execution of the body before the condition is checked. The loop continues until the condition returns true. ```go wf, _ := gs.NewWorkflow("poll"). DoUntil(gs.Step("check-status"), func(ctx *gs.Ctx) bool { return gs.Get[string](ctx, "status") == "ready" }). Step("proceed"). Commit() ``` -------------------------------- ### Define Plugin Interface Source: https://github.com/davidroman0o/gostage/blob/main/guide/08-middleware.md Implement the Plugin interface to provide middleware at engine, step, task, and child levels. Return nil for levels you don't need middleware for. ```go type Plugin interface { EngineMiddleware() EngineMiddleware // return nil to skip StepMiddleware() StepMiddleware TaskMiddleware() TaskMiddleware ChildMiddleware() ChildMiddleware } ``` -------------------------------- ### Use Built-in LoggingPlugin Source: https://github.com/davidroman0o/gostage/blob/main/guide/08-middleware.md Integrate the LoggingPlugin to automatically log engine, step, and task events with durations. Pass your logger implementation to the LoggingPlugin constructor. ```go engine, _ := gostage.New(gostage.WithPlugin(gostage.LoggingPlugin(myLogger))) ``` -------------------------------- ### Branching to a Sub-workflow Source: https://github.com/davidroman0o/gostage/blob/main/guide/02-step-kinds.md Demonstrates how a 'Branch' case can route execution to a nested sub-workflow instead of a single task. This is useful for complex conditional logic. ```go gs.When(isLargeOrder).Sub(largeOrderWorkflow) ``` -------------------------------- ### Using Exponential Backoff with Jitter Source: https://github.com/davidroman0o/gostage/blob/main/guide/06-retry-strategies.md Employ ExponentialBackoffWithJitter for sophisticated backoff strategies, providing a randomized delay between 0 and a calculated ceiling. This prevents thundering herd issues when multiple instances retry simultaneously. ```go gostage.Task("api-call", handler, gostage.WithRetry(5), gostage.WithRetryStrategy( gostage.ExponentialBackoffWithJitter(100*time.Millisecond, 30*time.Second), ), ) ``` -------------------------------- ### Run Go Tests with Race Detector Source: https://github.com/davidroman0o/gostage/blob/main/README.md Execute all Go tests with the race detector enabled to find concurrency issues. ```bash go test ./... -race ```