Skip to content

chore: release main - #519

Merged
kinyoklion merged 2 commits into
mainfrom
release-please--branches--main
Jul 22, 2024
Merged

kinyoklion merged 2 commits into
mainfrom
release-please--branches--main

Conversation

@github-actions

@github-actions github-actions Bot commented Jul 18, 2024 •

Copy link
Copy Markdown
Contributor

πŸ€– I have created a release beep boop

react-native-client-sdk: 10.3.0

10.3.0 (2024-07-19)

Features

  • Update expo and RN version used in example. (#520) (b8384c4)

Bug Fixes

  • Make it more clear what is happening when an event source is connecting. (#518) (52055ba)

This PR was generated with Release Please. See documentation.

@github-actions
github-actions Bot requested a review from a team as a code owner July 18, 2024 19:49
@github-actions
github-actions Bot force-pushed the release-please--branches--main branch from fff6c56 to ca03d66 Compare July 19, 2024 17:03
@kinyoklion
kinyoklion merged commit 60ac92b into main Jul 22, 2024
@kinyoklion
kinyoklion deleted the release-please--branches--main branch July 22, 2024 21:37
@github-actions

Copy link
Copy Markdown
Contributor Author

πŸ€– Created releases:

tanderson-ld added a commit that referenced this pull request Sep 28, 2026
…off (#2045)

## Summary

Adds a reusable retry controller to `@launchdarkly/js-sdk-common` that
owns retry tracking, health checking, and delay calculation for
long-running components. This is the foundation for RETRY-spec
conformance in the Node server SDK (SDK-2790); the data-source wiring
that consumes it arrives in a follow-up PR. **There are no consumers in
this PR** β€” the additions are unused by product code and exercised only
by tests.

The design ports the controller pattern from the Python server SDK
([python-server-sdk#522](launchdarkly/python-server-sdk#522),
wired in
[#519](launchdarkly/python-server-sdk#519)),
with deliberate differences noted below.

## What's where

- **`src/datasource/retry/`** β€” the retry mechanics: the `RetryState`
interface and its `createRetryState` factory (the controller), the
`ResetPolicy` interface with its two implementations (`AfterHealthyFor`
for streaming's healthy-duration reset, `AfterConsecutiveSuccesses` for
polling's two-in-a-row reset), and the `forStreaming`/`forPolling`
factories that bind the standard values (1s→30s normal and 5min→1hr
extended regimes; 60s healthy window; two-success polling reset) with
warn-and-default validation of the configured delay.
- **`src/errors.ts`** β€” failure classification, placed beside the legacy
helper it supersedes: `FailureKind` (`'normal' | 'unexpected'`),
`classifyHttpStatus` (400/408/429 and 5xx and non-error statuses are
normal; any other 4xx is unexpected), and `classifyTransportFailure`
(always normal β€” in an all-HTTPS system certificate failures can't be
reliably distinguished from transient faults). `isHttpRecoverable` now
delegates to `classifyHttpStatus` β€” one table, no drift β€” and is
documented as superseded; it stays undeprecated because the
event-delivery pathway still legitimately consumes it until that pathway
migrates.

## Design points for review

- **`RetryState` is an interface, not a class.**
`createRetryState(config)` returns it, backed by a closure over local
state; `forStreaming`/`forPolling` return the interface. This keeps the
publicly exposed surface an interface (per the repo's prefer-interfaces
guideline) and lets future mutators be added additively. The
`ResetPolicy` implementations stay classes, since the `ResetPolicy`
interface already fronts them everywhere they are consumed.
- **Clockless seams.** No method of `RetryState` or `ResetPolicy` takes
a timestamp. Time lives in exactly one place: `AfterHealthyFor`'s
constructor-injected clock, defaulting to a monotonic source
(`performance.now()`, with a `Date.now` closure fallback for exotic
runtimes). The controller itself holds no clock; its only injectable is
`random`, for deterministic jitter tests. (The parameter is named
`clock` rather than the codebase's `timeStamper` deliberately β€” it is
not a timestamp source.)
- **Ceiling-bounded backoff, no exponent constant.** The delay
computation compares the base against the ceiling scaled *down* (`base
>= max / 2**exponent`) rather than scaling the base up, so nothing can
overflow the ceiling β€” the same compare-before-shift structure as the
.NET implementation, expressed in lossless power-of-two float math. A
zero base (legal: the spec forbids flooring server-directed retry
values) short-circuits, closing a `0 Γ— Infinity = NaN` edge otherwise
reachable when a zero-valued server-directed retry is followed by very
many failures.
- **A configured delay above the normal ceiling clamps to it.** In the
normal regime the ceiling wins, matching the literal spec (1.3.2 +
1.4.2) and the majority of the SDK fleet (Go, Java, .NET). The extended
regime is the opposite: its bounds are floored at the configured delay,
which spec requirement 1.5.4.1 mandates ("a delay or ceiling that
applies after an `unexpected` failure MUST NOT be less than the
component's initial delay").
- **`applyServerDirectedRetry(ms)`** β€” the SSE `retry:` entry point:
sticky base that takes precedence over the regime's initial delay
(including the extended regime's), doubling restarted, ceiling still
applies, survives a healthy reset. Wire-level validation and the 1-hour
cap live in the SSE library
([launchdarkly/js-eventsource#40](launchdarkly/js-eventsource#40)),
not here.
- **Post-success wait = operating cadence**, even while the retry state
is raised β€” a recovering poller returns to schedule immediately rather
than serving one more extended-regime wait.

## Testing

89 tests across three suites (584 package-wide, all green):

- `RetryState.test.ts` (48) β€” exact delay ladders for both regimes under
injected clock/random (including the 5m/10m/20m/40m/1h/1h extended
ladder), regime transition/ratchet/re-arm, anchor-once healthy-stretch
discrimination, reset-before-count ordering, fast-second-poll cadence,
poll-interval wait floor under real jitter, jitter range with the
maximal-draw boundary (the exact-`T/2` tie) and distinctness assertions,
server-directed retry semantics
(replace/clamp/persist/reject-invalid/later-wins, precedence over both
regimes' initial delays, `retry: 0` staying finite through 1,100
failures), normal-ceiling clamp and regime-collapse cases, the
extended-floor mandate under direct construction,
flapping-never-ratchets, high-n robustness, and factory validation
matrices.
- `ResetPolicy.test.ts` (8) β€” the policy seam directly: threshold
boundary, anchor-once under repeated healthy reports, failure-clears,
consecutive-success counting, and both default-clock closures (monotonic
and the `Date.now` fallback).
- `errors.test.ts` (33) β€” the full classification matrix including
boundary and non-error statuses, transport classification, and parity
pins on the legacy `isHttpRecoverable` truth table through the
delegation.

The `src/datasource/retry` module is at 100% statement, branch,
function, and line coverage. Coverage was cross-checked against the
Python, Java, Go, and .NET RETRY test suites; the one technique
deliberately not ported is .NET's `BigInteger`/randomized reference
sweeps, which exist to exercise 64-bit integer shift surfaces that JS
float arithmetic does not have.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> Introduces a **RETRY-spec-oriented retry controller** in
`@launchdarkly/js-sdk-common` as shared library code onlyβ€”**no data
sources or SDKs call it yet**; follow-up PRs will wire
streaming/polling.
> 
> Adds `src/datasource/retry/` with **`RetryState`**
(`createRetryState`, `forStreaming`, `forPolling`): exponential backoff
with jitter, normal vs **extended** regimes after `unexpected` failures,
pluggable **`ResetPolicy`** (healthy-for duration for streaming,
consecutive successes for polling), and **`applyServerDirectedRetry`**
for SSE `retry:` values. **`errors.ts`** gains **`FailureKind`** plus
**`classifyHttpStatus`** / **`classifyTransportFailure`**;
**`isHttpRecoverable`** now delegates to the same rules.
> 
> New retry APIs are re-exported from the datasource barrel and package
**`index`**. CI **package size limit** for common ESM rises **29β€―000 β†’
29β€―500** bytes. Coverage is **89 new tests** across retry and error
classification.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
7f529cf. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant