Skip to content

Add @retry decorator to bubus-ts#5

Merged
pirate merged 12 commits intomainfrom
claude/add-retry-decorator-L3GT1
Feb 9, 2026
Merged

Add @retry decorator to bubus-ts#5
pirate merged 12 commits intomainfrom
claude/add-retry-decorator-L3GT1

Conversation

@pirate
Copy link
Copy Markdown
Member

@pirate pirate commented Feb 9, 2026

No description provided.

Standalone higher-order function / TC39 decorator that adds configurable
retry logic and semaphore-based concurrency limiting to any async function.
Works independently of the event bus (on plain functions, class methods,
or event handlers).

Features:
- max_attempts, retry_after, retry_backoff_factor, retry_on_errors, timeout
- Global semaphore registry (semaphore_limit, semaphore_name, semaphore_lax)
- AsyncLocalStorage-based re-entrancy tracking to prevent deadlocks when
  nested/recursive calls share the same semaphore
- 30 tests covering retry logic, backoff, error filtering, timeouts,
  semaphore concurrency, re-entrancy, and event bus integration

https://claude.ai/code/session_01TyuqFQFwDXa4h5QzQDCUsv
Switch from a separate node:async_hooks import to the existing
createAsyncLocalStorage() factory from async_context.ts. This ensures
browser compatibility by gracefully degrading to a no-op when
AsyncLocalStorage is unavailable.

https://claude.ai/code/session_01TyuqFQFwDXa4h5QzQDCUsv
@claude
Copy link
Copy Markdown

claude Bot commented Feb 9, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

retry_on_errors now accepts a mix of:
- Error class constructors (instanceof check)
- String error names (matched against error.name)
- RegExp patterns (tested against String(error))

https://claude.ai/code/session_01TyuqFQFwDXa4h5QzQDCUsv
@claude
Copy link
Copy Markdown

claude Bot commented Feb 9, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

@claude
Copy link
Copy Markdown

claude Bot commented Feb 9, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

- 'global': all calls share one semaphore (default, existing behavior)
- 'class': keyed by constructor.name — all instances of a class share one
- 'instance': keyed by WeakMap identity — each object gets its own

Falls back to 'global' when `this` is not an object (standalone calls).
Multiprocess scope is not supported (single-process JS runtime).

https://claude.ai/code/session_01TyuqFQFwDXa4h5QzQDCUsv
@claude
Copy link
Copy Markdown

claude Bot commented Feb 9, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

Tests verify: @Retry() works with native TC39 Stage 3 decorator syntax
on class methods, preserves `this` context, composes with semaphore_scope
(class/instance), works with bus.on() via .bind(), and class/instance
scopes correctly fall back to global for standalone functions.

https://claude.ai/code/session_01TyuqFQFwDXa4h5QzQDCUsv
@claude
Copy link
Copy Markdown

claude Bot commented Feb 9, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

Tests added (12 new, 51 total retry tests):
- TC39 @Retry() decorator on class methods with all 3 scopes
- @Retry + bus.on via .bind(this) for class/instance/global scopes
- HOF retry()(fn).bind(instance) pattern (bind after wrap)
- HOF retry()(fn.bind(instance)) → verifies scope falls back to global
- Standalone functions with class/instance scope → fall back to global

README updated:
- TC39 decorator syntax examples with bus.on + .bind(this)
- HOF .bind() ordering requirement documented
- Note on scope fallback for standalone/unbound functions

Also fixed flaky bus tests caused by handler ID collision (bus uses
ms-precision timestamps in handler ID hash — added 2ms delay between
same-millisecond handler registrations).

https://claude.ai/code/session_01TyuqFQFwDXa4h5QzQDCUsv
@claude
Copy link
Copy Markdown

claude Bot commented Feb 9, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

…ents

Verifies the pattern where retry() wraps the full bus.emit→event.done()
cycle so each retry dispatches a fresh event, while other events race
in parallel via Promise.all.

https://claude.ai/code/session_01TyuqFQFwDXa4h5QzQDCUsv
@claude
Copy link
Copy Markdown

claude Bot commented Feb 9, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

… wrapping

Rewrite README retry section to establish @Retry() on class methods as the
primary recommended pattern. Explain why retry/timeout is a handler-level
concern (handlers fail, events don't), why emit-level retry hurts
replayability/determinism, and how retry semaphores are orthogonal to bus
concurrency options. Mark the emit→done wrapping pattern as technically
supported but not recommended, with clear rationale.

Reorganize test section headers to reflect the recommended pattern hierarchy.

https://claude.ai/code/session_01TyuqFQFwDXa4h5QzQDCUsv
@claude
Copy link
Copy Markdown

claude Bot commented Feb 9, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

Implements event.first() which returns the first non-undefined handler result
value, then cancels remaining handlers (pending: cancelled, started: aborted
via signalAbort, plus their child events). Works with all concurrency modes:
parallel races all handlers, serial short-circuits after first success.

- BaseEvent: add _first_mode, _first_result, first() method
- EventBus.processEvent: monitor handler completions in first mode, cancel
  losers via new cancelEventHandlersForFirstMode() method
- 19 tests: parallel/serial, falsy values, @Retry integration, screenshot
  service pattern, error handling, child event cancellation
- README: document first() with examples and comparison to done()

https://claude.ai/code/session_01TyuqFQFwDXa4h5QzQDCUsv
@claude
Copy link
Copy Markdown

claude Bot commented Feb 9, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

Remove the internal _first_result property in favor of a computed getter
that reads from event_results sorted by completed_ts. This is cleaner —
the result is derived from the source of truth rather than duplicated.

https://claude.ai/code/session_01TyuqFQFwDXa4h5QzQDCUsv
@claude
Copy link
Copy Markdown

claude Bot commented Feb 9, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

…sibility

Replaces the internal _first_mode boolean with a proper event_handler_completion
field ('all' | 'first') that is part of the Zod schema, included in toJSON(),
and visible in replay logs. The field is orthogonal to event_handler_concurrency
(scheduling vs completion strategy).

https://claude.ai/code/session_01TyuqFQFwDXa4h5QzQDCUsv
@claude
Copy link
Copy Markdown

claude Bot commented Feb 9, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

@pirate pirate merged commit 305b6f8 into main Feb 9, 2026
14 of 19 checks passed
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