### Complete Workflow Example (Python) Source: https://docs.sayiir.dev/guides/signals-and-events A full Python example demonstrating a workflow that creates an order, waits for a manager's approval signal (with a 24-hour timeout), and then fulfills the order. It includes task definitions and backend setup. ```python from datetime import timedelta from sayiir import ( task, Flow, run_durable_workflow, send_signal, resume_workflow, InMemoryBackend ) @task def create_order(order_id: int) -> dict: print(f"Creating order {order_id}") return {"order_id": order_id, "status": "pending"} @task def fulfill(approval: dict) -> str: approver = approval.get("by", "unknown") print(f"Fulfilling order (approved by {approver})") return f"Fulfilled order (approved by {approver})" workflow = ( Flow("approval") .then(create_order) .wait_for_signal("manager_approval", timeout=timedelta(hours=24)) .then(fulfill) .build() ) backend = InMemoryBackend() # Start workflow status = run_durable_workflow(workflow, "order-42", 42, backend=backend) print(f"Status: {status}") # WaitingForSignal # External system sends approval send_signal("order-42", "manager_approval", {"by": "alice"}, backend=backend) ``` -------------------------------- ### PostgreSQL Connection URL Examples Source: https://docs.sayiir.dev/reference/configuration Illustrates various examples of PostgreSQL connection URLs, including local development setups, custom ports, and connection parameters like SSL mode and timeouts. ```text # Local development postgresql://postgres:password@localhost/sayiir # With custom port postgresql://user:pass@db.example.com:5433/workflows # Connection parameters postgresql://user:pass@host/db?sslmode=require&connect_timeout=10 ``` -------------------------------- ### Start, Signal, and Resume Workflow (Rust) Source: https://docs.sayiir.dev/guides/signals-and-events Illustrates starting a workflow with `CheckpointingRunner`, sending an event to it, and resuming its execution. This uses the `sayiir_runtime` crate and requires a `backend` implementation. ```rust use sayiir_runtime::CheckpointingRunner; let runner = CheckpointingRunner::new(backend.clone()); // Start the workflow let status = runner.run(&workflow, "order-42", 42u64).await?; println!("{:?}", status); // WaitingForSignal // Signal can be sent immediately or hours later let payload = json!({"by": "alice"}); backend.send_event("order-42", "manager_approval", payload).await?; // Resume to continue execution let status = runner.resume(&workflow, "order-42").await?; println!("{:?}", status); // Completed ``` -------------------------------- ### Install Sayiir with pnpm Source: https://docs.sayiir.dev/getting-started/nodejs Install the Sayiir package using the pnpm package manager. ```bash pnpm add sayiir ``` -------------------------------- ### Rust Task Metadata Examples Source: https://docs.sayiir.dev/reference/configuration Configure individual task behavior and documentation using Rust. ```rust #[task( id = "process_payment", timeout = "30s", retries = 3, backoff = "2s", tags = "payment", tags = "external", tags = "critical", description = "Processes customer payment via Stripe API" )] async fn charge_card(order: Order) -> Result { Ok(Receipt { receipt_id: "R123", status: "paid" }) } // Or construct TaskMetadata manually use sayiir_core::task::{TaskMetadata, RetryPolicy}; use std::time::Duration; let metadata = TaskMetadata { display_name: Some("Process Payment".to_string()), description: Some("Processes customer payment via Stripe API".to_string()), timeout: Some(Duration::from_secs(30)), retry_policy: Some(RetryPolicy { max_retries: 3, initial_delay: Duration::from_secs(2), backoff_multiplier: 2.0, }), tags: vec!["payment".to_string(), "external".to_string(), "critical".to_string()], }; ``` -------------------------------- ### Install Sayiir Source: https://docs.sayiir.dev/getting-started/python Install the Sayiir Python package using pip. ```bash pip install sayiir ``` -------------------------------- ### Python Database Connection Source: https://docs.sayiir.dev/reference/configuration Python example for configuring the PostgresBackend with connection pooling details. ```python # Python: connection pooling is built into the Rust backend backend = PostgresBackend("postgresql://user:pass@host/sayiir") ``` -------------------------------- ### Python Task Metadata Example Source: https://docs.sayiir.dev/reference/configuration Configure individual task behavior and documentation using Python. ```python from sayiir import task, RetryPolicy @task( "process_payment", timeout="30s", retries=RetryPolicy(max_retries=3, initial_delay_secs=2.0), tags=["payment", "external", "critical"], description="Processes customer payment via Stripe API" ) def charge_card(order: dict) -> dict: return {"receipt_id": "R123", "status": "paid"} ``` -------------------------------- ### Node.js Task Metadata Example Source: https://docs.sayiir.dev/reference/configuration Configure individual task behavior and documentation using Node.js. ```javascript import { task } from "sayiir"; const chargeCard = task("process_payment", (order: Record) => { return { receipt_id: "R123", status: "paid" }; }, { timeout: "30s", retry: { maxAttempts: 3, initialDelay: "2s", backoffMultiplier: 2.0, }, tags: ["payment", "external", "critical"], description: "Processes customer payment via Stripe API", }); ``` -------------------------------- ### Workflow Definition and Instance Example Source: https://docs.sayiir.dev/concepts/architecture Illustrates a simple workflow definition and its corresponding instances, showing their states and identifiers. ```text Definition: fetch_user → send_email (definition_hash = "a1b2c3") Instance 1: instance_id = "order-123" (InProgress, at send_email) Instance 2: instance_id = "order-456" (Completed) Instance 3: instance_id = "order-789" (Failed) ``` -------------------------------- ### Rust Database Connection Source: https://docs.sayiir.dev/reference/configuration Rust example for initializing the PostgresBackend, noting that connection pooling is managed internally. ```rust // Rust: connection pool is managed internally let backend = PostgresBackend::new("postgresql://user:pass@host/sayiir").await?; ``` -------------------------------- ### Run Cargo Project Source: https://docs.sayiir.dev/tutorials/background-jobs-rust Execute the full example cargo project locally. This command initiates the video processing pipeline, including real ffmpeg transcoding. ```bash cargo run ``` -------------------------------- ### TaskMetadata Example Source: https://docs.sayiir.dev/reference/rust-api Example of creating TaskMetadata, which includes display name, description, timeout, retry policy, and tags. This metadata is associated with a specific task. ```rust use sayiir_core::task::TaskMetadata; use std::time::Duration; let metadata = TaskMetadata { display_name: Some("Process Order".to_string()), description: Some("Validates and processes customer orders".to_string()), timeout: Some(Duration::from_secs(30)), retry_policy: Some(policy), tags: vec!["orders".to_string(), "payment".to_string()], }; ``` -------------------------------- ### Start, Signal, and Resume Workflow (Node.js) Source: https://docs.sayiir.dev/guides/signals-and-events Demonstrates the lifecycle of a workflow in Node.js: starting it with `runDurableWorkflow`, sending a signal using `sendSignal`, and resuming it with `resumeWorkflow`. Ensure the workflow definition and backend are correctly provided. ```javascript import { runDurableWorkflow, sendSignal, resumeWorkflow } from "sayiir"; // Start the workflow const status1 = runDurableWorkflow(workflow, "order-42", 42, backend); console.log(status1.status); // "awaiting_signal" // Signal can be sent immediately or hours later sendSignal("order-42", "manager_approval", { by: "alice" }, backend); // Resume to continue execution const status2 = resumeWorkflow(workflow, "order-42", backend); console.log(status2.status); // "completed" ``` -------------------------------- ### Start Worker and Get Handle Source: https://docs.sayiir.dev/reference/python-api Start a distributed worker using its `start` method, passing a list of workflows it can execute. This returns a `WorkerHandle` for lifecycle control. ```python workflow = Flow("pipeline").then(my_task).build() handle = worker.start([workflow]) ``` -------------------------------- ### Python Distributed Worker Setup Source: https://docs.sayiir.dev/guides/distributed-workers Sets up a distributed worker in Python to poll for and execute tasks from a PostgreSQL backend. This example demonstrates defining tasks, building a workflow, and starting the worker. ```Python import signal from sayiir import task, Flow, PostgresBackend from sayiir.worker import Worker @task def fetch_order(order_id: int) -> dict: return {"order_id": order_id, "total": 99.99} @task def charge_payment(order: dict) -> str: return f"charged ${order['total']}" @task def send_confirmation(receipt: str) -> str: return f"confirmed: {receipt}" workflow = ( Flow("order-pipeline") .then(fetch_order) .then(charge_payment) .then(send_confirmation) .build() ) backend = PostgresBackend("postgresql://localhost/sayiir") worker = Worker("worker-1", backend, poll_interval=2.0) handle = worker.start([workflow]) ``` -------------------------------- ### Install Sayiir for Cloudflare Workers Source: https://docs.sayiir.dev/getting-started/cloudflare Install the Sayiir package for Cloudflare Workers using pnpm. ```bash pnpm add @sayiir/cloudflare ``` -------------------------------- ### Python Workflow Example Source: https://docs.sayiir.dev/ Defines and runs a simple workflow in Python using Sayiir tasks for fetching user data and sending an email. ```python from sayiir import task, Flow, run_workflow @task def fetch_user(user_id: int) -> dict: return {"id": user_id, "name": "Alice"} @task def send_email(user: dict) -> str: return f"Sent welcome to {user['name']}" workflow = Flow("welcome").then(fetch_user).then(send_email).build() result = run_workflow(workflow, 42) ``` -------------------------------- ### Rust Workflow Example Source: https://docs.sayiir.dev/ Defines and runs a simple workflow in Rust using Sayiir tasks with specified timeouts and retries. ```rust use sayiir_runtime::prelude::*; #[task(timeout = "30s", retries = 3)] async fn fetch_user(id: u64) -> Result { db.get_user(id).await } #[task] async fn send_email(user: User) -> Result<(), BoxError> { email_service.send_welcome(&user).await } let workflow = workflow! { name: "welcome", steps: [fetch_user, send_email] }.unwrap(); ``` -------------------------------- ### Simple Sayiir Workflow Example Source: https://docs.sayiir.dev/getting-started/cloudflare Implement a basic Sayiir workflow with two tasks: fetching user data and sending a welcome email. The workflow is built using `task` and `flow` and executed via an `Engine`. ```typescript import { task, flow, Engine } from "@sayiir/cloudflare"; const fetchUser = task("fetch-user", async (id: number) => { const res = await fetch(`https://api.example.com/users/${id}`); return res.json() as Promise<{ id: number; name: string }>; }); const sendEmail = task("send-email", async (user: { id: number; name: string }) => { return `Sent welcome to ${user.name}`; }); const onboarding = flow("onboarding") .then(fetchUser) .then(sendEmail) .build(); export default { async fetch(request: Request, env: Env): Promise { const engine = await Engine.create(env.DB); const status = await engine.run(onboarding, "onboard-42", 42); return Response.json(status); }, }; ``` -------------------------------- ### Python: Run Workflow and Send Signal Source: https://docs.sayiir.dev/guides/signals-and-events Starts a workflow, prints its status (expected to be 'WaitingForSignal'), and then sends the 'manager_approval' signal. ```python from sayiir import run_durable_workflow, send_signal, resume_workflow # Start the workflow status = run_durable_workflow(workflow, "order-42", 42, backend=backend) print(status) # WaitingForSignal # Signal can be sent immediately or hours later send_signal("order-42", "manager_approval", {"by": "alice"}, backend=backend) ``` -------------------------------- ### Node.js/TypeScript Workflow Example Source: https://docs.sayiir.dev/ Defines and runs a simple workflow in Node.js/TypeScript using Sayiir tasks for fetching user data and sending an email. ```javascript import { task, flow, runWorkflow } from "sayiir"; const fetchUser = task("fetch-user", (userId: number) => { return { id: userId, name: "Alice" }; }); const sendEmail = task("send-email", (user: { id: number; name: string }) => { return `Sent welcome to ${user.name}`; }); const workflow = flow("welcome") .then(fetchUser) .then(sendEmail) .build(); const result = await runWorkflow(workflow, 42); ``` -------------------------------- ### .start() Source: https://docs.sayiir.dev/reference/nodejs-api Starts the worker and returns a handle for lifecycle control. This spawns a background thread that polls for tasks, claims them, and dispatches them to registered task functions. ```APIDOC ## .start() ### Description Start the worker and return a handle for lifecycle control. Spawns a background thread that polls for tasks, claims them, and dispatches them to registered task functions. ### Returns: `WorkerHandle` ### Example ```javascript const handle = worker.start(); process.on("SIGTERM", () => handle.shutdown()); ``` ``` -------------------------------- ### Local Testing with Sayiir's InMemoryBackend Source: https://docs.sayiir.dev/comparisons/vs-step-functions Demonstrates local testing of Sayiir workflows using an in-memory backend, requiring no external services or setup. This approach is suitable for rapid development and testing. ```python # Test with in-memory backend (no database, no setup) from sayiir import InMemoryBackend, run_workflow backend = InMemoryBackend() result = await run_workflow(workflow, test_input, backend=backend) assert result == expected_output ``` -------------------------------- ### Define and Run a Simple Workflow Source: https://docs.sayiir.dev/getting-started/nodejs Create a workflow by defining tasks and chaining them using `flow()`. This example demonstrates a simple user fetching and email sending process. ```typescript import { task, flow, runWorkflow } from "sayiir"; const fetchUser = task("fetch-user", (userId: number) => { return { id: userId, name: "Alice" }; }); const sendEmail = task("send-email", (user: { id: number; name: string }) => { return `Sent welcome to ${user.name}`; }); const workflow = flow("welcome") .then(fetchUser) .then(sendEmail) .build(); const result = await runWorkflow(workflow, 42); console.log(result); // "Sent welcome to Alice" ``` -------------------------------- ### Use PostgreSQL Backend for Durable Workflows Source: https://docs.sayiir.dev/getting-started/nodejs Configure `PostgresBackend` for production use with durable workflows. This example assumes `DATABASE_URL` is set in the environment. ```typescript import { PostgresBackend } from "sayiir"; // Connects and runs migrations automatically const backend = PostgresBackend.connect(process.env.DATABASE_URL!); const status = runDurableWorkflow(workflow, "order-123", 42, backend); ``` -------------------------------- ### Cloudflare Workers Runtime Example Source: https://docs.sayiir.dev/changelog/v05 Demonstrates how to run Sayiir workflows natively within Cloudflare Workers, utilizing D1 for persistence and handling scheduled events. ```typescript import { task, flow, Engine } from "@sayiir/cloudflare"; const onboarding = flow("onboarding") .then(fetchUser) .then(sendEmail) .build(); export default { async fetch(request: Request, env: Env): Promise { const engine = await Engine.create(env.DB); const status = await engine.run(onboarding, "onboard-42", 42); return Response.json(status); }, async scheduled(event: ScheduledEvent, env: Env): Promise { const engine = await Engine.create(env.DB); await engine.resumeAll(onboarding); }, }; ``` -------------------------------- ### Pluggable Storage Backends in Sayiir Source: https://docs.sayiir.dev/concepts/architecture Shows how to initialize different storage backend implementations. Use InMemory for testing and PostgreSQL for production, or implement a custom backend. ```rust // In-memory (testing) let backend = InMemoryBackend::new(); ``` ```rust // PostgreSQL (production) let backend = PostgresBackend::new(pool); ``` ```rust // Custom (bring your own) impl PersistentBackend for MyBackend { ... } ``` -------------------------------- ### Define and Run a Workflow with a Loop Source: https://docs.sayiir.dev/guides/loops-and-iteration This snippet demonstrates defining a workflow with setup, loop, and finalize tasks, then running it. The loop task 'countdown' reduces a number until it reaches 1, returning a final value. ```typescript const setup = task("setup", (x: number) => x + 10); const countdown = task("countdown", (n: number) => { if (n <= 1) return LoopResult.done(0); return LoopResult.again(n - 1); }); const finalize = task("finalize", (x: number) => x * 100); const wf = flow("pipeline") .then(setup) .loop(countdown, { maxIterations: 20 }) .then(finalize) .build(); await runWorkflow(wf, 5); // 5 → setup → 15 → countdown (15→…→1→done 0) → finalize → 0 ``` -------------------------------- ### Initialize PostgresBackend (Python) Source: https://docs.sayiir.dev/guides/durable-workflows Use PostgresBackend for production. State survives process restarts. Ensure you have the necessary connection details. ```Python from sayiir import InMemoryBackend, PostgresBackend # Production backend = PostgresBackend( host="localhost", port=5432, user="postgres", password="password", database="sayiir" ) ``` -------------------------------- ### Python Workflow Context Example Source: https://docs.sayiir.dev/roadmap Demonstrates how to use a shared workflow context to get and set state within a Python task. The context can be used to pass data between tasks without explicit manual passing. ```python @task def search_web(query: dict, ctx: WorkflowContext) -> list[dict]: depth = ctx.get("depth", "detailed") results = do_search(query["topic"], depth) ctx.set("web_results_count", len(results)) return results ``` -------------------------------- ### Create and Configure Database Source: https://docs.sayiir.dev/reference/configuration SQL commands to create a dedicated database and user for Sayiir, granting necessary privileges. ```sql CREATE DATABASE sayiir; CREATE USER sayiir_user WITH PASSWORD 'secure_password'; GRANT ALL PRIVILEGES ON DATABASE sayiir TO sayiir_user; ``` -------------------------------- ### Initialize PostgresBackend (Node.js) Source: https://docs.sayiir.dev/guides/durable-workflows Use PostgresBackend for production in Node.js. State survives process restarts. Connect using a PostgreSQL connection string. ```Node.js import { InMemoryBackend, PostgresBackend } from "sayiir"; // Production const backend = PostgresBackend.connect( "postgresql://postgres:password@localhost:5432/sayiir" ); ``` -------------------------------- ### Start a Worker and Handle Shutdown Source: https://docs.sayiir.dev/reference/nodejs-api Starts the worker process, which begins polling for and executing tasks. It returns a handle that can be used to gracefully shut down the worker upon receiving a SIGTERM signal. ```typescript const handle = worker.start(); process.on("SIGTERM", () => handle.shutdown()); ``` -------------------------------- ### Initialize PostgresBackend (Rust) Source: https://docs.sayiir.dev/guides/durable-workflows Use PostgresBackend for production in Rust. State survives process restarts. Requires async context for connection. ```Rust use sayiir_persistence::InMemoryBackend; use sayiir_postgres::PostgresBackend; use sayiir_runtime::prelude::*; // Production let backend = PostgresBackend::::connect( "postgresql://localhost/sayiir" ).await?; ``` -------------------------------- ### Start Parallel Execution with .fork() Source: https://docs.sayiir.dev/reference/nodejs-api Use .fork() to start parallel execution of multiple branches. Each branch is defined using the branch() function. The results are later combined using .join(). ```typescript import { flow, branch, task } from "sayiir"; const sendEmail = task("send-email", (order) => ({ emailSent: true })); const shipOrder = task("ship-order", (order) => ({ shipped: true })); const workflow = flow("process") .then(chargePayment) .fork([ branch("email", sendEmail), branch("shipping", shipOrder), ]) .join("finalize", ([email, shipping]) => ({ ...email, ...shipping })) .build(); ``` -------------------------------- ### .fork Source: https://docs.sayiir.dev/reference/python-api Starts a fork for parallel execution of workflow branches. ```APIDOC ## .fork() ### Description Start a fork for parallel execution. ### Returns ForkBuilder instance ``` -------------------------------- ### .route Source: https://docs.sayiir.dev/reference/python-api Starts conditional branching based on a routing key determined by a key function. ```APIDOC ## .route(key_fn, *, keys) ### Description Start conditional branching based on a routing key. The `keys` parameter declares all valid branch keys upfront — Sayiir validates `.branch()` calls against this set immediately and checks exhaustiveness at `.done()`. Declaring `keys=` is the recommended approach. It catches typos and missing branches at build time. Only omit it when keys are determined at runtime (e.g., dynamically built workflows). ### Parameters * `key_fn` — Callable that returns a string routing key. * `keys` (list[str], keyword-only) — All valid routing keys. `.branch()` calls validate against this set, and `.done()` checks exhaustiveness. ### Returns BranchBuilder instance ``` -------------------------------- ### Initialize Python Tracing Source: https://docs.sayiir.dev/guides/observability Call this once at startup to initialize Sayiir's tracing in your Python application. ```python import sayiir # Initialize logging (call once at startup) sayiir.init_tracing() ``` -------------------------------- ### Sayiir Server Architecture Overview Source: https://docs.sayiir.dev/server Illustrates the basic architecture of Sayiir Server, showing its interaction with user applications, PostgreSQL, worker pools, and the dashboard. ```text Your App → Sayiir Server (gRPC) → PostgreSQL ↕ Worker Pool (auto-scaled) ↕ Dashboard (web UI) ``` -------------------------------- ### Initialize InMemoryBackend (Python) Source: https://docs.sayiir.dev/guides/durable-workflows Use InMemoryBackend for development and testing. State is lost when the process stops. ```Python from sayiir import InMemoryBackend, PostgresBackend # Development/testing backend = InMemoryBackend() ``` -------------------------------- ### Configure PostgreSQL Backend in Python Source: https://docs.sayiir.dev/reference/configuration Initialize the PostgreSQL backend for durable storage in Python. The connection URL specifies the database credentials and location. ```python from sayiir import PostgresBackend # Connection URL format backend = PostgresBackend( "postgresql://username:password@host:port/database" ) # Example URLs backend = PostgresBackend("postgresql://postgres:password@localhost:5432/sayiir") backend = PostgresBackend("postgresql://user:pass@db.example.com/workflows") ``` -------------------------------- ### Node.js Countdown Loop Source: https://docs.sayiir.dev/guides/loops-and-iteration A Node.js example of a countdown loop. It uses the `LoopResult.again` to continue and `LoopResult.done` to exit. ```typescript import { task, flow, runWorkflow, LoopResult } from "sayiir"; const countdown = task("countdown", (n: number) => { if (n <= 1) return LoopResult.done(0); return LoopResult.again(n - 1); }); const wf = flow("countdown").loop(countdown).build(); const result = await runWorkflow(wf, 5); // 5 4 3 2 1 (done) result = 0 ``` -------------------------------- ### Python: Connect to PostgreSQL Backend Source: https://docs.sayiir.dev/guides/postgres-production Initialize a PostgresBackend with a connection string and use it to run a durable workflow. The backend automatically handles migrations on first connection. ```python from sayiir import PostgresBackend, Flow, run_durable_workflow backend = PostgresBackend("postgresql://localhost/sayiir") status = run_durable_workflow(workflow, "order-123", 42, backend=backend) ``` -------------------------------- ### Worker Processing Multiple Workflows (Python) Source: https://docs.sayiir.dev/guides/distributed-workers Define and start a worker that can process tasks from multiple distinct workflows. ```python order_wf = Flow("orders").then(process_order).build() email_wf = Flow("emails").then(send_email).build() handle = worker.start([order_wf, email_wf]) ``` -------------------------------- ### Run Jaeger with Docker Source: https://docs.sayiir.dev/guides/observability Start a local Jaeger instance using Docker for development and testing of OpenTelemetry traces. ```shell docker run -d --name jaeger \ -p 16686:16686 \ -p 4317:4317 \ jaegertracing/jaeger:latest ``` -------------------------------- ### Import Sayirre Prelude Source: https://docs.sayiir.dev/getting-started/rust Import the prelude to bring common types and macros into scope. ```rust use sayiir_runtime::prelude::*; // Re-exports: WorkflowBuilder, CheckpointingRunner, PooledWorker, // WorkerHandle, InMemoryBackend, JsonCodec, TaskRegistry, // task (macro), workflow (macro), etc. ``` -------------------------------- ### Define and Run a Workflow with Tasks Source: https://docs.sayiir.dev/guides/distributed-workers Defines tasks and a workflow, then starts a worker to process them. Includes graceful shutdown on SIGTERM. ```typescript import { task, flow, PostgresBackend, Worker } from "sayiir"; const fetchOrder = task("fetch-order", (orderId: number) => { return { orderId, total: 99.99 }; }); const chargePayment = task("charge-payment", (order: { total: number }) => { return `charged $${order.total}`; }); const sendConfirmation = task("send-confirmation", (receipt: string) => { return `confirmed: ${receipt}`; }); const workflow = flow("order-pipeline") .then(fetchOrder) .then(chargePayment) .then(sendConfirmation) .build(); const backend = PostgresBackend.connect(process.env.DATABASE_URL!); const worker = new Worker("worker-1", backend, [workflow], { pollInterval: "2s", }); const handle = worker.start(); // Graceful shutdown on SIGTERM process.on("SIGTERM", () => handle.shutdown()); ``` -------------------------------- ### Rust Retry Policy Example Source: https://docs.sayiir.dev/reference/configuration Configure automatic retry behavior for tasks that may fail transiently using Rust. ```rust use std::time::Duration; use sayiir_core::task::RetryPolicy; let policy = RetryPolicy { max_retries: 3, initial_delay: Duration::from_secs(1), backoff_multiplier: 2.0, }; #[task(retries = 3, backoff = "1s", backoff_multiplier = 2.0)] async fn flaky_task(data: Data) -> Result { // May fail and retry with exponential backoff Ok(process(data)) } ``` -------------------------------- ### Sayiir Checkpoint-Resume Recovery Example Source: https://docs.sayiir.dev/comparisons/vs-prefect Illustrates Sayiir's checkpoint-resume durability model. After each task, its output is stored, allowing workflows to resume from the last completed task upon failure, avoiding re-execution of already finished work. ```python # Sayiir checkpoint-resume example @task async def task_a() -> str: print("Task A executed") return "A done" @task async def task_b(a_result: str) -> str: print("Task B executed") raise Exception("Crash!") @task async def task_c(b_result: str) -> str: print("Task C executed") return "C done" workflow = Flow("recovery").then(task_a).then(task_b).then(task_c).build() # First run: A completes, B crashes try: await run_workflow(workflow, backend=postgres_backend) except Exception: pass # Crash after A completes # Resume: A is skipped (already completed), B retries, C runs result = await run_workflow(workflow, backend=postgres_backend) # Output: Only "Task B executed" and "Task C executed" print # task_a is not re-executed ``` -------------------------------- ### Default Workflow with JsonCodec Source: https://docs.sayiir.dev/guides/serialization-and-versioning Demonstrates creating a workflow using the default JsonCodec. No explicit codec configuration is needed. ```rust use sayiir_runtime::prelude::* // JsonCodec is the default — no explicit codec needed let workflow = workflow! { name: "order_pipeline", registry: registry, steps: [validate, charge, ship] }.unwrap(); ``` -------------------------------- ### Node.js Retry Policy Example Source: https://docs.sayiir.dev/reference/configuration Configure automatic retry behavior for tasks that may fail transiently using Node.js. ```javascript import { task } from "sayiir"; const flakyTask = task("flaky_task", (data: Record) => { // May fail and retry with exponential backoff return process(data); }, { retry: { maxAttempts: 3, initialDelay: "1s", backoffMultiplier: 2.0, }, }); ``` -------------------------------- ### Configure PostgreSQL Backend in Rust Source: https://docs.sayiir.dev/reference/configuration Initialize the PostgreSQL backend in Rust for persistent storage. The `new` method takes a connection URL and requires an asynchronous context. ```rust use sayiir_postgres::PostgresBackend; // Connection URL format let backend = PostgresBackend::new( "postgresql://username:password@host:port/database" ).await?; // Example URLs let backend = PostgresBackend::new( "postgresql://postgres:password@localhost:5432/sayiir" ).await?; let backend = PostgresBackend::new( "postgresql://user:pass@db.example.com/workflows" ).await?; ``` -------------------------------- ### Python Retry Policy Examples Source: https://docs.sayiir.dev/reference/configuration Configure automatic retry behavior for tasks that may fail transiently using Python. ```python from sayiir import RetryPolicy, task # Simple: int shorthand (uses 1s initial delay, 2× backoff) @task(retries=3) def flaky_task(data: dict) -> dict: return process(data) # Full control: custom backoff timing @task(retries=RetryPolicy( max_retries=3, initial_delay_secs=0.5, backoff_multiplier=3.0, )) def critical_task(data: dict) -> dict: return process(data) ``` -------------------------------- ### Configure PostgreSQL Backend in Node.js Source: https://docs.sayiir.dev/reference/configuration Set up the PostgreSQL backend for production workflows in Node.js using a connection URL. The `connect` method is used for initialization. ```javascript import { PostgresBackend } from "sayiir"; // Connection URL format const backend = PostgresBackend.connect( "postgresql://username:password@host:port/database" ); // Example URLs const backend1 = PostgresBackend.connect("postgresql://postgres:password@localhost:5432/sayiir"); const backend2 = PostgresBackend.connect("postgresql://user:pass@db.example.com/workflows"); ``` -------------------------------- ### WorkflowClient Initialization and Operations (Node.js) Source: https://docs.sayiir.dev/changelog/v04 Initializes a WorkflowClient with a PostgresBackend and demonstrates submitting, cancelling, pausing, unpausing, sending signals, and checking the status of a workflow. ```javascript import { WorkflowClient, PostgresBackend } from "sayiir"; const backend = PostgresBackend.connect(process.env.DATABASE_URL!); const client = new WorkflowClient(backend); // Submit a workflow — a worker will pick it up const status = client.submit(workflow, "order-42", { items: [1, 2, 3] }); // Lifecycle operations client.cancel("order-42", { reason: "Out of stock", cancelledBy: "system" }); client.pause("order-42", { reason: "Maintenance window" }); client.unpause("order-42"); client.sendSignal("order-42", "approval", { approved: true }); const current = client.status("order-42"); ``` -------------------------------- ### Worker ID Naming Conventions Source: https://docs.sayiir.dev/reference/configuration Examples of Rust code snippets illustrating different strategies for generating unique worker IDs. ```rust // By hostname format!("worker-{}", hostname) // By hostname and process ID format!("worker-{}-{}", hostname, process_id) // By role and instance format!("email-worker-{}", instance_number) ``` -------------------------------- ### Get Workflow Instance Status Source: https://docs.sayiir.dev/reference/python-api Retrieve the current status of a workflow instance. The status object provides information about completion and output. ```python status = client.status("order-42") if status.is_completed(): print(status.output) ``` -------------------------------- ### WorkflowClient Initialization and Operations (Python) Source: https://docs.sayiir.dev/changelog/v04 Initializes a WorkflowClient with a PostgresBackend and demonstrates submitting, cancelling, pausing, unpausing, sending signals, and checking the status of a workflow. ```Python from sayiir import WorkflowClient, PostgresBackend backend = PostgresBackend("postgresql://localhost/sayiir") client = WorkflowClient(backend) # Submit a workflow — a worker will pick it up status = client.submit(workflow, "order-42", {"items": [1, 2, 3]}) # Lifecycle operations client.cancel("order-42", reason="Out of stock", cancelled_by="system") client.pause("order-42", reason="Maintenance window") client.unpause("order-42") client.send_signal("order-42", "approval", {"approved": True}) status = client.status("order-42") ``` -------------------------------- ### Resume Durable Workflow Source: https://docs.sayiir.dev/reference/nodejs-api Resumes a previously started durable workflow from its last saved checkpoint using the provided instance ID and backend. ```javascript import { resumeWorkflow } from "sayiir"; const status = resumeWorkflow(workflow, "run-001", backend); ``` -------------------------------- ### Initialize InMemoryBackend (Node.js) Source: https://docs.sayiir.dev/guides/durable-workflows Use InMemoryBackend for development and testing in Node.js. State is lost when the process stops. ```Node.js import { InMemoryBackend, PostgresBackend } from "sayiir"; // Development/testing const backend = new InMemoryBackend(); ``` -------------------------------- ### Python Task with Timeout and Retries Source: https://docs.sayiir.dev/guides/retries-and-timeouts Example of a Python task configured with a 5-second timeout and 3 retries. If the task exceeds the timeout, it will be retried. ```python from sayiir import task import time @task(timeout="5s", retries=3) def slow_operation(data: str) -> str: # If this takes >5 seconds, it will be retried time.sleep(10) return data ``` -------------------------------- ### Pluggable Codecs in Sayiir Source: https://docs.sayiir.dev/concepts/architecture Demonstrates how to instantiate different codec implementations for serialization. Choose based on performance (rkyv), readability (JSON), or custom needs. ```rust // Zero-copy for maximum performance (default) let codec = RkyvCodec::new(); ``` ```rust // Human-readable for debugging let codec = JsonCodec::new(); ``` ```rust // Custom format (implement Codec trait) let codec = MyCustomCodec::new(); ``` -------------------------------- ### Sayiir: Unified Task Model for Workflow Logic Source: https://docs.sayiir.dev/comparisons/vs-temporal This Sayiir example illustrates its unified task model where I/O operations are directly included within tasks, simplifying workflow development by eliminating the need to split logic between workflows and activities. ```python # Sayiir: Just write tasks @task async def fetch_user(user_id: int) -> dict: return await db.get_user(user_id) @task async def send_email(user: dict) -> None: await email_service.send_welcome(user) workflow = Flow("welcome").then(fetch_user).then(send_email).build() ``` -------------------------------- ### PostgreSQL Connection String Format Source: https://docs.sayiir.dev/guides/postgres-production Standard PostgreSQL URL format for establishing connections. Examples show basic, authenticated, and SSL-enabled connections. ```sql postgresql://[user[:password]@][host][:port][/database][?parameters] ``` -------------------------------- ### Sayiir: Define a Durable Task with @task Source: https://docs.sayiir.dev/comparisons/vs-elsa Any Python async function can be made durable by decorating it with `@task`. This example shows sending an email. ```python # Sayiir: Any function becomes a task @task async def send_email(to: str, subject: str, body: str): async with aiosmtplib.SMTP("smtp.example.com") as smtp: msg = MIMEText(body) msg["Subject"] = subject msg["To"] = to await smtp.send_message(msg) ``` -------------------------------- ### Resume All Workflow Instances with Options Source: https://docs.sayiir.dev/getting-started/cloudflare Resume all eligible workflow instances, including those parked due to delays or waiting for signals, and those at risk of eviction. This example includes options to set a stale threshold and a limit for the number of instances to resume. ```typescript async scheduled(event: ScheduledEvent, env: Env): Promise { const engine = await Engine.create(env.DB); await engine.resumeAll(orderApproval, { staleAfter: 60, limit: 20 }); } ``` -------------------------------- ### Connect to PostgresBackend Source: https://docs.sayiir.dev/reference/nodejs-api Establishes a connection to a PostgreSQL database for production use, ensuring state survives process restarts. Requires PostgreSQL 13 or higher and supports auto-migration. ```javascript import { PostgresBackend } from "sayiir"; const backend = PostgresBackend.connect("postgresql://user:pass@localhost:5432/sayiir"); ``` -------------------------------- ### Parallel Execution with Fork and Join Source: https://docs.sayiir.dev/getting-started/python Use `.fork()` to start parallel branches and `.join()` to combine their results. This is useful for running independent tasks concurrently. ```python from sayiir import task, Flow, branch, run_workflow @task def validate_payment(order: dict) -> dict: return {"payment": "valid"} @task def check_inventory(order: dict) -> dict: return {"stock": "available"} workflow = ( Flow("checkout") .fork([ branch("payment", validate_payment), branch("inventory", check_inventory), ]) .join("finalize", lambda results: {**results[0], **results[1]}) .build() ) result = run_workflow(workflow, {"id": 1}) ``` -------------------------------- ### Rust Task with Timeout and Retries Source: https://docs.sayiir.dev/guides/retries-and-timeouts An example of a Rust task configured with a 5-second timeout and 3 retries. Exceeding the timeout will trigger a retry for the task. ```rust #[task(timeout = "5s", retries = 3, backoff = "2s")] async fn slow_operation(data: String) -> Result { // If this takes >5 seconds, it will be retried tokio::time::sleep(Duration::from_secs(10)).await; Ok(data) } ``` -------------------------------- ### WorkflowClient Initialization and Operations (Rust) Source: https://docs.sayiir.dev/changelog/v04 Initializes a WorkflowClient with a PostgresBackend and demonstrates submitting, cancelling, pausing, unpausing, sending signals, and checking the status of a workflow. ```rust use sayiir_runtime::WorkflowClient; let backend = PostgresBackend::::connect(url)?; let client = WorkflowClient::new(backend); // Submit a workflow — a worker will pick it up let (status, output) = client.submit(&workflow, "order-42", input).await?; // Lifecycle operations client.cancel("order-42", Some("Out of stock".into()), Some("system".into())).await?; client.pause("order-42", Some("Maintenance window".into()), None).await?; client.unpause("order-42").await?; client.send_event("order-42", "approval", payload).await?; let status = client.status("order-42").await?; ``` -------------------------------- ### Add Sayirre Dependencies Source: https://docs.sayiir.dev/getting-started/rust Add the core runtime and your chosen backend to your Cargo.toml file. ```toml [dependencies] sayiir-runtime = "0.4" # core runtime + runners + macros # Optional — pick your backend sayiir-persistence = "0.4" # InMemoryBackend (dev/testing) sayiir-postgres = "0.4" # PostgreSQL backend (production) ``` -------------------------------- ### SQL: Query Active Workflows Source: https://docs.sayiir.dev/guides/postgres-production Example SQL query to monitor active workflows by counting instances grouped by their status within the last hour. ```sql SELECT status, COUNT(*) FROM sayiir_workflow_instances WHERE created_at > NOW() - INTERVAL '1 hour' GROUP BY status; ``` -------------------------------- ### Get a Specific Task Result Source: https://docs.sayiir.dev/reference/python-api Retrieve the result of a specific task within a workflow instance. Returns the decoded output or None if the task was not executed. ```python result = client.get_task_result("order-42", "validate_order") if result is not None: print(result) ``` -------------------------------- ### Rust: Define and Run a Workflow with Signals Source: https://docs.sayiir.dev/guides/signals-and-events This Rust snippet defines a workflow using Sayiir's core and persistence crates. It demonstrates starting a workflow, sending an event to trigger a signal, and resuming the workflow to completion using an in-memory backend. ```rust use sayiir_core::prelude::*; use sayiir_persistence::InMemoryBackend; use sayiir_runtime::CheckpointingRunner; use serde_json::json; use std::time::Duration; #[task] async fn create_order(id: u64) -> Result { println!("Creating order {}", id); Ok(json!({ "order_id": id, "status": "pending" })) } #[task] async fn fulfill(approval: serde_json::Value) -> Result { let approver = approval["by"].as_str().unwrap_or("unknown"); println!("Fulfilling order (approved by {})", approver); Ok(format!("Fulfilled order (approved by {})", approver)) } #[tokio::main] async fn main() -> Result<(), BoxError> { let ctx = WorkflowContext::default(); let backend = InMemoryBackend::new(); let runner = CheckpointingRunner::new(backend.clone()); let workflow = WorkflowBuilder::new(ctx) .with_registry() .then_fn(create_order) .wait_for_signal( "approval_wait", "manager_approval", Some(Duration::from_secs(86400)) ) .then_fn(fulfill) .build()?; // Start workflow let status = runner.run(&workflow, "order-42", 42u64).await?; println!("Status: {:?}", status); // WaitingForSignal // External system sends approval let payload = json!({"by": "alice"}); backend.send_event("order-42", "manager_approval", payload).await?; // Resume workflow let status = runner.resume(&workflow, "order-42").await?; println!("Final status: {:?}", status); // Completed Ok(()) } ``` -------------------------------- ### Node.js Durable Workflow Execution and Resumption Source: https://docs.sayiir.dev/guides/retries-and-timeouts Illustrates starting a durable workflow in Node.js and resuming it. Retry state persistence ensures continuity after process interruptions. ```javascript import { runDurableWorkflow, resumeWorkflow } from "sayiir"; // Start workflow with retries const status = runDurableWorkflow(workflow, "job-1", "https://flaky-api.com", backend); // Process crashes during backoff... // Resume later - retry state is preserved const status2 = resumeWorkflow(workflow, "job-1", backend); ``` -------------------------------- ### Task Metadata Access within Task Source: https://docs.sayiir.dev/reference/python-api Access task metadata like timeout, retries, and tags from the TaskExecutionContext. This example shows how to print these values if available. ```python @task(timeout="30s", retries=3, tags=["io"]) def fetch_data(url: str) -> dict: ctx = get_task_context() if ctx: print(f"Timeout: {ctx.metadata.timeout_secs}s") print(f"Tags: {ctx.metadata.tags}") return do_fetch(url) ``` -------------------------------- ### Sayiir Distributed Workers Example Source: https://docs.sayiir.dev/comparisons/vs-prefect Demonstrates how to set up and use Sayiir's optional distributed worker pool for executing tasks in a separate process. This model simplifies distributed execution without requiring complex deployment configurations. ```python # Sayiir distributed workers (optional) from sayiir import Flow, WorkerPool, task @task async def expensive_task(data: str) -> str: # Long-running computation return f"Processed {data}" workflow = Flow("distributed").then(expensive_task).build() # Start worker pool (separate process) pool = WorkerPool(backend=postgres_backend, concurrency=10) pool.start() # Submit from main application await run_workflow(workflow, "data", backend=postgres_backend) # Worker pool automatically picks up and executes ``` -------------------------------- ### Use PostgreSQL Backend for Durability Source: https://docs.sayiir.dev/getting-started/python Swap to `PostgresBackend` for production environments. This backend connects and runs migrations automatically. ```python from sayiir import PostgresBackend # Connects and runs migrations automatically backend = PostgresBackend("postgresql://localhost/sayiir") status = run_durable_workflow(workflow, "order-123", 42, backend=backend) ``` -------------------------------- ### Integrate Zod for Input Validation Source: https://docs.sayiir.dev/getting-started/nodejs Use Zod schemas to validate workflow inputs and task inputs. This example defines an `OrderSchema` for validating order details. ```typescript import { z } from "zod"; import { task, flow, runWorkflow } from "sayiir"; const OrderSchema = z.object({ orderId: z.string(), amount: z.number().positive(), }); const processOrder = task("process-order", (order) => { // order is validated and typed as { orderId: string; amount: number } return { status: "ok", message: `Processed $${order.amount}` }; }, { input: OrderSchema, }); const workflow = flow("typed").then(processOrder).build(); const result = await runWorkflow(workflow, { orderId: "1", amount: 99.99 }); ``` -------------------------------- ### Run Workflow with Persistence (Production) Source: https://docs.sayiir.dev/reference/python-api For production, use `run_workflow` with an `instance_id` and a `backend` (like `PostgresBackend`) for full checkpointing. The output is still returned directly. ```python from sayiir import run_workflow, PostgresBackend # Production — same function, just add params backend = PostgresBackend("postgresql://localhost/sayiir") result = run_workflow(workflow, 21, instance_id="run-001", backend=backend) ``` -------------------------------- ### Get Task Execution Context Source: https://docs.sayiir.dev/reference/nodejs-api Retrieve the current task execution context from within a running task. Returns null if called outside of a task execution. ```typescript import { task, getTaskContext } from "sayiir"; const myTask = task("my-task", (data: { value: number }) => { const ctx = getTaskContext(); if (ctx) { console.log(`Running task ${ctx.taskId} in workflow ${ctx.workflowId}`); console.log(`Instance: ${ctx.instanceId}`); } return { result: data.value * 2 }; }); ``` -------------------------------- ### Initialize InMemoryBackend (Rust) Source: https://docs.sayiir.dev/guides/durable-workflows Use InMemoryBackend for development and testing in Rust. State is lost when the process stops. ```Rust use sayiir_persistence::InMemoryBackend; use sayiir_postgres::PostgresBackend; use sayiir_runtime::prelude::*; // Development/testing let backend = InMemoryBackend::new(); ```