Skip to content

Add EventBatcher transport#191

Draft
Reversean wants to merge 4 commits into
masterfrom
feat/event-batcher
Draft

Add EventBatcher transport#191
Reversean wants to merge 4 commits into
masterfrom
feat/event-batcher

Conversation

@Reversean
Copy link
Copy Markdown
Member

@Reversean Reversean commented Jun 1, 2026

Introduces EventBatcher — a Transport decorator that accumulates identical events within a configurable time window and forwards one representative message per unique signature with CatcherMessage.count set to the total number of occurrences.

Signature is computed from title, type, and per-frame coordinates (file, line, column, function) using null-byte delimiters. Lookup is O(1) via Map keyed on the signature string.

Flush is triggered by whichever condition is met first:

  • time window expires (default 5 s from first event in window)
  • buffer reaches maxBufferSize distinct signatures (default 100)
  • flush() is called explicitly (e.g. on pagehide)

BrowserCatcher wraps its transport in EventBatcher and registers a capture-phase pagehide listener to drain the buffer before the socket closes.

Protocol: CatcherMessage.count is declared via module augmentation on @hawk.so/types. Server must increment totalCount by count (defaulting to 1) and create a single Repetition record per batch.

Reversean and others added 3 commits May 27, 2026 20:02
…batching

Introduces EventBatcher — a Transport decorator that accumulates identical
events within a configurable time window and forwards one representative
message per unique signature with CatcherMessage.count set to the total
number of occurrences.

Signature is computed from title, type, and per-frame coordinates
(file, line, column, function) using null-byte delimiters. Lookup is O(1)
via Map keyed on the signature string.

Flush is triggered by whichever condition is met first:
- time window expires (default 5 s from first event in window)
- buffer reaches maxBufferSize distinct signatures (default 100)
- flush() is called explicitly (e.g. on pagehide)

BrowserCatcher wraps its transport in EventBatcher and registers a
capture-phase pagehide listener to drain the buffer before the socket
closes.

Protocol: CatcherMessage.count is declared via module augmentation on
@hawk.so/types. Server must increment totalCount by count (defaulting to 1)
and create a single Repetition record per batch.
*
* @param message - message to compute signature for
*/
function computeSignature<T extends CatcherMessageType>(message: CatcherMessage<T>): string {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets make it work like in Grouper (without backtrace)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed backtrace from key.

I think it's ok, if we still represent it as string concat, not hash as grouper does.


if (existing !== undefined) {
existing.count++;
} else {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if event is not batched, send it immediately (or with small delay to wait for duplicates)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now sending events with 5sec delay to wait duplicates.

* First occurrence is used as representative event for each batch.
* Context, user, and breadcrumbs of subsequent identical occurrences are not preserved.
*/
export class EventBatcher<T extends CatcherMessageType> implements Transport<T> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets call it "dedupe" instead of "batching" (because we will add batching later)

So lets the class EventDedupeTransport

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

import type { BacktraceFrame, CatcherMessage, CatcherMessageType } from '@hawk.so/types';
import type { Transport } from './transport';

declare module '@hawk.so/types' {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be added to the types package

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- Renamed EventBatcher to EventDedupeTransport
- removed backtrace from event key for debounce
@Reversean Reversean force-pushed the feat/event-batcher branch from d0c37a1 to 07cc623 Compare June 1, 2026 18:54
*/
constructor(transport: Transport<T>, options: EventDedupeTransportOptions = {}) {
this.transport = transport;
this.windowMs = options.windowMs ?? 5_000;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think five 5 sec is too large delay. We can loose a lots of errors.

Suggested change
this.windowMs = options.windowMs ?? 5_000;
this.windowMs = options.windowMs ?? 2_500;

if (existing !== undefined) {
existing.count++;

return;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we reschedule timer here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants