Skip to content

feat(js-sdk): support retry-after header#504

Closed
ryanpq wants to merge 5 commits into
mainfrom
feat(js-sdk)-support-retry-after-header
Closed

feat(js-sdk): support retry-after header#504
ryanpq wants to merge 5 commits into
mainfrom
feat(js-sdk)-support-retry-after-header

Conversation

@ryanpq

@ryanpq ryanpq commented Mar 4, 2025

Copy link
Copy Markdown
Contributor

Description

Honor the retry-after header so that a client does not overwhelm the FGA server with requests.

References

Closes: openfga/js-sdk#208

Review Checklist

  • I have clicked on "allow edits by maintainers".
  • I have added documentation for new/changed functionality in this PR or in a PR to openfga.dev [Provide a link to any relevant PRs in the references section above]
  • The correct base branch is being used, if not main
  • I have added tests to validate that the change in functionality is working as expected

Summary by CodeRabbit

  • New Features

    • Added configurable HTTP retries with exponential backoff and support for Retry-After headers.
    • Improved resilience to network errors and refined status-based error handling for rate limits and server errors.
  • Bug Fixes

    • More accurate retry delays and error mapping, reducing spurious failures and improving reliability.
  • Tests

    • Expanded coverage for retry scenarios (including Retry-After and backoff), token exchange flows, and error handling.
    • Reorganized test suites and added support for custom response headers in token exchange mocks.

@ryanpq
ryanpq marked this pull request as ready for review March 5, 2025 17:42
@ryanpq
ryanpq requested a review from a team as a code owner March 5, 2025 17:42
fgaApi.check(baseConfig.storeId!, { tuple_key: tupleKey }, { retryParams: { maxRetry: 0 }})
).rejects.toThrow(FgaApiInternalError);
});
it("should throw FgaApiInternalError for 500 error without Retry-After header", async () => {

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.

Without the retry header, it should fall back to exponential backoff

if (status === 429) {
retryDelayMs = randomTime(iterationCount, config.minWaitInMs);
} else if (status >= 500 && status !== 501) {
// For 5xx (except 501), we only retry if Retry-After was present

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.

Let's change this so that we fall back to exponential backoff here too

const secondsValue = parseInt(retryAfterValue, 10);
if (!isNaN(secondsValue)) {
const msValue = secondsValue * 1000;
if (msValue >= 1000 && msValue <= 1800000) {

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.

rather than these being hard-coded, now that the go-sdk changes have been merged, you can use the following variables:

  • {{maxBackoffTimeInSec}}
Suggested change
if (msValue >= 1000 && msValue <= 1800000) {
if (msValue > 0 && msValue <= {{retryHeaderMaxAllowableDurationInSec}}) {
}

const now = new Date();
const delayMs = dateValue.getTime() - now.getTime();

if (delayMs >= 1000 && delayMs <= 1800000) {

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.

This logic looks repeated, can you extract it to it's own function and reference it above and here?

const max = Math.ceil(2 ** (loopCount + 1) * minWaitInMs);
return Math.floor(Math.random() * (max - min) + min); //The maximum is exclusive and the minimum is inclusive
const calculatedTime = Math.floor(Math.random() * (max - min) + min);
return Math.min(calculatedTime, 120000);

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.

Suggested change
return Math.min(calculatedTime, 120000);
return Math.min(calculatedTime, {{maxBackoffTimeInSec}});

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.

Defined here:

"maxBackoffTimeInSec": 120,
"retryHeaderMaxAllowableDurationInSec": 1800,

throw new FgaApiNotFoundError(err);
} else if (status === 429 || status >= 500) {
if (iterationCount >= config.maxRetry) {
} else if (status === 429 || (status >= 500 && status !== 501)) {

@rhamzeh rhamzeh Mar 16, 2025

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.

@rhamzeh
rhamzeh marked this pull request as draft April 28, 2025 15:33
@coderabbitai

coderabbitai Bot commented Oct 8, 2025

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly states the addition of Retry-After header support in the JS SDK and follows the conventional commit style. It accurately captures the core feature introduced in the pull request without extraneous information. This conciseness ensures that teammates can immediately understand the primary change.
Linked Issues Check ✅ Passed The changes introduce structured retry logic that honors the Retry-After header, falls back to exponential backoff with configurable minWait and maxRetry, and implement specific handling for 429s, 5xxs, and network errors while dropping X-Rate-Limit-Reset, matching the objectives of issue #208. Tests were added to cover header parsing and backoff behavior and internal helper logic parses and validates the Retry-After header. Although default values and jitter bounds are inferred via minWaitInMs and config, the summary demonstrates coverage of the coding requirements.
Out of Scope Changes Check ✅ Passed All modifications relate to implementing and testing the enhanced retry strategy defined in issue #208, including parser logic, backoff behavior, and test mocks for headers, and no unrelated functionality was introduced. Reorganization of test structures and header parameter support in mocks facilitate the retry-after tests and do not add features outside the linked objectives. The changes in public exports align with the new retry API surface without unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@SoulPancake
SoulPancake force-pushed the feat(js-sdk)-support-retry-after-header branch from e5beab7 to 6bc19e4 Compare October 9, 2025 02:49
@SoulPancake
SoulPancake marked this pull request as ready for review October 9, 2025 18:32
@SoulPancake
SoulPancake requested a review from rhamzeh October 9, 2025 18:32

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2a6003 and e12cc58.

📒 Files selected for processing (3)
  • config/clients/js/template/common.mustache (2 hunks)
  • config/clients/js/template/tests/helpers/nocks.ts.mustache (1 hunks)
  • config/clients/js/template/tests/index.test.ts.mustache (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
config/**/*.mustache

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Validate mustache syntax and variable references across all template files, including CHANGELOG.md.mustache

Files:

  • config/clients/js/template/tests/helpers/nocks.ts.mustache
  • config/clients/js/template/tests/index.test.ts.mustache
  • config/clients/js/template/common.mustache
config/**/*.{json,mustache}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Never hardcode API keys or credentials in configuration or template files

Files:

  • config/clients/js/template/tests/helpers/nocks.ts.mustache
  • config/clients/js/template/tests/index.test.ts.mustache
  • config/clients/js/template/common.mustache
🧠 Learnings (1)
📚 Learning: 2025-08-12T14:18:58.827Z
Learnt from: rhamzeh
PR: openfga/sdk-generator#573
File: config/clients/java/config.overrides.json:184-191
Timestamp: 2025-08-12T14:18:58.827Z
Learning: In the OpenFGA SDK generator, templates are inherited from the upstream OpenAPI Generator (openapitools/openapi-generator-cli). Only custom or modified templates need to be explicitly defined in the config overrides files. Base templates like `settings.gradle.mustache` are provided by the upstream generator and don't require override mappings.

Applied to files:

  • config/clients/js/template/common.mustache
🪛 Gitleaks (8.28.0)
config/clients/js/template/tests/index.test.ts.mustache

[high] 881-881: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

Comment thread config/clients/js/template/common.mustache
Comment thread config/clients/js/template/common.mustache
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.

Improve the retry strategy

3 participants