> ## Documentation Index
> Fetch the complete documentation index at: https://abxbus.archivebox.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Context Propagation

> Context variables are passed from emit site to event handlers automatically.

## What are Context Variables?

[Context variables](https://nodejs.org/api/async_context.html#asynchronous-context-tracking) are a feature in some languages and runtimes, unrelated to `abxbus`, that let you set ambient "thread-local variables" that may be needed by many functions without passing them manually.

Context variables are mostly used for repetitive arguments that would clutter function signatures if they had to be passed through every layer, such as request IDs, trace and span IDs, user IDs, database transaction IDs, and connection handles.

They are especially common in FastAPI, Fastify, Express/Nest, and OpenTelemetry.

## How `abxbus` works in each runtime

`abxbus` generally snapshots context variables at emit time and provides the snapshot to each event handler when it executes.

For async or queued events, this gives handlers a consistent view of history that matches event dispatch order. This matters because dispatch order is the only strict order that exists when using any of the parallel `event_concurrency` or `event_handler_concurrency` modes.

Event execution order is only statically guaranteed with:

```ts theme={null}
event_concurrency: 'global-serial' | 'bus-serial'
```

These are the fully predictable modes, similar to a database's `STRICT SERIALIZABLE` mode, but they can be impractically slow when processing more than a few thousand events in a complex system. More parallel modes are typically used for performance.

* Python uses [`ContextVars`](https://docs.python.org/3/library/contextvars.html#contextvars.Context) (`contextvars.ContextVar`).
* TypeScript (Node/Bun) uses [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#asynchronous-context-tracking).
* Rust uses [`thread_local!`](https://doc.rust-lang.org/std/macro.thread_local.html) ([`dcontext`](https://docs.rs/dcontext/latest/dcontext/) snapshots are captured at emit time and restored for handlers).
* Go uses [`context.Context`](https://pkg.go.dev/context) values captured with `EmitWithContext(...)` and restored during handler execution.

## Why this matters

Without propagation, event-based code would be difficult to integrate gradually into larger existing systems.
With propagation, event handlers can log/trace normally like any other request handler or method an existing backend or framework.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from contextvars import ContextVar
    from abxbus import EventBus, BaseEvent

    request_id: ContextVar[str] = ContextVar('request_id', default='<unset>')

    class RequestEvent(BaseEvent):
        pass

    bus = EventBus('AppBus')

    async def handler(_: RequestEvent) -> None:
        print(request_id.get())
        # req-123

    bus.on(RequestEvent, handler)
    request_id.set('req-123')
    await bus.emit(RequestEvent()).now()
    ```
  </Tab>

  <Tab title="TypeScript">
    ```ts theme={null}
    import { AsyncLocalStorage } from 'node:async_hooks'
    import { BaseEvent, EventBus } from 'abxbus'

    const requestContext = new AsyncLocalStorage<{ requestId: string }>()
    const RequestEvent = BaseEvent.extend('RequestEvent', {})
    const bus = new EventBus('AppBus')

    bus.on(RequestEvent, () => {
      console.log(requestContext.getStore()?.requestId)
    })

    await requestContext.run({ requestId: 'req-123' }, async () => {
      await bus.emit(RequestEvent({})).now()
    })
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    use abxbus::{event, event_bus::EventBus};
    use dcontext::{enter_named_scope, get_context, set_context, try_initialize, RegistryBuilder};
    use futures::executor::block_on;

    event! {
        struct RequestEvent {
            event_result_type: (),
        }
    }

    let mut registry = RegistryBuilder::new();
    registry.register::<String>("request_id");
    let _ = try_initialize(registry);

    let bus = EventBus::new(Some("AppBus".to_string()));

    bus.on(RequestEvent, |_event: RequestEvent| async move {
        println!("{}", get_context::<String>("request_id"));
        // req-123
        Ok(())
    });

    let _scope = enter_named_scope("request");
    set_context("request_id", "req-123".to_string());

    let event = bus.emit(RequestEvent { ..Default::default() });
    block_on(event.now());
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    package main

    import (
    	"context"
    	"fmt"

    	abxbus "github.com/ArchiveBox/abxbus/abxbus-go"
    )

    type contextKey string

    func main() {
    	requestIDKey := contextKey("request_id")
    	bus := abxbus.NewEventBus("AppBus", nil)

    	bus.On("RequestEvent", "handler", func(event *abxbus.BaseEvent, ctx context.Context) (any, error) {
    		fmt.Println(ctx.Value(requestIDKey))
    		// req-123
    		return nil, nil
    	}, nil)

    	ctx := context.WithValue(context.Background(), requestIDKey, "req-123")
    	if _, err := bus.EmitWithContext(ctx, abxbus.NewBaseEvent("RequestEvent", nil)).Now(); err != nil {
    		panic(err)
    	}
    }
    ```
  </Tab>
</Tabs>

## Web server style examples

These patterns are typical in frameworks where each incoming request gets a request-local context object.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # FastAPI-style shape (conceptual)
    request_id.set(incoming_request.headers.get('x-request-id', 'generated-id'))
    await bus.emit(RequestEvent()).now()
    # handlers can still read request_id.get()
    ```
  </Tab>

  <Tab title="TypeScript">
    ```ts theme={null}
    // Fastify-style shape (conceptual)
    await requestContext.run({ requestId: req.id }, async () => {
      await bus.emit(RequestEvent({})).now()
    })
    // handlers can still read requestContext.getStore()
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    let _scope = enter_named_scope("request");
    set_context("request_id", incoming_request_id.to_string());

    let event = bus.emit(RequestEvent { ..Default::default() });
    block_on(event.now());
    // handlers can still read get_context::<String>("request_id")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // net/http-style shape (conceptual)
    ctx := context.WithValue(req.Context(), requestIDKey, req.Header.Get("x-request-id"))
    if _, err := bus.EmitWithContext(ctx, abxbus.NewBaseEvent("RequestEvent", nil)).Now(); err != nil {
    	panic(err)
    }
    // handlers can still read ctx.Value(requestIDKey)
    ```
  </Tab>
</Tabs>

## Browser runtime note

`AsyncLocalStorage` is a Node/Bun API and is not available in browser runtimes.

In browsers:

* AbxBus still works normally for events.
* ambient async context propagation via `AsyncLocalStorage` is not available.
* pass correlation/tracing fields explicitly in event payloads when you need that metadata.

See [Supported Runtimes](../operations/supported-runtimes) for runtime compatibility details.

## Golang runtime note

Go does not have ambient `AsyncLocalStorage`/`ContextVar` state; pass the request context at emit time with `bus.EmitWithContext(...)` or `event.EmitWithContext(...)` and AbxBus propagates that `context.Context` into handlers and awaited child events.
