> ## 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.

# Event Debouncing

> Deduplicate expensive event work using find(..., past/future) patterns.

Debouncing is most useful when events trigger expensive work:

* screenshots or browser automation
* external API calls
* LLM/tool runs
* heavyweight DB/file operations

Instead of starting duplicate work every time, reuse:

* a recent matching event (`past` window), or
* a matching event that is about to be emitted by another caller (`future` wait), or
* both (history-first, then short future wait, then emit).

Debouncing in AbxBus is built from `find(...)` + conditional `emit(...)`.

## Debounce building blocks

* `past`: search recent history (`true`/`false`/seconds)
* `future`: optionally wait for a matching future emit (`true`/`false`/seconds)
* `where` / event-field filters: scope matching to the same "work key" (url, account\_id, document\_id, etc.)

See [Find Events](./find-events) for full option semantics.

## Pattern 1: Reuse recent completed work (history-only)

Use when "fresh enough" cached results are acceptable.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    existing = await bus.find(
        ScreenshotEvent,
        where=lambda e: e.url == url,
        past=10,        # look back 10s
        future=False,   # do not wait
    )

    event = existing or bus.emit(ScreenshotEvent(url=url))
    await event.now()
    result = await event.event_result()
    ```
  </Tab>

  <Tab title="TypeScript">
    ```ts theme={null}
    const existing = await bus.find(
      ScreenshotEvent,
      (event) => event.url === url,
      { past: 10, future: false }
    )

    const event = existing ?? bus.emit(ScreenshotEvent({ url }))
    await event.now()
    const result = await event.eventResult()
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    use abxbus::{
        event,
        event_bus::FindOptions,
    };
    use futures::executor::block_on;
    use serde_json::json;
    use std::collections::HashMap;

    event! {
        struct ScreenshotEvent {
            url: String,
            event_result_type: serde_json::Value,
        }
    }

    let existing = block_on(bus.find_with_options(
        ScreenshotEvent::event_type,
        FindOptions {
            past_window: Some(10.0),
            future: None,
            where_filter: Some(HashMap::from([("url".to_string(), json!(url))])),
            ..FindOptions::default()
        },
    ));

    let event = existing.unwrap_or_else(|| {
        bus.emit(ScreenshotEvent { url: url.to_string(), ..Default::default() })
    });
    block_on(event.now());
    let result = block_on(event.event_result())?;
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    existing, err := bus.FindEventName("ScreenshotEvent", func(event *abxbus.BaseEvent) bool {
    	return event.Payload["url"] == url
    }, &abxbus.FindOptions{
    	Past: 10.0,     // look back 10s
    	Future: false,  // do not wait
    })
    if err != nil {
    	panic(err)
    }

    event := existing
    if event == nil {
    	event = bus.Emit(abxbus.NewBaseEvent("ScreenshotEvent", map[string]any{"url": url}))
    }
    if _, err := event.Now(); err != nil {
    	panic(err)
    }
    result, err := event.EventResult()
    ```
  </Tab>
</Tabs>

## Pattern 2: Coalesce concurrent callers (future-only)

Use when many callers may request the same expensive action at the same time.

Caller A emits first. Caller B waits briefly for that same event instead of emitting a duplicate.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    in_flight = await bus.find(
        ScreenshotEvent,
        where=lambda e: e.url == url,
        past=False,    # skip history
        future=2,      # wait up to 2s for another caller to emit
    )

    event = in_flight or bus.emit(ScreenshotEvent(url=url))
    await event.now()
    result = await event.event_result()
    ```
  </Tab>

  <Tab title="TypeScript">
    ```ts theme={null}
    const inFlight = await bus.find(
      ScreenshotEvent,
      (event) => event.url === url,
      { past: false, future: 2 }
    )

    const event = inFlight ?? bus.emit(ScreenshotEvent({ url }))
    await event.now()
    const result = await event.eventResult()
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    let in_flight = block_on(bus.find_with_options(
        ScreenshotEvent::event_type,
        FindOptions {
            past: false,
            future: Some(2.0),
            where_filter: Some(HashMap::from([("url".to_string(), json!(url))])),
            ..FindOptions::default()
        },
    ));

    let event = in_flight.unwrap_or_else(|| {
        bus.emit(ScreenshotEvent { url: url.to_string(), ..Default::default() })
    });
    block_on(event.now());
    let result = block_on(event.event_result())?;
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    inFlight, err := bus.FindEventName("ScreenshotEvent", func(event *abxbus.BaseEvent) bool {
    	return event.Payload["url"] == url
    }, &abxbus.FindOptions{
    	Past: false,  // skip history
    	Future: 2.0,  // wait up to 2s for another caller to emit
    })
    if err != nil {
    	panic(err)
    }

    event := inFlight
    if event == nil {
    	event = bus.Emit(abxbus.NewBaseEvent("ScreenshotEvent", map[string]any{"url": url}))
    }
    if _, err := event.Now(); err != nil {
    	panic(err)
    }
    result, err := event.EventResult()
    ```
  </Tab>
</Tabs>

## Pattern 3: Hybrid debounce (past + short future + emit)

This is the most practical default for expensive endpoints.

1. Reuse recent match.
2. If none, wait briefly for someone else to emit.
3. If still none, emit new work.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    event = (
        await bus.find(ScreenshotEvent, where=lambda e: e.url == url, past=10, future=False)
        or await bus.find(ScreenshotEvent, where=lambda e: e.url == url, past=False, future=2)
        or bus.emit(ScreenshotEvent(url=url))
    )

    await event.now()
    result = await event.event_result()
    ```
  </Tab>

  <Tab title="TypeScript">
    ```ts theme={null}
    const event =
      (await bus.find(ScreenshotEvent, (e) => e.url === url, { past: 10, future: false })) ??
      (await bus.find(ScreenshotEvent, (e) => e.url === url, { past: false, future: 2 })) ??
      bus.emit(ScreenshotEvent({ url }))

    await event.now()
    const result = await event.eventResult()
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    let by_url = HashMap::from([("url".to_string(), json!(url))]);
    let event = block_on(bus.find_with_options(
        ScreenshotEvent::event_type,
        FindOptions {
            past_window: Some(10.0),
            future: None,
            where_filter: Some(by_url.clone()),
            ..FindOptions::default()
        },
    ))
    .or_else(|| {
        block_on(bus.find_with_options(
            ScreenshotEvent::event_type,
            FindOptions {
                past: false,
                future: Some(2.0),
                where_filter: Some(by_url.clone()),
                ..FindOptions::default()
            },
        ))
    })
    .unwrap_or_else(|| {
        bus.emit(ScreenshotEvent { url: url.to_string(), ..Default::default() })
    });

    block_on(event.now());
    let result = block_on(event.event_result())?;
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    sameURL := func(event *abxbus.BaseEvent) bool {
    	return event.Payload["url"] == url
    }

    event, err := bus.FindEventName("ScreenshotEvent", sameURL, &abxbus.FindOptions{Past: 10.0, Future: false})
    if err != nil {
    	panic(err)
    }
    if event == nil {
    	event, err = bus.FindEventName("ScreenshotEvent", sameURL, &abxbus.FindOptions{Past: false, Future: 2.0})
    	if err != nil {
    		panic(err)
    	}
    }
    if event == nil {
    	event = bus.Emit(abxbus.NewBaseEvent("ScreenshotEvent", map[string]any{"url": url}))
    }

    if _, err := event.Now(); err != nil {
    	panic(err)
    }
    result, err := event.EventResult()
    ```
  </Tab>
</Tabs>

## Pattern 4: Keyed helper for repeated use

Wrap the debounce logic once and reuse it for all expensive keyed actions.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    async def emit_debounced_screenshot(url: str):
        event = (
            await bus.find(ScreenshotEvent, where=lambda e: e.url == url, past=15, future=False)
            or await bus.find(ScreenshotEvent, where=lambda e: e.url == url, past=False, future=3)
            or bus.emit(ScreenshotEvent(url=url))
        )
        await event.now()
        return await event.event_result()
    ```
  </Tab>

  <Tab title="TypeScript">
    ```ts theme={null}
    const emitDebouncedScreenshot = async (url: string) => {
      const event =
        (await bus.find(ScreenshotEvent, (e) => e.url === url, { past: 15, future: false })) ??
        (await bus.find(ScreenshotEvent, (e) => e.url === url, { past: false, future: 3 })) ??
        bus.emit(ScreenshotEvent({ url }))

      await event.now()
      return await event.eventResult()
    }
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    fn emit_debounced_screenshot(bus: &EventBus, url: &str) -> Result<Option<serde_json::Value>, String> {
        let by_url = HashMap::from([("url".to_string(), json!(url))]);
        let event = block_on(bus.find_with_options(
            ScreenshotEvent::event_type,
            FindOptions {
                past_window: Some(15.0),
                future: None,
                where_filter: Some(by_url.clone()),
                ..FindOptions::default()
            },
        ))
        .or_else(|| {
            block_on(bus.find_with_options(
                ScreenshotEvent::event_type,
                FindOptions {
                    past: false,
                    future: Some(3.0),
                    where_filter: Some(by_url),
                    ..FindOptions::default()
                },
            ))
        })
        .unwrap_or_else(|| {
            bus.emit(ScreenshotEvent { url: url.to_string(), ..Default::default() })
        });

        block_on(event.now());
        block_on(event.event_result())
    }
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    func emitDebouncedScreenshot(ctx context.Context, bus *abxbus.EventBus, url string) (any, error) {
    	sameURL := func(event *abxbus.BaseEvent) bool {
    		return event.Payload["url"] == url
    	}

    	event, err := bus.FindEventName("ScreenshotEvent", sameURL, &abxbus.FindOptions{Past: 15.0, Future: false})
    	if err != nil {
    		return nil, err
    	}
    	if event == nil {
    		event, err = bus.FindEventName("ScreenshotEvent", sameURL, &abxbus.FindOptions{Past: false, Future: 3.0})
    		if err != nil {
    			return nil, err
    		}
    	}
    	if event == nil {
    		event = bus.Emit(abxbus.NewBaseEvent("ScreenshotEvent", map[string]any{"url": url}))
    	}

    	if _, err := event.Now(); err != nil {
    		return nil, err
    	}
    	return event.EventResult()
    }
    ```
  </Tab>
</Tabs>

## Important behavior notes

* `find(...)` resolves when an event is emitted, not when handlers finish.
* Always await completion after selecting a debounced event:
  * Python: `await event.now()`, then `await event.event_result()`
  * TypeScript: `await event.now()`, then `await event.eventResult()`
  * Go: `event.Now()`, then `event.EventResult()`
* Debouncing scope depends on your match key (`where` / event-field filters).\
  Use the narrowest key that represents "same work."
* Debouncing depends on retained history. If history is aggressively trimmed, your `past` window can become less effective.

See also:

* [Find Events](./find-events)
* [Event History Store](./event-history-store)
